Merge branch 'develop' into e-commerce-refactor-develop
diff --git a/.flake8 b/.flake8
index 56c9b9a..5735456 100644
--- a/.flake8
+++ b/.flake8
@@ -28,6 +28,7 @@
B007,
B950,
W191,
+ E124, # closing bracket, irritating while writing QB code
max-line-length = 200
exclude=.github/helper/semgrep_rules
diff --git a/.github/labeler.yml b/.github/labeler.yml
index fc3f06d..3aaba71 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,53 +1,53 @@
accounts:
-- 'erpnext/accounts/*'
-- 'erpnext/controllers/accounts_controller.py'
-- 'erpnext/controllers/taxes_and_totals.py'
+- erpnext/accounts/*
+- erpnext/controllers/accounts_controller.py
+- erpnext/controllers/taxes_and_totals.py
stock:
-- 'erpnext/stock/*'
-- 'erpnext/controllers/stock_controller.py'
-- 'erpnext/controllers/item_variant.py'
+- erpnext/stock/*
+- erpnext/controllers/stock_controller.py
+- erpnext/controllers/item_variant.py
assets:
-- 'erpnext/assets/*'
+- erpnext/assets/*
regional:
-- 'erpnext/regional/*'
+- erpnext/regional/*
selling:
-- 'erpnext/selling/*'
-- 'erpnext/controllers/selling_controller.py'
+- erpnext/selling/*
+- erpnext/controllers/selling_controller.py
buying:
-- 'erpnext/buying/*'
-- 'erpnext/controllers/buying_controller.py'
+- erpnext/buying/*
+- erpnext/controllers/buying_controller.py
support:
-- 'erpnext/support/*'
+- erpnext/support/*
POS:
-- 'pos*'
+- pos*
ecommerce:
-- 'erpnext/e_commerce/*'
+- erpnext/e_commerce/*
maintenance:
-- 'erpnext/maintenance/*'
+- erpnext/maintenance/*
manufacturing:
-- 'erpnext/manufacturing/*'
+- erpnext/manufacturing/*
crm:
-- 'erpnext/crm/*'
+- erpnext/crm/*
HR:
-- 'erpnext/hr/*'
+- erpnext/hr/*
payroll:
-- 'erpnext/payroll*'
+- erpnext/payroll*
projects:
-- 'erpnext/projects/*'
+- erpnext/projects/*
# Any python files modifed but no test files modified
needs-tests:
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 22c81dd..9e2cdff 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -254,11 +254,13 @@
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
+ accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto')
+
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): return
- account_currency = get_account_currency(item.expense_account)
+ account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account
@@ -279,6 +281,10 @@
if not amount:
return
+ # check if books nor frozen till endate:
+ if getdate(end_date) >= getdate(accounts_frozen_upto):
+ end_date = get_last_day(add_days(accounts_frozen_upto, 1))
+
if via_journal_entry:
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
@@ -406,8 +412,6 @@
'account': credit_account,
'credit': base_amount,
'credit_in_account_currency': amount,
- 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
- 'party': against,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
@@ -420,8 +424,6 @@
'account': debit_account,
'debit': base_amount,
'debit_in_account_currency': amount,
- 'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
- 'party': against,
'account_currency': account_currency,
'reference_name': doc.name,
'reference_type': doc.doctype,
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index e786d13..1403303 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -16,6 +16,7 @@
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter
+INVALID_VALUES = ("", None)
class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs):
@@ -95,6 +96,18 @@
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
data_import.export_errored_rows()
+def parse_data_from_template(raw_data):
+ data = []
+
+ for i, row in enumerate(raw_data):
+ if all(v in INVALID_VALUES for v in row):
+ # empty row
+ continue
+
+ data.append(row)
+
+ return data
+
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
"""This method runs in background job"""
@@ -104,7 +117,8 @@
file = import_file_path if import_file_path else google_sheets_url
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
- data = import_file.raw_data
+
+ data = parse_data_from_template(import_file.raw_data)
if import_file_path:
add_bank_account(data, bank_account)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 44cea31..51e1d6e 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -2,9 +2,10 @@
# For license information, please see license.txt
+from functools import reduce
+
import frappe
from frappe.utils import flt
-from six.moves import reduce
from erpnext.controllers.status_updater import StatusUpdater
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 8fc4e8c..ac8ab31 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -407,13 +407,14 @@
debit_or_credit = 'Debit' if d.debit else 'Credit'
party_account = get_deferred_booking_accounts(d.reference_type, d.reference_detail_no,
debit_or_credit)
+ against_voucher = ['', against_voucher[1]]
else:
if d.reference_type == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
else:
party_account = against_voucher[1]
- if (against_voucher[0] != d.party or party_account != d.account):
+ if (against_voucher[0] != cstr(d.party) or party_account != d.account):
frappe.throw(_("Row {0}: Party / Account does not match with {1} / {2} in {3} {4}")
.format(d.idx, field_dict.get(d.reference_type)[0], field_dict.get(d.reference_type)[1],
d.reference_type, d.reference_name))
@@ -478,13 +479,22 @@
def set_against_account(self):
accounts_debited, accounts_credited = [], []
- for d in self.get("accounts"):
- if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
- if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
+ if self.voucher_type in ('Deferred Revenue', 'Deferred Expense'):
+ for d in self.get('accounts'):
+ if d.reference_type == 'Sales Invoice':
+ field = 'customer'
+ else:
+ field = 'supplier'
- for d in self.get("accounts"):
- if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
- if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
+ d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
+ else:
+ for d in self.get("accounts"):
+ if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
+ if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
+
+ for d in self.get("accounts"):
+ if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
+ if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
def validate_debit_credit_amount(self):
for d in self.get('accounts'):
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index ddb833f..19d8d49 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -135,7 +135,7 @@
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
rate = flt(row.outstanding_amount) / flt(row.qty)
- return frappe._dict({
+ item_dict = frappe._dict({
"uom": default_uom,
"rate": rate or 0.0,
"qty": row.qty,
@@ -146,6 +146,13 @@
"cost_center": cost_center
})
+ for dimension in get_accounting_dimensions():
+ item_dict.update({
+ dimension: row.get(dimension)
+ })
+
+ return item_dict
+
item = get_item_dict()
invoice = frappe._dict({
@@ -166,7 +173,7 @@
accounting_dimension = get_accounting_dimensions()
for dimension in accounting_dimension:
invoice.update({
- dimension: item.get(dimension)
+ dimension: self.get(dimension) or item.get(dimension)
})
return invoice
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index b5aae98..6700e9b 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -7,21 +7,26 @@
from frappe.cache_manager import clear_doctype_cache
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
+from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
+ create_dimension,
+ disable_dimension,
+)
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account,
)
-test_dependencies = ["Customer", "Supplier"]
+test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
class TestOpeningInvoiceCreationTool(unittest.TestCase):
def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
+ create_dimension()
- def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
+ def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
- party_1=party_1, party_2=party_2, invoice_number=invoice_number)
+ party_1=party_1, party_2=party_2, invoice_number=invoice_number, department=department)
doc.update(args)
return doc.make_invoices()
@@ -106,6 +111,19 @@
doc = frappe.get_doc('Sales Invoice', inv)
doc.cancel()
+ def test_opening_invoice_with_accounting_dimension(self):
+ invoices = self.make_invoices(invoice_type="Sales", company="_Test Opening Invoice Company", department='Sales - _TOIC')
+
+ expected_value = {
+ "keys": ["customer", "outstanding_amount", "status", "department"],
+ 0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"],
+ 1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"],
+ }
+ self.check_expected_values(invoices, expected_value, invoice_type="Sales")
+
+ def tearDown(self):
+ disable_dimension()
+
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 0e07abd..02a144d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -3,6 +3,7 @@
import json
+from functools import reduce
import frappe
from frappe import ValidationError, _, scrub, throw
@@ -1523,6 +1524,10 @@
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
+ if dt in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice']:
+ pe.project = (doc.get('project') or
+ reduce(lambda prev,cur: prev or cur, [x.get('project') for x in doc.get('items')], None)) # get first non-empty project from items
+
if pe.party_type in ["Customer", "Supplier"]:
bank_account = get_party_bank_account(pe.party_type, pe.party)
pe.set("bank_account", bank_account)
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 134bccf..f66abdc 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -158,6 +158,20 @@
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
+ def validate_invalid_serial_nos(self, item):
+ serial_nos = get_serial_nos(item.serial_no)
+ error_msg = []
+ invalid_serials, msg = "", ""
+ for serial_no in serial_nos:
+ if not frappe.db.exists('Serial No', serial_no):
+ invalid_serials = invalid_serials + (", " if invalid_serials else "") + serial_no
+ msg = (_("Row #{}: Following Serial numbers for item {} are <b>Invalid</b>: {}").format(item.idx, frappe.bold(item.get("item_code")), frappe.bold(invalid_serials)))
+ if invalid_serials:
+ error_msg.append(msg)
+
+ if error_msg:
+ frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
+
def validate_stock_availablility(self):
if self.is_return or self.docstatus != 1:
return
@@ -167,6 +181,7 @@
if d.serial_no:
self.validate_pos_reserved_serial_nos(d)
self.validate_delivered_serial_nos(d)
+ self.validate_invalid_serial_nos(d)
elif d.batch_no:
self.validate_pos_reserved_batch_qty(d)
else:
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 56479a0..ba751c0 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -354,6 +354,24 @@
pos2.insert()
self.assertRaises(frappe.ValidationError, pos2.submit)
+ def test_invalid_serial_no_validation(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+ se = make_serialized_item(company='_Test Company',
+ target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
+ serial_nos = se.get("items")[0].serial_no + 'wrong'
+
+ pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
+ account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
+ expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
+ item=se.get("items")[0].item_code, rate=1000, qty=2, do_not_save=1)
+
+ pos.get('items')[0].has_serial_no = 1
+ pos.get('items')[0].serial_no = serial_nos
+ pos.insert()
+
+ self.assertRaises(frappe.ValidationError, pos.submit)
+
def test_loyalty_points(self):
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 5746a84..968137e 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -628,6 +628,26 @@
for doc in [si, si1]:
doc.delete()
+ def test_multiple_pricing_rules_with_min_qty(self):
+ make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4,
+ apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1")
+ make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4,
+ apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2")
+
+ si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
+ item = si.items[0]
+ item.stock_qty = 1
+ si.save()
+ self.assertFalse(item.discount_percentage)
+ item.qty = 5
+ item.stock_qty = 5
+ si.save()
+ self.assertEqual(item.discount_percentage, 30)
+ si.delete()
+
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1")
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2")
+
test_dependencies = ["Campaign"]
def make_pricing_rule(**args):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 02bfc9d..7792590 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -73,7 +73,7 @@
for key in sorted(pricing_rule_dict):
pricing_rules_list.extend(pricing_rule_dict.get(key))
- return pricing_rules_list or pricing_rules
+ return pricing_rules_list
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = []
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js
index 6336db1..f54bce8 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js
@@ -1,6 +1,8 @@
{% include "erpnext/regional/india/taxes.js" %}
+{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
erpnext.setup_auto_gst_taxation('Sales Invoice');
+erpnext.setup_einvoice_actions('Sales Invoice')
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
index d9d6634..f01325d 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
@@ -36,4 +36,139 @@
};
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
+
+ const generate_irns = () => {
+ const docnames = list_view.get_checked_items(true);
+ if (docnames && docnames.length) {
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
+ args: { docnames },
+ freeze: true,
+ freeze_message: __('Generating E-Invoices...')
+ });
+ } else {
+ frappe.msgprint({
+ message: __('Please select at least one sales invoice to generate IRN'),
+ title: __('No Invoice Selected'),
+ indicator: 'red'
+ });
+ }
+ };
+
+ const cancel_irns = () => {
+ const docnames = list_view.get_checked_items(true);
+
+ const fields = [
+ {
+ "label": "Reason",
+ "fieldname": "reason",
+ "fieldtype": "Select",
+ "reqd": 1,
+ "default": "1-Duplicate",
+ "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+ },
+ {
+ "label": "Remark",
+ "fieldname": "remark",
+ "fieldtype": "Data",
+ "reqd": 1
+ }
+ ];
+
+ const d = new frappe.ui.Dialog({
+ title: __("Cancel IRN"),
+ fields: fields,
+ primary_action: function() {
+ const data = d.get_values();
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
+ args: {
+ doctype: list_view.doctype,
+ docnames,
+ reason: data.reason.split('-')[0],
+ remark: data.remark
+ },
+ freeze: true,
+ freeze_message: __('Cancelling E-Invoices...'),
+ });
+ d.hide();
+ },
+ primary_action_label: __('Submit')
+ });
+ d.show();
+ };
+
+ let einvoicing_enabled = false;
+ frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
+ einvoicing_enabled = enabled;
+ });
+
+ list_view.$result.on("change", "input[type=checkbox]", () => {
+ if (einvoicing_enabled) {
+ const docnames = list_view.get_checked_items(true);
+ // show/hide e-invoicing actions when no sales invoices are checked
+ if (docnames && docnames.length) {
+ // prevent adding actions twice if e-invoicing action group already exists
+ if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
+ list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
+ list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
+ }
+ } else {
+ list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
+ list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
+ }
+ }
+ });
+
+ frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
+ const { failures, user, invoices } = data;
+
+ if (invoices.length != failures.length) {
+ frappe.msgprint({
+ message: __('{0} e-invoices generated successfully', [invoices.length]),
+ title: __('Bulk E-Invoice Generation Complete'),
+ indicator: 'orange'
+ });
+ }
+
+ if (failures && failures.length && user == frappe.session.user) {
+ let message = `
+ Failed to generate IRNs for following ${failures.length} sales invoices:
+ <ul style="padding-left: 20px; padding-top: 5px;">
+ ${failures.map(d => `<li>${d.docname}</li>`).join('')}
+ </ul>
+ `;
+ frappe.msgprint({
+ message: message,
+ title: __('Bulk E-Invoice Generation Complete'),
+ indicator: 'orange'
+ });
+ }
+ });
+
+ frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
+ const { failures, user, invoices } = data;
+
+ if (invoices.length != failures.length) {
+ frappe.msgprint({
+ message: __('{0} e-invoices cancelled successfully', [invoices.length]),
+ title: __('Bulk E-Invoice Cancellation Complete'),
+ indicator: 'orange'
+ });
+ }
+
+ if (failures && failures.length && user == frappe.session.user) {
+ let message = `
+ Failed to cancel IRNs for following ${failures.length} sales invoices:
+ <ul style="padding-left: 20px; padding-top: 5px;">
+ ${failures.map(d => `<li>${d.docname}</li>`).join('')}
+ </ul>
+ `;
+ frappe.msgprint({
+ message: message,
+ title: __('Bulk E-Invoice Cancellation Complete'),
+ indicator: 'orange'
+ });
+ }
+ });
};
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 39dfd8d..af6a52a 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -469,7 +469,7 @@
let row = frappe.get_doc(d.doctype, d.name)
set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail)
});
- frm.trigger("calculate_timesheet_totals");
+ this.frm.trigger("calculate_timesheet_totals");
}
}
};
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 98bc953..bc44358 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -43,6 +43,7 @@
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
+from erpnext.stock.utils import calculate_mapped_packed_items_return
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -293,6 +294,8 @@
def before_cancel(self):
self.check_if_consolidated_invoice()
+
+ super(SalesInvoice, self).before_cancel()
self.update_time_sheet(None)
def on_cancel(self):
@@ -728,8 +731,11 @@
def update_packing_list(self):
if cint(self.update_stock) == 1:
- from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
- make_packing_list(self)
+ if cint(self.is_return) and self.return_against:
+ calculate_mapped_packed_items_return(self)
+ else:
+ from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+ make_packing_list(self)
else:
self.set('packed_items', [])
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c02c80a..941061f 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1781,47 +1781,6 @@
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
- def test_deferred_revenue_post_account_freeze_upto_by_admin(self):
- frappe.set_user("Administrator")
-
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
- frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
-
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
-
- item = create_item("_Test Item for Deferred Accounting")
- item.enable_deferred_revenue = 1
- item.deferred_revenue_account = deferred_account
- item.no_of_months = 12
- item.save()
-
- si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True)
- si.items[0].enable_deferred_revenue = 1
- si.items[0].service_start_date = "2019-01-10"
- si.items[0].service_end_date = "2019-03-15"
- si.items[0].deferred_revenue_account = deferred_account
- si.save()
- si.submit()
-
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
- frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager')
-
- pda1 = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date=nowdate(),
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Income",
- company="_Test Company"
- ))
-
- pda1.insert()
- self.assertRaises(frappe.ValidationError, pda1.submit)
-
- frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
- frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
-
def test_fixed_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
@@ -2141,6 +2100,54 @@
self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
self.assertEqual(data['billLists'][0]['fromStateCode'],27)
+ def test_einvoice_submission_without_irn(self):
+ # init
+ einvoice_settings = frappe.get_doc('E Invoice Settings')
+ einvoice_settings.enable = 1
+ einvoice_settings.applicable_from = nowdate()
+ einvoice_settings.append('credentials', {
+ 'company': '_Test Company',
+ 'gstin': '27AAECE4835E1ZR',
+ 'username': 'test',
+ 'password': 'test'
+ })
+ einvoice_settings.save()
+
+ country = frappe.flags.country
+ frappe.flags.country = 'India'
+
+ si = make_sales_invoice_for_ewaybill()
+ self.assertRaises(frappe.ValidationError, si.submit)
+
+ si.irn = 'test_irn'
+ si.submit()
+
+ # reset
+ einvoice_settings = frappe.get_doc('E Invoice Settings')
+ einvoice_settings.enable = 0
+ frappe.flags.country = country
+
+ def test_einvoice_json(self):
+ from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
+
+ si = get_sales_invoice_for_e_invoice()
+ si.discount_amount = 100
+ si.save()
+
+ einvoice = make_einvoice(si)
+ self.assertTrue(einvoice['EwbDtls'])
+ validate_totals(einvoice)
+
+ si.apply_discount_on = 'Net Total'
+ si.save()
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+ [d.set('included_in_print_rate', 1) for d in si.taxes]
+ si.save()
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
def test_item_tax_net_range(self):
item = create_item("T Shirt")
@@ -2233,9 +2240,9 @@
asset.load_from_db()
expected_values = [
- ["2020-06-30", 1311.48, 1311.48],
- ["2021-06-30", 20000.0, 21311.48],
- ["2021-09-30", 5041.1, 26352.58]
+ ["2020-06-30", 1366.12, 1366.12],
+ ["2021-06-30", 20000.0, 21366.12],
+ ["2021-09-30", 5041.1, 26407.22]
]
for i, schedule in enumerate(asset.schedules):
@@ -2283,12 +2290,12 @@
asset.load_from_db()
expected_values = [
- ["2020-06-30", 1311.48, 1311.48, True],
- ["2021-06-30", 20000.0, 21311.48, True],
- ["2022-06-30", 20000.0, 41311.48, False],
- ["2023-06-30", 20000.0, 61311.48, False],
- ["2024-06-30", 20000.0, 81311.48, False],
- ["2025-06-06", 18688.52, 100000.0, False]
+ ["2020-06-30", 1366.12, 1366.12, True],
+ ["2021-06-30", 20000.0, 21366.12, True],
+ ["2022-06-30", 20000.0, 41366.12, False],
+ ["2023-06-30", 20000.0, 61366.12, False],
+ ["2024-06-30", 20000.0, 81366.12, False],
+ ["2025-06-06", 18633.88, 100000.0, False]
]
for i, schedule in enumerate(asset.schedules):
@@ -2482,6 +2489,74 @@
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
+ def test_multi_currency_deferred_revenue_via_journal_entry(self):
+ deferred_account = create_account(account_name="Deferred Revenue",
+ parent_account="Current Liabilities - _TC", company="_Test Company")
+
+ acc_settings = frappe.get_single('Accounts Settings')
+ acc_settings.book_deferred_entries_via_journal_entry = 1
+ acc_settings.submit_journal_entries = 1
+ acc_settings.save()
+
+ item = create_item("_Test Item for Deferred Accounting")
+ item.enable_deferred_expense = 1
+ item.deferred_revenue_account = deferred_account
+ item.save()
+
+ si = create_sales_invoice(customer='_Test Customer USD', currency='USD',
+ item=item.name, qty=1, rate=100, conversion_rate=60, do_not_save=True)
+
+ si.set_posting_time = 1
+ si.posting_date = '2019-01-01'
+ si.debit_to = '_Test Receivable USD - _TC'
+ si.items[0].enable_deferred_revenue = 1
+ si.items[0].service_start_date = "2019-01-01"
+ si.items[0].service_end_date = "2019-03-30"
+ si.items[0].deferred_expense_account = deferred_account
+ si.save()
+ si.submit()
+
+ frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
+
+ pda1 = frappe.get_doc(dict(
+ doctype='Process Deferred Accounting',
+ posting_date=nowdate(),
+ start_date="2019-01-01",
+ end_date="2019-03-31",
+ type="Income",
+ company="_Test Company"
+ ))
+
+ pda1.insert()
+ pda1.submit()
+
+ expected_gle = [
+ ["Sales - _TC", 0.0, 2089.89, "2019-01-28"],
+ [deferred_account, 2089.89, 0.0, "2019-01-28"],
+ ["Sales - _TC", 0.0, 1887.64, "2019-02-28"],
+ [deferred_account, 1887.64, 0.0, "2019-02-28"],
+ ["Sales - _TC", 0.0, 2022.47, "2019-03-15"],
+ [deferred_account, 2022.47, 0.0, "2019-03-15"]
+ ]
+
+ gl_entries = gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ from `tabGL Entry`
+ where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
+ order by posting_date asc, account asc""", (si.items[0].name, si.posting_date), as_dict=1)
+
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_gle[i][0], gle.account)
+ self.assertEqual(expected_gle[i][1], gle.credit)
+ self.assertEqual(expected_gle[i][2], gle.debit)
+ self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
+
+ acc_settings = frappe.get_single('Accounts Settings')
+ acc_settings.book_deferred_entries_via_journal_entry = 0
+ acc_settings.submit_journal_entriessubmit_journal_entries = 0
+ acc_settings.save()
+
+ frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
+
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index 7e51299..792e7d2 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -71,7 +71,8 @@
if doc.currency != doc.company_currency:
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
- self.add_shipping_rule_to_tax_table(doc, shipping_amount)
+ if shipping_amount:
+ self.add_shipping_rule_to_tax_table(doc, shipping_amount)
def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):
diff --git a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
index d2c505c..e032bb3 100644
--- a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
+++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
@@ -28,14 +28,14 @@
{
"columns": 2,
"fieldname": "single_threshold",
- "fieldtype": "Currency",
+ "fieldtype": "Float",
"in_list_view": 1,
"label": "Single Transaction Threshold"
},
{
"columns": 3,
"fieldname": "cumulative_threshold",
- "fieldtype": "Currency",
+ "fieldtype": "Float",
"in_list_view": 1,
"label": "Cumulative Transaction Threshold"
},
@@ -59,7 +59,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-08-31 11:42:12.213977",
+ "modified": "2022-01-13 12:04:42.904263",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Rate",
@@ -68,5 +68,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
index 7de9ae1..02e30c3 100644
--- a/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
+++ b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
@@ -2,15 +2,17 @@
"creation": "2021-08-24 12:28:18.044902",
"docstatus": 0,
"doctype": "Form Tour",
+ "first_document": 0,
"idx": 0,
+ "include_name_field": 0,
"is_standard": 1,
- "modified": "2021-08-24 12:28:18.044902",
+ "modified": "2022-01-18 18:32:17.102330",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",
"owner": "Administrator",
"reference_doctype": "Sales Taxes and Charges Template",
- "save_on_complete": 0,
+ "save_on_complete": 1,
"steps": [
{
"description": "A name by which you will identify this template. You can change this later.",
diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
index 2e0ab43..aa7cdf7 100644
--- a/erpnext/accounts/module_onboarding/accounts/accounts.json
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -13,16 +13,13 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0,
"is_complete": 0,
- "modified": "2021-08-13 11:59:35.690443",
+ "modified": "2022-01-18 18:35:52.326688",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",
"owner": "Administrator",
"steps": [
{
- "step": "Company"
- },
- {
"step": "Chart of Accounts"
},
{
diff --git a/erpnext/accounts/onboarding_step/company/company.json b/erpnext/accounts/onboarding_step/company/company.json
deleted file mode 100644
index 4992e4d..0000000
--- a/erpnext/accounts/onboarding_step/company/company.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "action": "Go to Page",
- "action_label": "Let's Review your Company",
- "creation": "2021-06-29 14:47:42.497318",
- "description": "# Company\n\nIn ERPNext, you can also create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company. \n",
- "docstatus": 0,
- "doctype": "Onboarding Step",
- "idx": 0,
- "is_complete": 0,
- "is_single": 0,
- "is_skipped": 0,
- "modified": "2021-08-13 11:43:35.767341",
- "modified_by": "Administrator",
- "name": "Company",
- "owner": "Administrator",
- "path": "app/company",
- "reference_document": "Company",
- "show_form_tour": 0,
- "show_full_form": 0,
- "title": "Review Company",
- "validate_action": 1
-}
\ No newline at end of file
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 6b4b43d..c13bc23 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -58,7 +58,7 @@
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
party = frappe.get_doc(party_type, party)
- currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
+ currency = party.get("default_currency") or currency or get_company_currency(company)
party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
set_contact_details(party_details, party, party_type)
diff --git a/erpnext/demo/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py
similarity index 100%
rename from erpnext/demo/__init__.py
rename to erpnext/accounts/print_format/gst_e_invoice/__init__.py
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
new file mode 100644
index 0000000..e658049
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -0,0 +1,173 @@
+{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
+{%- set einvoice = json.loads(doc.signed_einvoice) -%}
+
+<div class="page-break">
+ <div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
+ {% if letter_head and not no_letterhead %}
+ <div class="letter-head">{{ letter_head }}</div>
+ {% endif %}
+ <div class="print-heading">
+ <h2>E Invoice<br><small>{{ doc.name }}</small></h2>
+ </div>
+ </div>
+ {% if print_settings.repeat_header_footer %}
+ <div id="footer-html" class="visible-pdf">
+ {% if not no_letterhead and footer %}
+ <div class="letter-head-footer">
+ {{ footer }}
+ </div>
+ {% endif %}
+ <p class="text-center small page-number visible-pdf">
+ {{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
+ </p>
+ </div>
+ {% endif %}
+ <h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
+ <div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+ <div class="col-xs-8 column-break">
+ <div class="row data-field">
+ <div class="col-xs-4"><label>IRN</label></div>
+ <div class="col-xs-8 value">{{ einvoice.Irn }}</div>
+ </div>
+ <div class="row data-field">
+ <div class="col-xs-4"><label>Ack. No</label></div>
+ <div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
+ </div>
+ <div class="row data-field">
+ <div class="col-xs-4"><label>Ack. Date</label></div>
+ <div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
+ </div>
+ <div class="row data-field">
+ <div class="col-xs-4"><label>Category</label></div>
+ <div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
+ </div>
+ <div class="row data-field">
+ <div class="col-xs-4"><label>Document Type</label></div>
+ <div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
+ </div>
+ <div class="row data-field">
+ <div class="col-xs-4"><label>Document No</label></div>
+ <div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
+ </div>
+ </div>
+ <div class="col-xs-4 column-break">
+ <img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
+ </div>
+ </div>
+ <h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
+ <div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+ {%- set seller = einvoice.SellerDtls -%}
+ <div class="col-xs-6 column-break">
+ <h5 style="margin-bottom: 5px;">Seller</h5>
+ <p>{{ seller.Gstin }}</p>
+ <p>{{ seller.LglNm }}</p>
+ <p>{{ seller.Addr1 }}</p>
+ {%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
+ <p>{{ seller.Loc }}</p>
+ <p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
+
+ {%- if einvoice.ShipDtls -%}
+ {%- set shipping = einvoice.ShipDtls -%}
+ <h5 style="margin-bottom: 5px;">Shipped From</h5>
+ <p>{{ shipping.Gstin }}</p>
+ <p>{{ shipping.LglNm }}</p>
+ <p>{{ shipping.Addr1 }}</p>
+ {%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
+ <p>{{ shipping.Loc }}</p>
+ <p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
+ {% endif %}
+ </div>
+ {%- set buyer = einvoice.BuyerDtls -%}
+ <div class="col-xs-6 column-break">
+ <h5 style="margin-bottom: 5px;">Buyer</h5>
+ <p>{{ buyer.Gstin }}</p>
+ <p>{{ buyer.LglNm }}</p>
+ <p>{{ buyer.Addr1 }}</p>
+ {%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
+ <p>{{ buyer.Loc }}</p>
+ <p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
+
+ {%- if einvoice.DispDtls -%}
+ {%- set dispatch = einvoice.DispDtls -%}
+ <h5 style="margin-bottom: 5px;">Dispatched From</h5>
+ {%- if dispatch.Gstin -%} <p>{{ dispatch.Gstin }}</p> {% endif %}
+ <p>{{ dispatch.LglNm }}</p>
+ <p>{{ dispatch.Addr1 }}</p>
+ {%- if dispatch.Addr2 -%} <p>{{ dispatch.Addr2 }}</p> {% endif %}
+ <p>{{ dispatch.Loc }}</p>
+ <p>{{ frappe.db.get_value("Address", doc.dispatch_address_name, "gst_state") }} - {{ dispatch.Pin }}</p>
+ {% endif %}
+ </div>
+ </div>
+ <div style="overflow-x: auto;">
+ <h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th class="text-left" style="width: 3%;">Sr. No.</th>
+ <th class="text-left">Item</th>
+ <th class="text-left" style="width: 10%;">HSN Code</th>
+ <th class="text-left" style="width: 5%;">Qty</th>
+ <th class="text-left" style="width: 5%;">UOM</th>
+ <th class="text-left">Rate</th>
+ <th class="text-left" style="width: 5%;">Discount</th>
+ <th class="text-left">Taxable Amount</th>
+ <th class="text-left" style="width: 7%;">Tax Rate</th>
+ <th class="text-left" style="width: 5%;">Other Charges</th>
+ <th class="text-left">Total</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for item in einvoice.ItemList %}
+ <tr>
+ <td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
+ <td class="text-left">{{ item.PrdDesc }}</td>
+ <td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
+ <td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
+ <td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
+ <td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
+ <td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
+ <td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ <div style="overflow-x: auto;">
+ <h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th class="text-left">Taxable Amount</th>
+ <th class="text-left">CGST</th>
+ <th class="text-left"">SGST</th>
+ <th class="text-left">IGST</th>
+ <th class="text-left">CESS</th>
+ <th class="text-left" style="width: 10%;">State CESS</th>
+ <th class="text-left">Discount</th>
+ <th class="text-left" style="width: 10%;">Other Charges</th>
+ <th class="text-left" style="width: 10%;">Round Off</th>
+ <th class="text-left">Total Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ {%- set value_details = einvoice.ValDtls -%}
+ <tr>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
+ <td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
new file mode 100644
index 0000000..1001199
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
@@ -0,0 +1,24 @@
+{
+ "align_labels_right": 1,
+ "creation": "2020-10-10 18:01:21.032914",
+ "custom_format": 0,
+ "default_print_language": "en-US",
+ "disabled": 1,
+ "doc_type": "Sales Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "",
+ "idx": 0,
+ "line_breaks": 1,
+ "modified": "2020-10-23 19:54:40.634936",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "GST E-Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 1,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 4559fa9..8e3bd8b 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -5,7 +5,6 @@
import frappe
from frappe import _, scrub
from frappe.utils import cint, flt
-from six import iteritems
from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@@ -40,7 +39,7 @@
if self.filters.show_gl_balance:
gl_balance_map = get_gl_balance(self.filters.report_date)
- for party, party_dict in iteritems(self.party_total):
+ for party, party_dict in self.party_total.items():
if party_dict.outstanding == 0:
continue
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
index 1de6fb6..86eb213 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
@@ -17,10 +17,42 @@
class TestDeferredRevenueAndExpense(unittest.TestCase):
@classmethod
def setUpClass(self):
- clear_old_entries()
+ clear_accounts_and_items()
create_company()
+ self.maxDiff = None
+
+ def clear_old_entries(self):
+ sinv = qb.DocType("Sales Invoice")
+ sinv_item = qb.DocType("Sales Invoice Item")
+ pinv = qb.DocType("Purchase Invoice")
+ pinv_item = qb.DocType("Purchase Invoice Item")
+
+ # delete existing invoices with deferred items
+ deferred_invoices = (
+ qb.from_(sinv)
+ .join(sinv_item)
+ .on(sinv.name == sinv_item.parent)
+ .select(sinv.name)
+ .where(sinv_item.enable_deferred_revenue == 1)
+ .run()
+ )
+ if deferred_invoices:
+ qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
+
+ deferred_invoices = (
+ qb.from_(pinv)
+ .join(pinv_item)
+ .on(pinv.name == pinv_item.parent)
+ .select(pinv.name)
+ .where(pinv_item.enable_deferred_expense == 1)
+ .run()
+ )
+ if deferred_invoices:
+ qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
def test_deferred_revenue(self):
+ self.clear_old_entries()
+
# created deferred expense accounts, if not found
deferred_revenue_account = create_account(
account_name="Deferred Revenue",
@@ -108,6 +140,8 @@
self.assertEqual(report.period_total, expected)
def test_deferred_expense(self):
+ self.clear_old_entries()
+
# created deferred expense accounts, if not found
deferred_expense_account = create_account(
account_name="Deferred Expense",
@@ -198,6 +232,91 @@
]
self.assertEqual(report.period_total, expected)
+ def test_zero_months(self):
+ self.clear_old_entries()
+ # created deferred expense accounts, if not found
+ deferred_revenue_account = create_account(
+ account_name="Deferred Revenue",
+ parent_account="Current Liabilities - _CD",
+ company="_Test Company DR",
+ )
+
+ acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
+ acc_settings.book_deferred_entries_based_on = "Months"
+ acc_settings.save()
+
+ customer = frappe.new_doc("Customer")
+ customer.customer_name = "_Test Customer DR"
+ customer.type = "Individual"
+ customer.insert()
+
+ item = create_item(
+ "_Test Internet Subscription",
+ is_stock_item=0,
+ warehouse="All Warehouses - _CD",
+ company="_Test Company DR",
+ )
+ item.enable_deferred_revenue = 1
+ item.deferred_revenue_account = deferred_revenue_account
+ item.no_of_months = 0
+ item.save()
+
+ si = create_sales_invoice(
+ item=item.name,
+ company="_Test Company DR",
+ customer="_Test Customer DR",
+ debit_to="Debtors - _CD",
+ posting_date="2021-05-01",
+ parent_cost_center="Main - _CD",
+ cost_center="Main - _CD",
+ do_not_submit=True,
+ rate=300,
+ price_list_rate=300,
+ )
+ si.items[0].enable_deferred_revenue = 1
+ si.items[0].deferred_revenue_account = deferred_revenue_account
+ si.items[0].income_account = "Sales - _CD"
+ si.save()
+ si.submit()
+
+ pda = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ posting_date=nowdate(),
+ start_date="2021-05-01",
+ end_date="2021-08-01",
+ type="Income",
+ company="_Test Company DR",
+ )
+ )
+ pda.insert()
+ pda.submit()
+
+ # execute report
+ fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
+ self.filters = frappe._dict(
+ {
+ "company": frappe.defaults.get_user_default("Company"),
+ "filter_based_on": "Date Range",
+ "period_start_date": "2021-05-01",
+ "period_end_date": "2021-08-01",
+ "from_fiscal_year": fiscal_year.year,
+ "to_fiscal_year": fiscal_year.year,
+ "periodicity": "Monthly",
+ "type": "Revenue",
+ "with_upcoming_postings": False,
+ }
+ )
+
+ report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
+ report.run()
+ expected = [
+ {"key": "may_2021", "total": 300.0, "actual": 300.0},
+ {"key": "jun_2021", "total": 0, "actual": 0},
+ {"key": "jul_2021", "total": 0, "actual": 0},
+ {"key": "aug_2021", "total": 0, "actual": 0},
+ ]
+ self.assertEqual(report.period_total, expected)
def create_company():
company = frappe.db.exists("Company", "_Test Company DR")
@@ -209,15 +328,11 @@
company.insert()
-def clear_old_entries():
+def clear_accounts_and_items():
item = qb.DocType("Item")
account = qb.DocType("Account")
customer = qb.DocType("Customer")
supplier = qb.DocType("Supplier")
- sinv = qb.DocType("Sales Invoice")
- sinv_item = qb.DocType("Sales Invoice Item")
- pinv = qb.DocType("Purchase Invoice")
- pinv_item = qb.DocType("Purchase Invoice Item")
qb.from_(account).delete().where(
(account.account_name == "Deferred Revenue")
@@ -228,26 +343,3 @@
).run()
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
-
- # delete existing invoices with deferred items
- deferred_invoices = (
- qb.from_(sinv)
- .join(sinv_item)
- .on(sinv.name == sinv_item.parent)
- .select(sinv.name)
- .where(sinv_item.enable_deferred_revenue == 1)
- .run()
- )
- if deferred_invoices:
- qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
-
- deferred_invoices = (
- qb.from_(pinv)
- .join(pinv_item)
- .on(pinv.name == pinv_item.parent)
- .select(pinv.name)
- .where(pinv_item.enable_deferred_expense == 1)
- .run()
- )
- if deferred_invoices:
- qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 33d1748..203ea20 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -5,7 +5,7 @@
"label": "Profit and Loss"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Accounts\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chart of Accounts\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Journal Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payment Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Trial Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounting Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Payable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Financial Statements\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Multi Currency\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bank Statement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Subscription Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goods and Services Tax (GST India)\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Share Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Cost Center and Budgeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening and Closing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Profitability\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goods and Services Tax (GST India)\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]",
"creation": "2020-03-02 15:41:59.515192",
"docstatus": 0,
"doctype": "Workspace",
@@ -230,6 +230,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
+ "link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
@@ -346,6 +347,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
+ "link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
@@ -527,16 +529,17 @@
"type": "Link"
},
{
- "dependencies": "GL Entry",
- "hidden": 0,
- "is_query_report": 1,
- "label": "KSA VAT Report",
- "link_to": "KSA VAT",
- "link_type": "Report",
- "onboard": 0,
- "only_for": "Saudi Arabia",
- "type": "Link"
- },
+ "dependencies": "GL Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "KSA VAT Report",
+ "link_count": 0,
+ "link_to": "KSA VAT",
+ "link_type": "Report",
+ "onboard": 0,
+ "only_for": "Saudi Arabia",
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -1158,15 +1161,16 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "KSA VAT Setting",
- "link_to": "KSA VAT Setting",
- "link_type": "DocType",
- "onboard": 0,
- "only_for": "Saudi Arabia",
- "type": "Link"
- },
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "KSA VAT Setting",
+ "link_count": 0,
+ "link_to": "KSA VAT Setting",
+ "link_type": "DocType",
+ "onboard": 0,
+ "only_for": "Saudi Arabia",
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -1220,7 +1224,7 @@
"type": "Link"
}
],
- "modified": "2021-08-27 12:15:52.872471",
+ "modified": "2022-01-13 17:25:09.835345",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@@ -1229,7 +1233,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 2,
+ "sequence_id": 2.0,
"shortcuts": [
{
"label": "Chart of Accounts",
@@ -1278,4 +1282,4 @@
}
],
"title": "Accounting"
-}
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 153f5c5..f414930 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -108,6 +108,10 @@
frm.trigger("create_asset_repair");
}, __("Manage"));
+ frm.add_custom_button(__("Split Asset"), function() {
+ frm.trigger("split_asset");
+ }, __("Manage"));
+
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() {
frm.trigger("create_asset_value_adjustment");
@@ -322,6 +326,43 @@
});
},
+ split_asset: function(frm) {
+ const title = __('Split Asset');
+
+ const fields = [
+ {
+ fieldname: 'split_qty',
+ fieldtype: 'Int',
+ label: __('Split Qty'),
+ reqd: 1
+ }
+ ];
+
+ let dialog = new frappe.ui.Dialog({
+ title: title,
+ fields: fields
+ });
+
+ dialog.set_primary_action(__('Split'), function() {
+ const dialog_data = dialog.get_values();
+ frappe.call({
+ args: {
+ "asset_name": frm.doc.name,
+ "split_qty": cint(dialog_data.split_qty)
+ },
+ method: "erpnext.assets.doctype.asset.asset.split_asset",
+ callback: function(r) {
+ let doclist = frappe.model.sync(r.message);
+ frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+ }
+ });
+
+ dialog.hide();
+ });
+
+ dialog.show();
+ },
+
create_asset_value_adjustment: function(frm) {
frappe.call({
args: {
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index de06075..6e6bbf1 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
- "creation": "2016-03-01 17:01:27.920130",
+ "creation": "2022-01-18 02:26:55.975005",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
@@ -23,6 +23,7 @@
"asset_name",
"asset_category",
"location",
+ "split_from",
"custodian",
"department",
"disposal_date",
@@ -35,6 +36,7 @@
"available_for_use_date",
"column_break_23",
"gross_purchase_amount",
+ "asset_quantity",
"purchase_date",
"section_break_23",
"calculate_depreciation",
@@ -141,6 +143,7 @@
},
{
"allow_on_submit": 1,
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
@@ -480,6 +483,19 @@
"fieldname": "section_break_36",
"fieldtype": "Section Break",
"label": "Finance Books"
+ },
+ {
+ "fieldname": "split_from",
+ "fieldtype": "Link",
+ "label": "Split From",
+ "options": "Asset",
+ "read_only": 1
+ },
+ {
+ "fieldname": "asset_quantity",
+ "fieldtype": "Int",
+ "label": "Asset Quantity",
+ "read_only_depends_on": "eval:!doc.is_existing_asset"
}
],
"idx": 72,
@@ -502,10 +518,11 @@
"link_fieldname": "asset"
}
],
- "modified": "2021-06-24 14:58:51.097908",
+ "modified": "2022-01-30 20:19:24.680027",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -542,6 +559,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "asset_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index a18b03a..6e87426 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -36,8 +36,10 @@
self.validate_asset_values()
self.validate_asset_and_reference()
self.validate_item()
+ self.validate_cost_center()
self.set_missing_values()
- self.prepare_depreciation_data()
+ if not self.split_from:
+ self.prepare_depreciation_data()
self.validate_gross_and_purchase_amount()
if self.get("schedules"):
self.validate_expected_value_after_useful_life()
@@ -95,6 +97,19 @@
elif item.is_stock_item:
frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code))
+ def validate_cost_center(self):
+ if not self.cost_center: return
+
+ cost_center_company = frappe.db.get_value('Cost Center', self.cost_center, 'company')
+ if cost_center_company != self.company:
+ frappe.throw(
+ _("Selected Cost Center {} doesn't belongs to {}").format(
+ frappe.bold(self.cost_center),
+ frappe.bold(self.company)
+ ),
+ title=_("Invalid Cost Center")
+ )
+
def validate_in_use_date(self):
if not self.available_for_use_date:
frappe.throw(_("Available for use date is required"))
@@ -188,142 +203,143 @@
start = self.clear_depreciation_schedule()
for finance_book in self.get('finance_books'):
- self.validate_asset_finance_books(finance_book)
+ self._make_depreciation_schedule(finance_book, start, date_of_sale)
- # value_after_depreciation - current Asset value
- if self.docstatus == 1 and finance_book.value_after_depreciation:
- value_after_depreciation = flt(finance_book.value_after_depreciation)
- else:
- value_after_depreciation = (flt(self.gross_purchase_amount) -
- flt(self.opening_accumulated_depreciation))
+ def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
+ self.validate_asset_finance_books(finance_book)
- finance_book.value_after_depreciation = value_after_depreciation
+ value_after_depreciation = self._get_value_after_depreciation(finance_book)
+ finance_book.value_after_depreciation = value_after_depreciation
- number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
- cint(self.number_of_depreciations_booked)
+ number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
+ cint(self.number_of_depreciations_booked)
- has_pro_rata = self.check_is_pro_rata(finance_book)
+ has_pro_rata = self.check_is_pro_rata(finance_book)
+ if has_pro_rata:
+ number_of_pending_depreciations += 1
- if has_pro_rata:
- number_of_pending_depreciations += 1
+ skip_row = False
- skip_row = False
+ for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
+ # If depreciation is already completed (for double declining balance)
+ if skip_row: continue
- for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
- # If depreciation is already completed (for double declining balance)
- if skip_row: continue
+ depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
- depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
+ if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
+ schedule_date = add_months(finance_book.depreciation_start_date,
+ n * cint(finance_book.frequency_of_depreciation))
- if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
- schedule_date = add_months(finance_book.depreciation_start_date,
- n * cint(finance_book.frequency_of_depreciation))
+ # schedule date will be a year later from start date
+ # so monthly schedule date is calculated by removing 11 months from it
+ monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
- # schedule date will be a year later from start date
- # so monthly schedule date is calculated by removing 11 months from it
- monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
-
- # if asset is being sold
- if date_of_sale:
- from_date = self.get_from_date(finance_book.finance_book)
- depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
- from_date, date_of_sale)
-
- if depreciation_amount > 0:
- self.append("schedules", {
- "schedule_date": date_of_sale,
- "depreciation_amount": depreciation_amount,
- "depreciation_method": finance_book.depreciation_method,
- "finance_book": finance_book.finance_book,
- "finance_book_id": finance_book.idx
- })
-
- break
-
- # For first row
- if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
- depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
- self.available_for_use_date, finance_book.depreciation_start_date)
-
- # For first depr schedule date will be the start date
- # so monthly schedule date is calculated by removing month difference between use date and start date
- monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
-
- # For last row
- elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
- if not self.flags.increase_in_asset_life:
- # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
- self.to_date = add_months(self.available_for_use_date,
- (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
-
- depreciation_amount_without_pro_rata = depreciation_amount
-
- depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
- depreciation_amount, schedule_date, self.to_date)
-
- depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
- depreciation_amount, finance_book.finance_book)
-
- monthly_schedule_date = add_months(schedule_date, 1)
- schedule_date = add_days(schedule_date, days)
- last_schedule_date = schedule_date
-
- if not depreciation_amount: continue
- value_after_depreciation -= flt(depreciation_amount,
- self.precision("gross_purchase_amount"))
-
- # Adjust depreciation amount in the last period based on the expected value after useful life
- if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
- and value_after_depreciation != finance_book.expected_value_after_useful_life)
- or value_after_depreciation < finance_book.expected_value_after_useful_life):
- depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
- skip_row = True
+ # if asset is being sold
+ if date_of_sale:
+ from_date = self.get_from_date(finance_book.finance_book)
+ depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
+ from_date, date_of_sale)
if depreciation_amount > 0:
- # With monthly depreciation, each depreciation is divided by months remaining until next date
- if self.allow_monthly_depreciation:
- # month range is 1 to 12
- # In pro rata case, for first and last depreciation, month range would be different
- month_range = months \
- if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
- else finance_book.frequency_of_depreciation
+ self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method,
+ finance_book.finance_book, finance_book.idx)
- for r in range(month_range):
- if (has_pro_rata and n == 0):
- # For first entry of monthly depr
- if r == 0:
- days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
- per_day_amt = depreciation_amount / days
- depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
- depreciation_amount -= depreciation_amount_for_current_month
- date = monthly_schedule_date
- amount = depreciation_amount_for_current_month
- else:
- date = add_months(monthly_schedule_date, r)
- amount = depreciation_amount / (month_range - 1)
- elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
- # For last entry of monthly depr
- date = last_schedule_date
- amount = depreciation_amount / month_range
+ break
+
+ # For first row
+ if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
+ from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too
+ depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
+ from_date, finance_book.depreciation_start_date)
+
+ # For first depr schedule date will be the start date
+ # so monthly schedule date is calculated by removing month difference between use date and start date
+ monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
+
+ # For last row
+ elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
+ if not self.flags.increase_in_asset_life:
+ # In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
+ self.to_date = add_months(self.available_for_use_date,
+ (n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
+
+ depreciation_amount_without_pro_rata = depreciation_amount
+
+ depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
+ depreciation_amount, schedule_date, self.to_date)
+
+ depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
+ depreciation_amount, finance_book.finance_book)
+
+ monthly_schedule_date = add_months(schedule_date, 1)
+ schedule_date = add_days(schedule_date, days)
+ last_schedule_date = schedule_date
+
+ if not depreciation_amount: continue
+ value_after_depreciation -= flt(depreciation_amount,
+ self.precision("gross_purchase_amount"))
+
+ # Adjust depreciation amount in the last period based on the expected value after useful life
+ if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
+ and value_after_depreciation != finance_book.expected_value_after_useful_life)
+ or value_after_depreciation < finance_book.expected_value_after_useful_life):
+ depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
+ skip_row = True
+
+ if depreciation_amount > 0:
+ # With monthly depreciation, each depreciation is divided by months remaining until next date
+ if self.allow_monthly_depreciation:
+ # month range is 1 to 12
+ # In pro rata case, for first and last depreciation, month range would be different
+ month_range = months \
+ if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
+ else finance_book.frequency_of_depreciation
+
+ for r in range(month_range):
+ if (has_pro_rata and n == 0):
+ # For first entry of monthly depr
+ if r == 0:
+ days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
+ per_day_amt = depreciation_amount / days
+ depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
+ depreciation_amount -= depreciation_amount_for_current_month
+ date = monthly_schedule_date
+ amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
- amount = depreciation_amount / month_range
+ amount = depreciation_amount / (month_range - 1)
+ elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
+ # For last entry of monthly depr
+ date = last_schedule_date
+ amount = depreciation_amount / month_range
+ else:
+ date = add_months(monthly_schedule_date, r)
+ amount = depreciation_amount / month_range
- self.append("schedules", {
- "schedule_date": date,
- "depreciation_amount": amount,
- "depreciation_method": finance_book.depreciation_method,
- "finance_book": finance_book.finance_book,
- "finance_book_id": finance_book.idx
- })
- else:
- self.append("schedules", {
- "schedule_date": schedule_date,
- "depreciation_amount": depreciation_amount,
- "depreciation_method": finance_book.depreciation_method,
- "finance_book": finance_book.finance_book,
- "finance_book_id": finance_book.idx
- })
+ self._add_depreciation_row(date, amount, finance_book.depreciation_method,
+ finance_book.finance_book, finance_book.idx)
+ else:
+ self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method,
+ finance_book.finance_book, finance_book.idx)
+
+ def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id):
+ self.append("schedules", {
+ "schedule_date": schedule_date,
+ "depreciation_amount": depreciation_amount,
+ "depreciation_method": depreciation_method,
+ "finance_book": finance_book,
+ "finance_book_id": finance_book_id
+ })
+
+ def _get_value_after_depreciation(self, finance_book):
+ # value_after_depreciation - current Asset value
+ if self.docstatus == 1 and finance_book.value_after_depreciation:
+ value_after_depreciation = flt(finance_book.value_after_depreciation)
+ else:
+ value_after_depreciation = (flt(self.gross_purchase_amount) -
+ flt(self.opening_accumulated_depreciation))
+
+ return value_after_depreciation
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
@@ -333,7 +349,6 @@
depr_schedule = []
for schedule in self.get('schedules'):
-
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
if len(start) == (int(schedule.finance_book_id) - 2):
start.append(num_of_depreciations_completed)
@@ -374,7 +389,9 @@
if from_date:
return from_date
- return self.available_for_use_date
+
+ # since depr for available_for_use_date is not yet booked
+ return add_days(self.available_for_use_date, -1)
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
@@ -608,7 +625,17 @@
return purchase_document
def get_fixed_asset_account(self):
- return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
+ fixed_asset_account = get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
+ if not fixed_asset_account:
+ frappe.throw(
+ _("Set {0} in asset category {1} for company {2}").format(
+ frappe.bold("Fixed Asset Account"),
+ frappe.bold(self.asset_category),
+ frappe.bold(self.company),
+ ),
+ title=_("Account not Found"),
+ )
+ return fixed_asset_account
def get_cwip_account(self, cwip_enabled=False):
cwip_account = None
@@ -897,3 +924,113 @@
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
+
+@frappe.whitelist()
+def split_asset(asset_name, split_qty):
+ asset = frappe.get_doc("Asset", asset_name)
+ split_qty = cint(split_qty)
+
+ if split_qty >= asset.asset_quantity:
+ frappe.throw(_("Split qty cannot be grater than or equal to asset qty"))
+
+ remaining_qty = asset.asset_quantity - split_qty
+
+ new_asset = create_new_asset_after_split(asset, split_qty)
+ update_existing_asset(asset, remaining_qty)
+
+ return new_asset
+
+def update_existing_asset(asset, remaining_qty):
+ remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity)
+ opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity)
+
+ frappe.db.set_value("Asset", asset.name, {
+ 'opening_accumulated_depreciation': opening_accumulated_depreciation,
+ 'gross_purchase_amount': remaining_gross_purchase_amount,
+ 'asset_quantity': remaining_qty
+ })
+
+ for finance_book in asset.get('finance_books'):
+ value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity)
+ expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity)
+ frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation)
+ frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life)
+
+ accumulated_depreciation = 0
+
+ for term in asset.get('schedules'):
+ depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity)
+ frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount)
+ accumulated_depreciation += depreciation_amount
+ frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation)
+
+def create_new_asset_after_split(asset, split_qty):
+ new_asset = frappe.copy_doc(asset)
+ new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity)
+ opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity)
+
+ new_asset.gross_purchase_amount = new_gross_purchase_amount
+ new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
+ new_asset.asset_quantity = split_qty
+ new_asset.split_from = asset.name
+ accumulated_depreciation = 0
+
+ for finance_book in new_asset.get('finance_books'):
+ finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity)
+ finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity)
+
+ for term in new_asset.get('schedules'):
+ depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.asset_quantity)
+ term.depreciation_amount = depreciation_amount
+ accumulated_depreciation += depreciation_amount
+ term.accumulated_depreciation_amount = accumulated_depreciation
+
+ new_asset.submit()
+ new_asset.set_status()
+
+ for term in new_asset.get('schedules'):
+ # Update references in JV
+ if term.journal_entry:
+ add_reference_in_jv_on_split(term.journal_entry, new_asset.name, asset.name, term.depreciation_amount)
+
+ return new_asset
+
+def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
+ journal_entry = frappe.get_doc('Journal Entry', entry_name)
+ entries_to_add = []
+ idx = len(journal_entry.get('accounts')) + 1
+
+ for account in journal_entry.get('accounts'):
+ if account.reference_name == old_asset_name:
+ entries_to_add.append(frappe.copy_doc(account).as_dict())
+ if account.credit:
+ account.credit = account.credit - depreciation_amount
+ account.credit_in_account_currency = account.credit_in_account_currency - \
+ account.exchange_rate * depreciation_amount
+ elif account.debit:
+ account.debit = account.debit - depreciation_amount
+ account.debit_in_account_currency = account.debit_in_account_currency - \
+ account.exchange_rate * depreciation_amount
+
+ for entry in entries_to_add:
+ entry.reference_name = new_asset_name
+ if entry.credit:
+ entry.credit = depreciation_amount
+ entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount
+ elif entry.debit:
+ entry.debit = depreciation_amount
+ entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount
+
+ entry.idx = idx
+ idx += 1
+
+ journal_entry.append('accounts', entry)
+
+ journal_entry.flags.ignore_validate_update_after_submit = True
+ journal_entry.save()
+
+ # Repost GL Entries
+ journal_entry.docstatus = 2
+ journal_entry.make_gl_entries(1)
+ journal_entry.docstatus = 1
+ journal_entry.make_gl_entries()
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 44c4ce5..c08dc21 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -7,7 +7,7 @@
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
-from erpnext.assets.doctype.asset.asset import make_sales_invoice
+from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
from erpnext.assets.doctype.asset.depreciation import (
post_depreciation_entries,
restore_asset,
@@ -134,6 +134,29 @@
pr.cancel()
self.assertEqual(asset.docstatus, 2)
+ def test_purchase_of_grouped_asset(self):
+ create_fixed_asset_item("Rack", is_grouped_asset=1)
+ pr = make_purchase_receipt(item_code="Rack", qty=3, rate=100000.0, location="Test Location")
+
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
+ asset = frappe.get_doc('Asset', asset_name)
+ self.assertEqual(asset.asset_quantity, 3)
+ asset.calculate_depreciation = 1
+
+ month_end_date = get_last_day(nowdate())
+ purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
+
+ asset.available_for_use_date = purchase_date
+ asset.purchase_date = purchase_date
+ asset.append("finance_books", {
+ "expected_value_after_useful_life": 10000,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 10,
+ "depreciation_start_date": month_end_date
+ })
+ asset.submit()
+
def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset = 1)
doc = frappe.new_doc('Purchase Invoice')
@@ -207,9 +230,9 @@
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_gle = (
- ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0),
+ ("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
- ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0),
+ ("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
("Debtors - _TC", 25000.0, 0.0)
)
@@ -222,6 +245,57 @@
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
+ def test_asset_splitting(self):
+ asset = create_asset(
+ calculate_depreciation = 1,
+ asset_quantity=10,
+ available_for_use_date = '2020-01-01',
+ purchase_date = '2020-01-01',
+ expected_value_after_useful_life = 0,
+ total_number_of_depreciations = 6,
+ number_of_depreciations_booked = 1,
+ frequency_of_depreciation = 10,
+ depreciation_start_date = '2021-01-01',
+ opening_accumulated_depreciation=20000,
+ gross_purchase_amount=120000,
+ submit = 1
+ )
+
+ post_depreciation_entries(date="2021-01-01")
+
+ self.assertEqual(asset.asset_quantity, 10)
+ self.assertEqual(asset.gross_purchase_amount, 120000)
+ self.assertEqual(asset.opening_accumulated_depreciation, 20000)
+
+ new_asset = split_asset(asset.name, 2)
+ asset.load_from_db()
+
+ self.assertEqual(new_asset.asset_quantity, 2)
+ self.assertEqual(new_asset.gross_purchase_amount, 24000)
+ self.assertEqual(new_asset.opening_accumulated_depreciation, 4000)
+ self.assertEqual(new_asset.split_from, asset.name)
+ self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000)
+ self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000)
+
+ self.assertEqual(asset.asset_quantity, 8)
+ self.assertEqual(asset.gross_purchase_amount, 96000)
+ self.assertEqual(asset.opening_accumulated_depreciation, 16000)
+ self.assertEqual(asset.schedules[0].depreciation_amount, 16000)
+ self.assertEqual(asset.schedules[1].depreciation_amount, 16000)
+
+ journal_entry = asset.schedules[0].journal_entry
+
+ jv = frappe.get_doc('Journal Entry', journal_entry)
+ self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
+ self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000)
+ self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000)
+ self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000)
+
+ self.assertEqual(jv.accounts[0].reference_name, asset.name)
+ self.assertEqual(jv.accounts[1].reference_name, asset.name)
+ self.assertEqual(jv.accounts[2].reference_name, new_asset.name)
+ self.assertEqual(jv.accounts[3].reference_name, new_asset.name)
+
def test_expense_head(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=2, rate=200000.0, location="Test Location")
@@ -491,10 +565,10 @@
)
expected_schedules = [
- ["2030-12-31", 27534.25, 27534.25],
- ["2031-12-31", 30000.0, 57534.25],
- ["2032-12-31", 30000.0, 87534.25],
- ["2033-01-30", 2465.75, 90000.0]
+ ['2030-12-31', 27616.44, 27616.44],
+ ['2031-12-31', 30000.0, 57616.44],
+ ['2032-12-31', 30000.0, 87616.44],
+ ['2033-01-30', 2383.56, 90000.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -544,10 +618,10 @@
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
- ["2030-12-31", 28493.15, 28493.15],
- ["2031-12-31", 35753.43, 64246.58],
- ["2032-12-31", 17876.71, 82123.29],
- ["2033-06-06", 5376.71, 87500.0]
+ ['2030-12-31', 28630.14, 28630.14],
+ ['2031-12-31', 35684.93, 64315.07],
+ ['2032-12-31', 17842.47, 82157.54],
+ ['2033-06-06', 5342.46, 87500.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -580,10 +654,10 @@
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
- ["2030-12-31", 11780.82, 11780.82],
- ["2031-12-31", 44109.59, 55890.41],
- ["2032-12-31", 22054.8, 77945.21],
- ["2033-07-12", 9554.79, 87500.0]
+ ["2030-12-31", 11849.32, 11849.32],
+ ["2031-12-31", 44075.34, 55924.66],
+ ["2032-12-31", 22037.67, 77962.33],
+ ["2033-07-12", 9537.67, 87500.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -621,7 +695,7 @@
asset = create_asset(
item_code = "Macbook Pro",
calculate_depreciation = 1,
- available_for_use_date = getdate("2019-12-31"),
+ available_for_use_date = getdate("2020-01-01"),
total_number_of_depreciations = 3,
expected_value_after_useful_life = 10000,
depreciation_start_date = getdate("2020-07-01"),
@@ -632,7 +706,7 @@
["2020-07-01", 15000, 15000],
["2021-07-01", 30000, 45000],
["2022-07-01", 30000, 75000],
- ["2022-12-31", 15000, 90000]
+ ["2023-01-01", 15000, 90000]
]
for i, schedule in enumerate(asset.schedules):
@@ -1109,6 +1183,7 @@
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
+
def test_expected_value_change(self):
"""
tests if changing `expected_value_after_useful_life`
@@ -1130,6 +1205,15 @@
asset.reload()
self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
+ def test_asset_cost_center(self):
+ asset = create_asset(is_existing_asset = 1, do_not_save=1)
+ asset.cost_center = "Main - WP"
+
+ self.assertRaises(frappe.ValidationError, asset.submit)
+
+ asset.cost_center = "Main - _TC"
+ asset.submit()
+
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
@@ -1164,7 +1248,8 @@
"available_for_use_date": args.available_for_use_date or "2020-06-06",
"location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company",
- "is_existing_asset": args.is_existing_asset or 1
+ "is_existing_asset": args.is_existing_asset or 1,
+ "asset_quantity": args.get("asset_quantity") or 1
})
if asset.calculate_depreciation:
@@ -1202,13 +1287,13 @@
})
asset_category.insert()
-def create_fixed_asset_item():
+def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
meta = frappe.get_meta('Asset')
naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-'
try:
- frappe.get_doc({
+ item = frappe.get_doc({
"doctype": "Item",
- "item_code": "Macbook Pro",
+ "item_code": item_code or "Macbook Pro",
"item_name": "Macbook Pro",
"description": "Macbook Pro Retina Display",
"asset_category": "Computers",
@@ -1216,11 +1301,14 @@
"stock_uom": "Nos",
"is_stock_item": 0,
"is_fixed_asset": 1,
- "auto_create_assets": 1,
+ "auto_create_assets": auto_create_assets,
+ "is_grouped_asset": is_grouped_asset,
"asset_naming_series": naming_series
- }).insert()
+ })
+ item.insert()
except frappe.DuplicateEntryError:
pass
+ return item
def set_depreciation_settings_in_company():
company = frappe.get_doc("Company", "_Test Company")
diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json
index 495de46..26a6609 100644
--- a/erpnext/assets/workspace/assets/assets.json
+++ b/erpnext/assets/workspace/assets/assets.json
@@ -5,7 +5,7 @@
"label": "Asset Value Analytics"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Assets\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Asset Value Analytics\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset Category\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fixed Asset Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assets\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Assets\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset Category\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fixed Asset Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:27.634865",
"docstatus": 0,
"doctype": "Workspace",
@@ -172,7 +172,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:15:54.839453",
+ "modified": "2022-01-13 17:25:41.730628",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
@@ -181,7 +181,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 4,
+ "sequence_id": 4.0,
"shortcuts": [
{
"label": "Asset",
diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json
index 380ef36..5ad93f0 100644
--- a/erpnext/buying/workspace/buying/buying.json
+++ b/erpnext/buying/workspace/buying/buying.json
@@ -5,7 +5,7 @@
"label": "Purchase Order Trends"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Buying\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Purchase Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Buying\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items & Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier Scorecard\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Regional\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
"creation": "2020-01-28 11:50:26.195467",
"docstatus": 0,
"doctype": "Workspace",
@@ -509,7 +509,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:15:56.218428",
+ "modified": "2022-01-13 17:26:39.090190",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
@@ -518,7 +518,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 6,
+ "sequence_id": 6.0,
"shortcuts": [
{
"color": "Green",
diff --git a/erpnext/commands/__init__.py b/erpnext/commands/__init__.py
index 5931119..8e12fad 100644
--- a/erpnext/commands/__init__.py
+++ b/erpnext/commands/__init__.py
@@ -1,49 +1,10 @@
-# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# GPL v3 License. See license.txt
import click
-import frappe
-from frappe.commands import get_site, pass_context
def call_command(cmd, context):
return click.Context(cmd, obj=context).forward(cmd)
-@click.command('make-demo')
-@click.option('--site', help='site name')
-@click.option('--domain', default='Manufacturing')
-@click.option('--days', default=100,
- help='Run the demo for so many days. Default 100')
-@click.option('--resume', default=False, is_flag=True,
- help='Continue running the demo for given days')
-@click.option('--reinstall', default=False, is_flag=True,
- help='Reinstall site before demo')
-@pass_context
-def make_demo(context, site, domain='Manufacturing', days=100,
- resume=False, reinstall=False):
- "Reinstall site and setup demo"
- from frappe.commands.site import _reinstall
- from frappe.installer import install_app
-
- site = get_site(context)
-
- if resume:
- with frappe.init_site(site):
- frappe.connect()
- from erpnext.demo import demo
- demo.simulate(days=days)
- else:
- if reinstall:
- _reinstall(site, yes=True)
- with frappe.init_site(site=site):
- frappe.connect()
- if not 'erpnext' in frappe.get_installed_apps():
- install_app('erpnext')
-
- # import needs site
- from erpnext.demo import demo
- demo.make(domain, days)
-
-commands = [
- make_demo
-]
+commands = []
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index eab9e12..bab16a4 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -167,9 +167,14 @@
validate_regional(self)
+ validate_einvoice_fields(self)
+
if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self)
+ def before_cancel(self):
+ validate_einvoice_fields(self)
+
def on_trash(self):
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
@@ -185,8 +190,6 @@
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
elif getdate(self.posting_date) > getdate(d.service_end_date):
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
- elif getdate(self.posting_date) > getdate(d.service_start_date):
- frappe.throw(_("Row #{0}: Service Start Date cannot be before Invoice Posting Date").format(d.idx))
def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates()
@@ -2153,3 +2156,7 @@
@erpnext.allow_regional
def validate_regional(doc):
pass
+
+@erpnext.allow_regional
+def validate_einvoice_fields(doc):
+ pass
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index a3d2502..a181af7 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -70,9 +70,18 @@
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
- self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
- doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
- fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
+ self.update_if_missing(
+ get_party_details(
+ self.supplier,
+ party_type="Supplier",
+ doctype=self.doctype,
+ company=self.company,
+ party_address=self.get("supplier_address"),
+ shipping_address=self.get('shipping_address'),
+ fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
+ ignore_permissions=self.flags.ignore_permissions
+ )
+ )
self.set_missing_item_details(for_validate)
@@ -554,10 +563,13 @@
# Check for asset naming series
if item_data.get('asset_naming_series'):
created_assets = []
-
- for qty in range(cint(d.qty)):
- asset = self.make_asset(d)
+ if item_data.get('is_grouped_asset'):
+ asset = self.make_asset(d, is_grouped_asset=True)
created_assets.append(asset)
+ else:
+ for qty in range(cint(d.qty)):
+ asset = self.make_asset(d)
+ created_assets.append(asset)
if len(created_assets) > 5:
# dont show asset form links if more than 5 assets are created
@@ -580,14 +592,18 @@
for message in messages:
frappe.msgprint(message, title="Success", indicator="green")
- def make_asset(self, row):
+ def make_asset(self, row, is_grouped_asset=False):
if not row.asset_location:
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
item_data = frappe.db.get_value('Item',
row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
- purchase_amount = flt(row.base_rate + row.item_tax_amount)
+ if is_grouped_asset:
+ purchase_amount = flt(row.base_amount + row.item_tax_amount)
+ else:
+ purchase_amount = flt(row.base_rate + row.item_tax_amount)
+
asset = frappe.get_doc({
'doctype': 'Asset',
'item_code': row.item_code,
@@ -601,6 +617,7 @@
'calculate_depreciation': 1,
'purchase_receipt_amount': purchase_amount,
'gross_purchase_amount': purchase_amount,
+ 'asset_quantity': row.qty if is_grouped_asset else 0,
'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None,
'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None
})
@@ -687,7 +704,7 @@
def get_asset_item_details(asset_items):
asset_items_data = {}
- for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
+ for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series", "is_grouped_asset"],
filters = {'name': ('in', asset_items)}):
asset_items_data.setdefault(d.name, d)
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
index b8dc92e..ae2c737 100644
--- a/erpnext/controllers/employee_boarding_controller.py
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -132,13 +132,17 @@
def on_cancel(self):
# delete task project
- for task in frappe.get_all('Task', filters={'project': self.project}):
+ project = self.project
+ for task in frappe.get_all('Task', filters={'project': project}):
frappe.delete_doc('Task', task.name, force=1)
- frappe.delete_doc('Project', self.project, force=1)
+ frappe.delete_doc('Project', project, force=1)
self.db_set('project', '')
for activity in self.activities:
activity.db_set('task', '')
+ frappe.msgprint(_('Linked Project {} and Tasks deleted.').format(
+ project), alert=True, indicator='blue')
+
@frappe.whitelist()
def get_onboarding_details(parent, parenttype):
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index cc773b7..4ff851d 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -385,7 +385,7 @@
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
- if not d.incoming_rate:
+ if not (self.get("is_return") and d.incoming_rate):
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 7073e32..2912d3e 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -17,7 +17,7 @@
from erpnext.accounts.utils import get_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.stock import get_warehouse_account_map
-from erpnext.stock.stock_ledger import get_items_to_be_repost, get_valuation_rate
+from erpnext.stock.stock_ledger import get_items_to_be_repost
class QualityInspectionRequiredError(frappe.ValidationError): pass
@@ -77,17 +77,17 @@
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
def clean_serial_nos(self):
+ from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
+
for row in self.get("items"):
if hasattr(row, "serial_no") and row.serial_no:
- # replace commas by linefeed
- row.serial_no = row.serial_no.replace(",", "\n")
+ # remove extra whitespace and store one serial no on each line
+ row.serial_no = clean_serial_no_string(row.serial_no)
- # strip preceeding and succeeding spaces for each SN
- # (SN could have valid spaces in between e.g. SN - 123 - 2021)
- serial_no_list = row.serial_no.split("\n")
- serial_no_list = [sn.strip() for sn in serial_no_list]
-
- row.serial_no = "\n".join(serial_no_list)
+ for row in self.get('packed_items') or []:
+ if hasattr(row, "serial_no") and row.serial_no:
+ # remove extra whitespace and store one serial no on each line
+ row.serial_no = clean_serial_no_string(row.serial_no)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
@@ -111,17 +111,6 @@
self.check_expense_account(item_row)
- # If the item does not have the allow zero valuation rate flag set
- # and ( valuation rate not mentioned in an incoming entry
- # or incoming entry not found while delivering the item),
- # try to pick valuation rate from previous sle or Item master and update in SLE
- # Otherwise, throw an exception
-
- if not sle.stock_value_difference and self.doctype != "Stock Reconciliation" \
- and not item_row.get("allow_zero_valuation_rate"):
-
- sle = self.update_stock_ledger_entries(sle)
-
# expense account/ target_warehouse / source_warehouse
if item_row.get('target_warehouse'):
warehouse = item_row.get('target_warehouse')
@@ -164,26 +153,6 @@
return frappe.flags.debit_field_precision
- def update_stock_ledger_entries(self, sle):
- sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
- self.doctype, self.name, currency=self.company_currency, company=self.company)
-
- sle.stock_value = flt(sle.qty_after_transaction) * flt(sle.valuation_rate)
- sle.stock_value_difference = flt(sle.actual_qty) * flt(sle.valuation_rate)
-
- if sle.name:
- frappe.db.sql("""
- update
- `tabStock Ledger Entry`
- set
- stock_value = %(stock_value)s,
- valuation_rate = %(valuation_rate)s,
- stock_value_difference = %(stock_value_difference)s
- where
- name = %(name)s""", (sle))
-
- return sle
-
def get_voucher_details(self, default_expense_account, default_cost_center, sle_map):
if self.doctype == "Stock Reconciliation":
reconciliation_purpose = frappe.db.get_value(self.doctype, self.name, "purpose")
@@ -287,11 +256,7 @@
for d in self.items:
if not d.batch_no: continue
- serial_nos = [sr.name for sr in frappe.get_all("Serial No",
- {'batch_no': d.batch_no, 'status': 'Inactive'})]
-
- if serial_nos:
- frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
+ frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None)
d.batch_no = None
d.db_set("batch_no", None)
diff --git a/erpnext/controllers/tests/test_transaction_base.py b/erpnext/controllers/tests/test_transaction_base.py
index 13aa697..f4d3f97 100644
--- a/erpnext/controllers/tests/test_transaction_base.py
+++ b/erpnext/controllers/tests/test_transaction_base.py
@@ -4,19 +4,72 @@
class TestUtils(unittest.TestCase):
- def test_reset_default_field_value(self):
- doc = frappe.get_doc({
- "doctype": "Purchase Receipt",
- "set_warehouse": "Warehouse 1",
- })
+ def test_reset_default_field_value(self):
+ doc = frappe.get_doc({
+ "doctype": "Purchase Receipt",
+ "set_warehouse": "Warehouse 1",
+ })
- # Same values
- doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
- doc.reset_default_field_value("set_warehouse", "items", "warehouse")
- self.assertEqual(doc.set_warehouse, "Warehouse 1")
+ # Same values
+ doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
+ doc.reset_default_field_value("set_warehouse", "items", "warehouse")
+ self.assertEqual(doc.set_warehouse, "Warehouse 1")
- # Mixed values
- doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
- doc.reset_default_field_value("set_warehouse", "items", "warehouse")
- self.assertEqual(doc.set_warehouse, None)
+ # Mixed values
+ doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
+ doc.reset_default_field_value("set_warehouse", "items", "warehouse")
+ self.assertEqual(doc.set_warehouse, None)
+ def test_reset_default_field_value_in_mfg_stock_entry(self):
+ # manufacture stock entry with rows having blank source/target wh
+ se = frappe.get_doc(
+ doctype="Stock Entry",
+ purpose="Manufacture",
+ stock_entry_type="Manufacture",
+ company="_Test Company",
+ from_warehouse="_Test Warehouse - _TC",
+ to_warehouse="_Test Warehouse 1 - _TC",
+ items=[
+ frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
+ frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1)
+ ]
+ )
+ se.save()
+
+ # default fields must be untouched
+ self.assertEqual(se.from_warehouse, "_Test Warehouse - _TC")
+ self.assertEqual(se.to_warehouse, "_Test Warehouse 1 - _TC")
+
+ se.delete()
+
+ def test_reset_default_field_value_in_transfer_stock_entry(self):
+ doc = frappe.get_doc({
+ "doctype": "Stock Entry",
+ "purpose": "Material Receipt",
+ "from_warehouse": "Warehouse 1",
+ "to_warehouse": "Warehouse 2",
+ })
+
+ # Same values
+ doc.items = [
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
+ ]
+
+ doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
+ doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
+ self.assertEqual(doc.from_warehouse, "Warehouse 1")
+ self.assertEqual(doc.to_warehouse, "Warehouse 2")
+
+ # Mixed values in source wh
+ doc.items = [
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
+ {"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"},
+ {"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
+ ]
+
+ doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
+ doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
+ self.assertEqual(doc.from_warehouse, None)
+ self.assertEqual(doc.to_warehouse, "Warehouse 2")
\ No newline at end of file
diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js
index 11bfa74..cac45c6 100644
--- a/erpnext/crm/doctype/campaign/campaign.js
+++ b/erpnext/crm/doctype/campaign/campaign.js
@@ -5,7 +5,7 @@
refresh: function(frm) {
erpnext.toggle_naming_series();
- if (frm.doc.__islocal) {
+ if (frm.is_new()) {
frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
} else {
cur_frm.add_custom_button(__("View Leads"), function() {
diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.py b/erpnext/crm/doctype/crm_settings/crm_settings.py
index bde5254..98cf7d8 100644
--- a/erpnext/crm/doctype/crm_settings/crm_settings.py
+++ b/erpnext/crm/doctype/crm_settings/crm_settings.py
@@ -1,9 +1,10 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-# import frappe
+import frappe
from frappe.model.document import Document
class CRMSettings(Document):
- pass
+ def validate(self):
+ frappe.db.set_default("campaign_naming_by", self.get("campaign_naming_by", ""))
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index 8fd4978..d2ac10a 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -2,13 +2,14 @@
# For license information, please see license.txt
+from urllib.parse import urlencode
+
import frappe
import requests
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_url_to_form
from frappe.utils.file_manager import get_file_path
-from six.moves.urllib.parse import urlencode
class LinkedInSettings(Document):
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index f8376e6..8e7d67e 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -24,6 +24,14 @@
frm.trigger('set_contact_link');
}
},
+
+ validate: function(frm) {
+ if (frm.doc.status == "Lost" && !frm.doc.lost_reasons.length) {
+ frm.trigger('set_as_lost_dialog');
+ frappe.throw(__("Lost Reasons are required in case opportunity is Lost."));
+ }
+ },
+
contact_date: function(frm) {
if(frm.doc.contact_date < frappe.datetime.now_datetime()){
frm.set_value("contact_date", "");
@@ -82,7 +90,7 @@
frm.trigger('setup_opportunity_from');
erpnext.toggle_naming_series();
- if(!doc.__islocal && doc.status!=="Lost") {
+ if(!frm.is_new() && doc.status!=="Lost") {
if(doc.with_items){
frm.add_custom_button(__('Supplier Quotation'),
function() {
@@ -187,11 +195,11 @@
change_form_labels: function(frm) {
let company_currency = erpnext.get_currency(frm.doc.company);
- frm.set_currency_labels(["base_opportunity_amount", "base_total", "base_grand_total"], company_currency);
- frm.set_currency_labels(["opportunity_amount", "total", "grand_total"], frm.doc.currency);
+ frm.set_currency_labels(["base_opportunity_amount", "base_total"], company_currency);
+ frm.set_currency_labels(["opportunity_amount", "total"], frm.doc.currency);
// toggle fields
- frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total", "base_grand_total"],
+ frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total"],
frm.doc.currency != company_currency);
},
@@ -209,20 +217,15 @@
},
calculate_total: function(frm) {
- let total = 0, base_total = 0, grand_total = 0, base_grand_total = 0;
+ let total = 0, base_total = 0;
frm.doc.items.forEach(item => {
total += item.amount;
base_total += item.base_amount;
})
- base_grand_total = base_total + frm.doc.base_opportunity_amount;
- grand_total = total + frm.doc.opportunity_amount;
-
frm.set_value({
'total': flt(total),
- 'base_total': flt(base_total),
- 'grand_total': flt(grand_total),
- 'base_grand_total': flt(base_grand_total)
+ 'base_total': flt(base_total)
});
}
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index dc32d9a..089f2d2 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -42,10 +42,8 @@
"items",
"section_break_32",
"base_total",
- "base_grand_total",
"column_break_33",
"total",
- "grand_total",
"contact_info",
"customer_address",
"address_display",
@@ -476,21 +474,6 @@
"fieldtype": "Column Break"
},
{
- "fieldname": "base_grand_total",
- "fieldtype": "Currency",
- "label": "Grand Total (Company Currency)",
- "options": "Company:company:default_currency",
- "print_hide": 1,
- "read_only": 1
- },
- {
- "fieldname": "grand_total",
- "fieldtype": "Currency",
- "label": "Grand Total",
- "options": "currency",
- "read_only": 1
- },
- {
"fieldname": "lost_detail_section",
"fieldtype": "Section Break",
"label": "Lost Reasons"
@@ -510,7 +493,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
- "modified": "2021-10-21 12:04:30.151379",
+ "modified": "2022-01-29 19:32:26.382896",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
@@ -547,6 +530,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"subject_field": "title",
"timeline_field": "party_name",
"title_field": "title",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index a4fd765..2d53874 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -69,8 +69,6 @@
self.total = flt(total)
self.base_total = flt(base_total)
- self.grand_total = flt(self.total) + flt(self.opportunity_amount)
- self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount)
def make_new_lead_if_required(self):
"""Set lead against new opportunity"""
diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js
index 67018e1..8721a5b 100644
--- a/erpnext/crm/doctype/prospect/prospect.js
+++ b/erpnext/crm/doctype/prospect/prospect.js
@@ -3,6 +3,8 @@
frappe.ui.form.on('Prospect', {
refresh (frm) {
+ frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
+
if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
frm.add_custom_button(__("Customer"), function() {
frappe.model.open_mapped_doc({
diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json
index 8315218..0faad1d 100644
--- a/erpnext/crm/module_onboarding/crm/crm.json
+++ b/erpnext/crm/module_onboarding/crm/crm.json
@@ -16,7 +16,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
"idx": 0,
"is_complete": 0,
- "modified": "2020-07-08 14:05:42.644448",
+ "modified": "2022-01-29 20:14:29.502145",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@@ -33,6 +33,9 @@
},
{
"step": "Create and Send Quotation"
+ },
+ {
+ "step": "CRM Settings"
}
],
"subtitle": "Lead, Opportunity, Customer, and more.",
diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
index 78f7e4d..f0f50de 100644
--- a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
+++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
@@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-28 21:07:11.461172",
@@ -13,6 +12,7 @@
"name": "Create and Send Quotation",
"owner": "Administrator",
"reference_document": "Quotation",
+ "show_form_tour": 0,
"show_full_form": 1,
"title": "Create and Send Quotation",
"validate_action": 1
diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json
index c45e8b0..cb5cce6 100644
--- a/erpnext/crm/onboarding_step/create_lead/create_lead.json
+++ b/erpnext/crm/onboarding_step/create_lead/create_lead.json
@@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-28 21:07:01.373403",
@@ -13,6 +12,7 @@
"name": "Create Lead",
"owner": "Administrator",
"reference_document": "Lead",
+ "show_form_tour": 0,
"show_full_form": 1,
"title": "Create Lead",
"validate_action": 1
diff --git a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
index 0ee9317..96e0256 100644
--- a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
+++ b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
@@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-01-21 15:28:52.483839",
@@ -13,6 +12,7 @@
"name": "Create Opportunity",
"owner": "Administrator",
"reference_document": "Opportunity",
+ "show_form_tour": 0,
"show_full_form": 1,
"title": "Create Opportunity",
"validate_action": 1
diff --git a/erpnext/crm/onboarding_step/crm_settings/crm_settings.json b/erpnext/crm/onboarding_step/crm_settings/crm_settings.json
new file mode 100644
index 0000000..555d795
--- /dev/null
+++ b/erpnext/crm/onboarding_step/crm_settings/crm_settings.json
@@ -0,0 +1,21 @@
+{
+ "action": "Go to Page",
+ "creation": "2022-01-29 20:14:24.803844",
+ "description": "# CRM Settings\n\nCRM module\u2019s features are configurable as per your business needs. CRM Settings is the place where you can set your preferences for:\n- Campaign\n- Lead\n- Opportunity\n- Quotation",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2022-01-29 20:14:24.803844",
+ "modified_by": "Administrator",
+ "name": "CRM Settings",
+ "owner": "Administrator",
+ "path": "#crm-settings/CRM%20Settings",
+ "reference_document": "CRM Settings",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "CRM Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
index fa26921a..8871753 100644
--- a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
+++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
@@ -5,13 +5,13 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:28:16.448676",
"modified_by": "Administrator",
"name": "Introduction to CRM",
"owner": "Administrator",
+ "show_form_tour": 0,
"show_full_form": 0,
"title": "Introduction to CRM",
"validate_action": 1,
diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json
index 5a63dc1..83341f5 100644
--- a/erpnext/crm/workspace/crm/crm.json
+++ b/erpnext/crm/workspace/crm/crm.json
@@ -1,10 +1,11 @@
{
"charts": [
{
- "chart_name": "Territory Wise Sales"
+ "chart_name": "Territory Wise Sales",
+ "label": "Territory Wise Sales"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"CRM\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Lead\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Opportunity\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Sales Pipeline\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Campaign\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"CRM\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Territory Wise Sales\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Opportunity\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Sales Pipeline\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Campaign\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-01-23 14:48:30.183272",
"docstatus": 0,
"doctype": "Workspace",
@@ -144,6 +145,7 @@
"hidden": 0,
"is_query_report": 1,
"label": "Sales Pipeline Analytics",
+ "link_count": 0,
"link_to": "Sales Pipeline Analytics",
"link_type": "Report",
"onboard": 0,
@@ -153,6 +155,7 @@
"hidden": 0,
"is_query_report": 1,
"label": "Opportunity Summary by Sales Stage",
+ "link_count": 0,
"link_to": "Opportunity Summary by Sales Stage",
"link_type": "Report",
"onboard": 0,
@@ -414,7 +417,7 @@
"type": "Link"
}
],
- "modified": "2021-08-20 12:15:56.913092",
+ "modified": "2022-01-13 17:53:17.509844",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@@ -423,7 +426,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 7,
+ "sequence_id": 7.0,
"shortcuts": [
{
"color": "Blue",
diff --git a/erpnext/demo/data/account.json b/erpnext/demo/data/account.json
deleted file mode 100644
index b50b0c9..0000000
--- a/erpnext/demo/data/account.json
+++ /dev/null
@@ -1,18 +0,0 @@
-[{
- "account_name": "Debtors EUR",
- "parent_account": "Accounts Receivable",
- "account_type": "Receivable",
- "account_currency": "EUR"
-},
-{
- "account_name": "Creditors EUR",
- "parent_account": "Accounts Payable",
- "account_type": "Payable",
- "account_currency": "EUR"
-},
-{
- "account_name": "Paypal",
- "parent_account": "Bank Accounts",
- "account_type": "Bank",
- "account_currency": "EUR"
-}]
\ No newline at end of file
diff --git a/erpnext/demo/data/address.json b/erpnext/demo/data/address.json
deleted file mode 100644
index 7618c2c..0000000
--- a/erpnext/demo/data/address.json
+++ /dev/null
@@ -1,218 +0,0 @@
-[
- {
- "address_line1": "254 Theotokopoulou Str.",
- "address_type": "Office",
- "city": "Larnaka",
- "country": "Cyprus",
- "links": [{"link_doctype": "Customer", "link_name": "Adaptas"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "R Patr\u00e3o Caramelho 116",
- "address_type": "Office",
- "city": "Fajozes",
- "country": "Portugal",
- "links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "30 Fulford Road",
- "address_type": "Office",
- "city": "PENTRE-PIOD",
- "country": "United Kingdom",
- "links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Schoenebergerstrasse 13",
- "address_type": "Office",
- "city": "Raschau",
- "country": "Germany",
- "links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Hoheluftchaussee 43",
- "address_type": "Office",
- "city": "Kieritzsch",
- "country": "Germany",
- "links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "R Cimo Vila 6",
- "address_type": "Office",
- "city": "Rebordosa",
- "country": "Portugal",
- "links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "R 5 Outubro 9",
- "address_type": "Office",
- "city": "Quinta Nova S\u00e3o Domingos",
- "country": "Portugal",
- "links": [{"link_doctype": "Customer", "link_name": "Choices"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Avenida Macambira 953",
- "address_type": "Office",
- "city": "Goi\u00e2nia",
- "country": "Brazil",
- "links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "2342 Goyeau Ave",
- "address_type": "Office",
- "city": "Windsor",
- "country": "Canada",
- "links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Laukaantie 82",
- "address_type": "Office",
- "city": "KOKKOLA",
- "country": "Finland",
- "links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "9 Brown Street",
- "address_type": "Office",
- "city": "PETERSHAM",
- "country": "Australia",
- "links": [{"link_doctype": "Customer", "link_name": "Fayva"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Via Donnalbina 41",
- "address_type": "Office",
- "city": "Cala Gonone",
- "country": "Italy",
- "links": [{"link_doctype": "Customer", "link_name": "Intelacard"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Liljerum Grenadj\u00e4rtorpet 69",
- "address_type": "Office",
- "city": "TOMTEBODA",
- "country": "Sweden",
- "links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "72 Bishopgate Street",
- "address_type": "Office",
- "city": "SEAHAM",
- "country": "United Kingdom",
- "links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "\u03a3\u03ba\u03b1\u03c6\u03af\u03b4\u03b9\u03b1 105",
- "address_type": "Office",
- "city": "\u03a0\u0391\u03a1\u0395\u039a\u039a\u039b\u0397\u03a3\u0399\u0391",
- "country": "Cyprus",
- "links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Mellemvej 7",
- "address_type": "Office",
- "city": "Aabybro",
- "country": "Denmark",
- "links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "Plougg\u00e5rdsvej 98",
- "address_type": "Office",
- "city": "Karby",
- "country": "Denmark",
- "links": [{"link_doctype": "Customer", "link_name": "Netobill"}],
- "phone": "23566775757"
- },
- {
- "address_line1": "176 Michalakopoulou Street",
- "address_type": "Office",
- "city": "Agio Georgoudi",
- "country": "Cyprus",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}]
- },
- {
- "address_line1": "Fibichova 1102",
- "address_type": "Office",
- "city": "Kokor\u00edn",
- "country": "Czech Republic",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}]
- },
- {
- "address_line1": "Zahradn\u00ed 888",
- "address_type": "Office",
- "city": "Cecht\u00edn",
- "country": "Czech Republic",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}]
- },
- {
- "address_line1": "ul. Grochowska 94",
- "address_type": "Office",
- "city": "Warszawa",
- "country": "Poland",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}]
- },
- {
- "address_line1": "Norra Esplanaden 87",
- "address_type": "Office",
- "city": "HELSINKI",
- "country": "Finland",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}]
- },
- {
- "address_line1": "2038 Fallon Drive",
- "address_type": "Office",
- "city": "Dresden",
- "country": "Canada",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}]
- },
- {
- "address_line1": "77 cours Franklin Roosevelt",
- "address_type": "Office",
- "city": "MARSEILLE",
- "country": "France",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}]
- },
- {
- "address_line1": "ul. Tuwima Juliana 85",
- "address_type": "Office",
- "city": "\u0141\u00f3d\u017a",
- "country": "Poland",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}]
- },
- {
- "address_line1": "Gl. Sygehusvej 41",
- "address_type": "Office",
- "city": "Narsaq",
- "country": "Greenland",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}]
- },
- {
- "address_line1": "Gosposka ulica 50",
- "address_type": "Office",
- "city": "Nova Gorica",
- "country": "Slovenia",
- "phone": "23566775757",
- "links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/assessment_criteria.json b/erpnext/demo/data/assessment_criteria.json
deleted file mode 100644
index 8295682..0000000
--- a/erpnext/demo/data/assessment_criteria.json
+++ /dev/null
@@ -1,18 +0,0 @@
-[
- {
- "doctype": "Assessment Criteria",
- "assessment_criteria": "Aptitude"
- },
- {
- "doctype": "Assessment Criteria",
- "assessment_criteria": "Application"
- },
- {
- "doctype": "Assessment Criteria",
- "assessment_criteria": "Understanding"
- },
- {
- "doctype": "Assessment Criteria",
- "assessment_criteria": "Knowledge"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/asset.json b/erpnext/demo/data/asset.json
deleted file mode 100644
index 44db2ae..0000000
--- a/erpnext/demo/data/asset.json
+++ /dev/null
@@ -1,58 +0,0 @@
-[
- {
- "asset_name": "Macbook Pro - 1",
- "item_code": "Computer",
- "gross_purchase_amount": 100000,
- "asset_owner": "Company",
- "available_for_use_date": "2017-01-02",
- "location": "Main Location"
- },
- {
- "asset_name": "Macbook Air - 1",
- "item_code": "Computer",
- "gross_purchase_amount": 60000,
- "asset_owner": "Company",
- "available_for_use_date": "2017-10-02",
- "location": "Avg Location"
- },
- {
- "asset_name": "Conferrence Table",
- "item_code": "Table",
- "gross_purchase_amount": 30000,
- "asset_owner": "Company",
- "available_for_use_date": "2018-10-02",
- "location": "Zany Location"
- },
- {
- "asset_name": "Lunch Table",
- "item_code": "Table",
- "gross_purchase_amount": 20000,
- "asset_owner": "Company",
- "available_for_use_date": "2018-06-02",
- "location": "Fletcher Location"
- },
- {
- "asset_name": "ERPNext",
- "item_code": "ERP",
- "gross_purchase_amount": 100000,
- "asset_owner": "Company",
- "available_for_use_date": "2018-09-02",
- "location":"Main Location"
- },
- {
- "asset_name": "Chair 1",
- "item_code": "Chair",
- "gross_purchase_amount": 10000,
- "asset_owner": "Company",
- "available_for_use_date": "2018-07-02",
- "location": "Zany Location"
- },
- {
- "asset_name": "Chair 2",
- "item_code": "Chair",
- "gross_purchase_amount": 10000,
- "asset_owner": "Company",
- "available_for_use_date": "2018-07-02",
- "location": "Avg Location"
- }
-]
diff --git a/erpnext/demo/data/asset_category.json b/erpnext/demo/data/asset_category.json
deleted file mode 100644
index 54f779d..0000000
--- a/erpnext/demo/data/asset_category.json
+++ /dev/null
@@ -1,38 +0,0 @@
-[
- {
- "asset_category_name": "Furnitures",
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 5,
- "frequency_of_depreciation": 12,
- "accounts": [{
- "company_name": "Wind Power LLC",
- "fixed_asset_account": "Furnitures and Fixtures - WPL",
- "accumulated_depreciation_account": "Accumulated Depreciation - WPL",
- "depreciation_expense_account": "Depreciation - WPL"
- }]
- },
- {
- "asset_category_name": "Electronic Equipments",
- "depreciation_method": "Double Declining Balance",
- "total_number_of_depreciations": 10,
- "frequency_of_depreciation": 6,
- "accounts": [{
- "company_name": "Wind Power LLC",
- "fixed_asset_account": "Electronic Equipments - WPL",
- "accumulated_depreciation_account": "Accumulated Depreciation - WPL",
- "depreciation_expense_account": "Depreciation - WPL"
- }]
- },
- {
- "asset_category_name": "Softwares",
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 10,
- "frequency_of_depreciation": 12,
- "accounts": [{
- "company_name": "Wind Power LLC",
- "fixed_asset_account": "Softwares - WPL",
- "accumulated_depreciation_account": "Accumulated Depreciation - WPL",
- "depreciation_expense_account": "Depreciation - WPL"
- }]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/bom.json b/erpnext/demo/data/bom.json
deleted file mode 100644
index 3085435..0000000
--- a/erpnext/demo/data/bom.json
+++ /dev/null
@@ -1,180 +0,0 @@
-[
- {
- "item": "Bearing Assembly",
- "items": [
- {
- "item_code": "Base Bearing Plate",
- "qty": 1.0,
- "rate": 15.0
- },
- {
- "item_code": "Bearing Block",
- "qty": 1.0,
- "rate": 10.0
- },
- {
- "item_code": "Bearing Collar",
- "qty": 2.0,
- "rate": 20.0
- },
- {
- "item_code": "Bearing Pipe",
- "qty": 1.0,
- "rate": 15.0
- },
- {
- "item_code": "Upper Bearing Plate",
- "qty": 1.0,
- "rate": 50.0
- }
- ]
- },
- {
- "item": "Wind Mill A Series",
- "items": [
- {
- "item_code": "Base Bearing Plate",
- "qty": 1.0,
- "rate": 15.0
- },
- {
- "item_code": "Base Plate",
- "qty": 1.0,
- "rate": 20.0
- },
- {
- "item_code": "Bearing Block",
- "qty": 1.0,
- "rate": 10.0
- },
- {
- "item_code": "Bearing Pipe",
- "qty": 1.0,
- "rate": 15.0
- },
- {
- "item_code": "External Disc",
- "qty": 1.0,
- "rate": 45.0
- },
- {
- "item_code": "Shaft",
- "qty": 1.0,
- "rate": 30.0
- },
- {
- "item_code": "Wing Sheet",
- "qty": 4.0,
- "rate": 22.0
- }
- ]
- },
- {
- "item": "Wind MIll C Series",
- "items": [
- {
- "item_code": "Base Plate",
- "qty": 2.0,
- "rate": 20.0
- },
- {
- "item_code": "Internal Disc",
- "qty": 1.0,
- "rate": 33.0
- },
- {
- "item_code": "External Disc",
- "qty": 1.0,
- "rate": 45.0
- },
- {
- "item_code": "Bearing Assembly",
- "qty": 1.0,
- "rate": 130.0
- },
- {
- "item_code": "Wing Sheet",
- "qty": 3.0,
- "rate": 22.0
- }
- ]
- },
- {
- "item": "Wind Turbine-S",
- "with_operations": 1,
- "operations": [
- {
- "operation": "Prepare Frame",
- "time_in_mins": 30.0,
- "workstation": "Drilling Machine 1"
- },
- {
- "operation": "Setup Fixtures",
- "time_in_mins": 15.0,
- "workstation": "Assembly Station 1"
- },
- {
- "operation": "Assembly Operation",
- "time_in_mins": 30.0,
- "workstation": "Assembly Station 1"
- },
- {
- "operation": "Wiring",
- "time_in_mins": 20.0,
- "workstation": "Assembly Station 1"
- },
- {
- "operation": "Testing",
- "time_in_mins": 10.0,
- "workstation": "Packing and Testing Station"
- },
- {
- "operation": "Packing",
- "time_in_mins": 25.0,
- "workstation": "Packing and Testing Station"
- }
- ],
- "items": [
- {
- "item_code": "Base Bearing Plate",
- "qty": 1.0,
- "rate": 15.0
- },
- {
- "item_code": "Base Plate",
- "qty": 1.0,
- "rate": 20.0
- },
- {
- "item_code": "Bearing Collar",
- "qty": 1.0,
- "rate": 20.0
- },
- {
- "item_code": "Blade Rib",
- "qty": 1.0,
- "rate": 10.0
- },
- {
- "item_code": "Shaft",
- "qty": 1.0,
- "rate": 30.0
- },
- {
- "item_code": "Wing Sheet",
- "qty": 2.0,
- "rate": 22.0
- }
- ]
- },
- {
- "item": "Base Plate",
- "items": [
- {
- "item_code": "Base Plate Un Painted",
- "qty": 1.0,
- "rate": 16.0
- }
- ]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/contact.json b/erpnext/demo/data/contact.json
deleted file mode 100644
index 113b561..0000000
--- a/erpnext/demo/data/contact.json
+++ /dev/null
@@ -1,164 +0,0 @@
-[
- {
- "email_id": "JanVaclavik@example.com",
- "first_name": "January",
- "last_name": "V\u00e1clav\u00edk",
- "links": [{"link_doctype": "Customer", "link_name": "Adaptas"}]
- },
- {
- "email_id": "ChidumagaTobeolisa@example.com",
- "first_name": "Chidumaga",
- "last_name": "Tobeolisa",
- "links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}]
- },
- {
- "email_id": "JanaKubanova@example.com",
- "first_name": "Jana",
- "last_name": "Kub\u00e1\u0148ov\u00e1",
- "links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}]
- },
- {
- "email_id": "XuChaoXuan@example.com",
- "first_name": "\u7d39\u8431",
- "last_name": "\u4e8e",
- "links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}]
- },
- {
- "email_id": "OzlemVerwijmeren@example.com",
- "first_name": "\u00d6zlem",
- "last_name": "Verwijmeren",
- "links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}]
- },
- {
- "email_id": "HansRasmussen@example.com",
- "first_name": "Hans",
- "last_name": "Rasmussen",
- "links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}]
- },
- {
- "email_id": "SatomiShigeki@example.com",
- "first_name": "Satomi",
- "last_name": "Shigeki",
- "links": [{"link_doctype": "Customer", "link_name": "Choices"}]
- },
- {
- "email_id": "SimonVJessen@example.com",
- "first_name": "Simon",
- "last_name": "Jessen",
- "links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}]
- },
- {
- "email_id": "NeguaranShahsaah@example.com",
- "first_name": "\u0646\u06af\u0627\u0631\u06cc\u0646",
- "last_name": "\u0634\u0627\u0647 \u0633\u06cc\u0627\u0647",
- "links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}]
- },
- {
- "email_id": "Lom-AliBataev@example.com",
- "first_name": "Lom-Ali",
- "last_name": "Bataev",
- "links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}]
- },
- {
- "email_id": "VanNgocTien@example.com",
- "first_name": "Ti\u00ean",
- "last_name": "V\u0103n",
- "links": [{"link_doctype": "Customer", "link_name": "Fayva"}]
- },
- {
- "email_id": "QuimeyOsorioRuelas@example.com",
- "first_name": "Quimey",
- "last_name": "Osorio",
- "links": [{"link_doctype": "Customer", "link_name": "Intelacard"}]
- },
- {
- "email_id": "EdgardaSalcedoRaya@example.com",
- "first_name": "Edgarda",
- "last_name": "Salcedo",
- "links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}]
- },
- {
- "email_id": "HafsteinnBjarnarsonar@example.com",
- "first_name": "Hafsteinn",
- "last_name": "Bjarnarsonar",
- "links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}]
- },
- {
- "email_id": "\u0434\u0430\u043d\u0438\u0438\u043b@example.com",
- "first_name": "\u0414\u0430\u043d\u0438\u0438\u043b",
- "last_name": "\u041a\u043e\u043d\u043e\u0432\u0430\u043b\u043e\u0432",
- "links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}]
- },
- {
- "email_id": "SelmaMAndersen@example.com",
- "first_name": "Selma",
- "last_name": "Andersen",
- "links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}]
- },
- {
- "email_id": "LadislavKolaja@example.com",
- "first_name": "Ladislav",
- "last_name": "Kolaja",
- "links": [{"link_doctype": "Customer", "link_name": "Netobill"}]
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}],
- "email_id": "TewoldeAbaalom@example.com",
- "first_name": "Tewolde",
- "last_name": "Abaalom"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}],
- "email_id": "LeilaFernandesRodrigues@example.com",
- "first_name": "Leila",
- "last_name": "Rodrigues"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}],
- "email_id": "DmitryBulgakov@example.com",
- "first_name": "Dmitry",
- "last_name": "Bulgakov"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}],
- "email_id": "HaiducWhitfoot@example.com",
- "first_name": "Haiduc",
- "last_name": "Whitfoot"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}],
- "email_id": "SesseljaPetursdottir@example.com",
- "first_name": "Sesselja",
- "last_name": "P\u00e9tursd\u00f3ttir"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}],
- "email_id": "HajdarPignar@example.com",
- "first_name": "Hajdar",
- "last_name": "Pignar"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}],
- "email_id": "GustavaLorenzo@example.com",
- "first_name": "Gustava",
- "last_name": "Lorenzo"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}],
- "email_id": "BethanyWood@example.com",
- "first_name": "Bethany",
- "last_name": "Wood"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}],
- "email_id": "GlorianaBrownlock@example.com",
- "first_name": "Gloriana",
- "last_name": "Brownlock"
- },
- {
- "links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}],
- "email_id": "JensonFraser@gustr.com",
- "first_name": "Jenson",
- "last_name": "Fraser"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/course.json b/erpnext/demo/data/course.json
deleted file mode 100644
index 15728d5..0000000
--- a/erpnext/demo/data/course.json
+++ /dev/null
@@ -1,134 +0,0 @@
-[
- {
- "doctype": "Course",
- "course_name": "Communication Skiils",
- "course_code": "BCA2040",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Object Oriented Programing - C++",
- "course_code": "BCA2030",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Data Structures and Algorithm",
- "course_code": "BCA2020",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Operating System",
- "course_code": "BCA2010",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Digital Logic",
- "course_code": "BCA1040",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Basic Mathematics",
- "course_code": "BCA1030",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Programing in C",
- "course_code": "BCA1020",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Fundamentals of IT & Programing",
- "course_code": "BCA1010",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Microprocessor",
- "course_code": "MCA4010",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Probability and Statistics",
- "course_code": "MCA4020",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Programing in Java",
- "course_code": "MCA4030",
- "department": "Information Technology"
- },
- {
- "doctype": "Course",
- "course_name": "Communication Skills",
- "course_code": "BBA 101",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Organizational Behavior",
- "course_code": "BBA 102",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Business Environment",
- "course_code": "BBA 103",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Legal and Regulatory Framework",
- "course_code": "BBA 301",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Human Resource Management",
- "course_code": "BBA 302",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Advertising and Sales",
- "course_code": "BBA 304",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Entrepreneurship Management",
- "course_code": "BBA 505",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Visual Merchandising",
- "course_code": "BBR 504",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Warehouse Management",
- "course_code": "BBR 505",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Store Operations and Job Knowledge",
- "course_code": "BBR 501",
- "department": "Management Studies"
- },
- {
- "doctype": "Course",
- "course_name": "Management Development and Skills",
- "course_code": "BBA 602",
- "department": "Management Studies"
- }
-]
diff --git a/erpnext/demo/data/department.json b/erpnext/demo/data/department.json
deleted file mode 100644
index f4355ba..0000000
--- a/erpnext/demo/data/department.json
+++ /dev/null
@@ -1,30 +0,0 @@
-[
- {
- "doctype": "Department",
- "department_name": "Information Technology"
- },
- {
- "doctype": "Department",
- "department_name": "Physics"
- },
- {
- "doctype": "Department",
- "department_name": "Chemistry"
- },
- {
- "doctype": "Department",
- "department_name": "Biology"
- },
- {
- "doctype": "Department",
- "department_name": "Commerce"
- },
- {
- "doctype": "Department",
- "department_name": "English"
- },
- {
- "doctype": "Department",
- "department_name": "Management Studies"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json
deleted file mode 100644
index 2ecd14f..0000000
--- a/erpnext/demo/data/drug_list.json
+++ /dev/null
@@ -1,5048 +0,0 @@
-[
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Atocopherol",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Atocopherol",
- "item_group": "Drug",
- "item_name": "Atocopherol",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
-
-
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.577151",
- "name": "Atocopherol",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Abacavir",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Abacavir",
- "item_group": "Drug",
- "item_name": "Abacavir",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
-
-
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.678257",
- "name": "Abacavir",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Abciximab",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Abciximab",
- "item_group": "Drug",
- "item_name": "Abciximab",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.695413",
- "name": "Abciximab",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Acacia",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Acacia",
- "item_group": "Drug",
- "item_name": "Acacia",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.797774",
- "name": "Acacia",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Acamprosate",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Acamprosate",
- "item_group": "Drug",
- "item_name": "Acamprosate",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.826952",
- "name": "Acamprosate",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Acarbose",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Acarbose",
- "item_group": "Drug",
- "item_name": "Acarbose",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.843890",
- "name": "Acarbose",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Acebrofylline",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Acebrofylline",
- "item_group": "Drug",
- "item_name": "Acebrofylline",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.969984",
- "name": "Acebrofylline",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Acebrofylline (SR)",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Acebrofylline (SR)",
- "item_group": "Drug",
- "item_name": "Acebrofylline (SR)",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:16.987354",
- "name": "Acebrofylline (SR)",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Aceclofenac",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Aceclofenac",
- "item_group": "Drug",
- "item_name": "Aceclofenac",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.004369",
- "name": "Aceclofenac",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Ash",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Ash",
- "item_group": "Drug",
- "item_name": "Ash",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.021192",
- "name": "Ash",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Asparaginase",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Asparaginase",
- "item_group": "Drug",
- "item_name": "Asparaginase",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.038058",
- "name": "Asparaginase",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Aspartame",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Aspartame",
- "item_group": "Drug",
- "item_name": "Aspartame",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.054463",
- "name": "Aspartame",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Aspartic Acid",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Aspartic Acid",
- "item_group": "Drug",
- "item_name": "Aspartic Acid",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.071001",
- "name": "Aspartic Acid",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Bleomycin",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Bleomycin",
- "item_group": "Drug",
- "item_name": "Bleomycin",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.087170",
- "name": "Bleomycin",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Bleomycin Sulphate",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Bleomycin Sulphate",
- "item_group": "Drug",
- "item_name": "Bleomycin Sulphate",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.103691",
- "name": "Bleomycin Sulphate",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Blue cap contains",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Blue cap contains",
- "item_group": "Drug",
- "item_name": "Blue cap contains",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.120040",
- "name": "Blue cap contains",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Boran",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Boran",
- "item_group": "Drug",
- "item_name": "Boran",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.135964",
- "name": "Boran",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Borax",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Borax",
- "item_group": "Drug",
- "item_name": "Borax",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.152575",
- "name": "Borax",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorbutanol",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorbutanol",
- "item_group": "Drug",
- "item_name": "Chlorbutanol",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.168998",
- "name": "Chlorbutanol",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorbutol",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorbutol",
- "item_group": "Drug",
- "item_name": "Chlorbutol",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.185316",
- "name": "Chlorbutol",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlordiazepoxide",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlordiazepoxide",
- "item_group": "Drug",
- "item_name": "Chlordiazepoxide",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.208361",
- "name": "Chlordiazepoxide",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlordiazepoxide and Clidinium Bromide",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlordiazepoxide and Clidinium Bromide",
- "item_group": "Drug",
- "item_name": "Chlordiazepoxide and Clidinium Bromide",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.224341",
- "name": "Chlordiazepoxide and Clidinium Bromide",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorhexidine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorhexidine",
- "item_group": "Drug",
- "item_name": "Chlorhexidine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.240634",
- "name": "Chlorhexidine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorhexidine 40%",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorhexidine 40%",
- "item_group": "Drug",
- "item_name": "Chlorhexidine 40%",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.256922",
- "name": "Chlorhexidine 40%",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorhexidine Acetate",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorhexidine Acetate",
- "item_group": "Drug",
- "item_name": "Chlorhexidine Acetate",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.274789",
- "name": "Chlorhexidine Acetate",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorhexidine Gluconate",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorhexidine Gluconate",
- "item_group": "Drug",
- "item_name": "Chlorhexidine Gluconate",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.295371",
- "name": "Chlorhexidine Gluconate",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorhexidine HCL",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorhexidine HCL",
- "item_group": "Drug",
- "item_name": "Chlorhexidine HCL",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.312916",
- "name": "Chlorhexidine HCL",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chlorhexidine Hydrochloride",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chlorhexidine Hydrochloride",
- "item_group": "Drug",
- "item_name": "Chlorhexidine Hydrochloride",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.329570",
- "name": "Chlorhexidine Hydrochloride",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Chloride",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Chloride",
- "item_group": "Drug",
- "item_name": "Chloride",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.346088",
- "name": "Chloride",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Fosfomycin Tromethamine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Fosfomycin Tromethamine",
- "item_group": "Drug",
- "item_name": "Fosfomycin Tromethamine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.362777",
- "name": "Fosfomycin Tromethamine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Fosinopril",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Fosinopril",
- "item_group": "Drug",
- "item_name": "Fosinopril",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.379465",
- "name": "Fosinopril",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Iodochlorhydroxyquinoline",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Iodochlorhydroxyquinoline",
- "item_group": "Drug",
- "item_name": "Iodochlorhydroxyquinoline",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.396068",
- "name": "Iodochlorhydroxyquinoline",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Iodochlorohydroxyquinoline",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Iodochlorohydroxyquinoline",
- "item_group": "Drug",
- "item_name": "Iodochlorohydroxyquinoline",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.412734",
- "name": "Iodochlorohydroxyquinoline",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Ipratropium",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Ipratropium",
- "item_group": "Drug",
- "item_name": "Ipratropium",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.429333",
- "name": "Ipratropium",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Mebeverine hydrochloride",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Mebeverine hydrochloride",
- "item_group": "Drug",
- "item_name": "Mebeverine hydrochloride",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.445814",
- "name": "Mebeverine hydrochloride",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Mecetronium ethylsulphate",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Mecetronium ethylsulphate",
- "item_group": "Drug",
- "item_name": "Mecetronium ethylsulphate",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.461696",
- "name": "Mecetronium ethylsulphate",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Meclizine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Meclizine",
- "item_group": "Drug",
- "item_name": "Meclizine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.478020",
- "name": "Meclizine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Oxaprozin",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Oxaprozin",
- "item_group": "Drug",
- "item_name": "Oxaprozin",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
-
-
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.496221",
- "name": "Oxaprozin",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Oxazepam",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Oxazepam",
- "item_group": "Drug",
- "item_name": "Oxazepam",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.511933",
- "name": "Oxazepam",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Oxcarbazepine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Oxcarbazepine",
- "item_group": "Drug",
- "item_name": "Oxcarbazepine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.528472",
- "name": "Oxcarbazepine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Oxetacaine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Oxetacaine",
- "item_group": "Drug",
- "item_name": "Oxetacaine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.544177",
- "name": "Oxetacaine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Oxethazaine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Oxethazaine",
- "item_group": "Drug",
- "item_name": "Oxethazaine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.560193",
- "name": "Oxethazaine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Suxamethonium Chloride",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Suxamethonium Chloride",
- "item_group": "Drug",
- "item_name": "Suxamethonium Chloride",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.576447",
- "name": "Suxamethonium Chloride",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Tacrolimus",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Tacrolimus",
- "item_group": "Drug",
- "item_name": "Tacrolimus",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.593481",
- "name": "Tacrolimus",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Ubiquinol",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Ubiquinol",
- "item_group": "Drug",
- "item_name": "Ubiquinol",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.609930",
- "name": "Ubiquinol",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin B12",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin B12",
- "item_group": "Drug",
- "item_name": "Vitamin B12",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.626225",
- "name": "Vitamin B12",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin B1Hydrochloride",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin B1Hydrochloride",
- "item_group": "Drug",
- "item_name": "Vitamin B1Hydrochloride",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.642423",
- "name": "Vitamin B1Hydrochloride",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin B1Monohydrate",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin B1Monohydrate",
- "item_group": "Drug",
- "item_name": "Vitamin B1Monohydrate",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.658946",
- "name": "Vitamin B1Monohydrate",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin B2",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin B2",
- "item_group": "Drug",
- "item_name": "Vitamin B2",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.675234",
- "name": "Vitamin B2",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin B3",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin B3",
- "item_group": "Drug",
- "item_name": "Vitamin B3",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.691598",
- "name": "Vitamin B3",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin D4",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin D4",
- "item_group": "Drug",
- "item_name": "Vitamin D4",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.707840",
- "name": "Vitamin D4",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Vitamin E",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Vitamin E",
- "item_group": "Drug",
- "item_name": "Vitamin E",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.723859",
- "name": "Vitamin E",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Wheat Germ Oil",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Wheat Germ Oil",
- "item_group": "Drug",
- "item_name": "Wheat Germ Oil",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.739829",
- "name": "Wheat Germ Oil",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Wheatgrass extr",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Wheatgrass extr",
- "item_group": "Drug",
- "item_name": "Wheatgrass extr",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.757695",
- "name": "Wheatgrass extr",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Whey Protein",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Whey Protein",
- "item_group": "Drug",
- "item_name": "Whey Protein",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.774098",
- "name": "Whey Protein",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Xylometazoline",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Xylometazoline",
- "item_group": "Drug",
- "item_name": "Xylometazoline",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.790224",
- "name": "Xylometazoline",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Xylometazoline Hydrochloride",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Xylometazoline Hydrochloride",
- "item_group": "Drug",
- "item_name": "Xylometazoline Hydrochloride",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.806359",
- "name": "Xylometazoline Hydrochloride",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Yeast",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Yeast",
- "item_group": "Drug",
- "item_name": "Yeast",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.823305",
- "name": "Yeast",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Yellow Fever Vaccine",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Yellow Fever Vaccine",
- "item_group": "Drug",
- "item_name": "Yellow Fever Vaccine",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.840250",
- "name": "Yellow Fever Vaccine",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Zafirlukast",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Zafirlukast",
- "item_group": "Drug",
- "item_name": "Zafirlukast",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.856856",
- "name": "Zafirlukast",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Zaleplon",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Zaleplon",
- "item_group": "Drug",
- "item_name": "Zaleplon",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.873287",
- "name": "Zaleplon",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Zaltoprofen",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Zaltoprofen",
- "item_group": "Drug",
- "item_name": "Zaltoprofen",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.889263",
- "name": "Zaltoprofen",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- },
- {
- "asset_category": null,
- "attributes": [],
- "barcode": null,
- "brand": null,
- "buying_cost_center": null,
- "country_of_origin": null,
- "create_new_batch": 0,
- "customer_code": "",
- "customer_items": [],
- "customs_tariff_number": null,
- "default_bom": null,
- "default_material_request_type": null,
- "default_supplier": null,
- "default_warehouse": null,
- "delivered_by_supplier": 0,
- "description": "Zanamivir",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Item",
- "end_of_life": null,
- "expense_account": null,
- "gst_hsn_code": null,
- "has_batch_no": 0,
- "has_serial_no": 0,
- "has_variants": 0,
- "image": null,
- "income_account": null,
- "inspection_required_before_delivery": 0,
- "inspection_required_before_purchase": 0,
- "is_fixed_asset": 0,
- "is_purchase_item": 1,
- "is_sales_item": 1,
- "is_stock_item": 1,
- "is_sub_contracted_item": 0,
- "item_code": "Zanamivir",
- "item_group": "Drug",
- "item_name": "Zanamivir",
- "last_purchase_rate": 0.0,
- "lead_time_days": 0,
- "max_discount": 0.0,
- "min_order_qty": 0.0,
- "modified": "2017-07-06 12:53:17.905022",
- "name": "Zanamivir",
- "naming_series": null,
- "net_weight": 0.0,
- "opening_stock": 0.0,
- "quality_parameters": [],
- "reorder_levels": [],
- "route": null,
- "safety_stock": 0.0,
- "selling_cost_center": null,
- "serial_no_series": null,
- "show_variant_in_website": 0,
- "slideshow": null,
- "standard_rate": 0.0,
- "stock_uom": "Nos",
- "supplier_items": [],
- "taxes": [],
- "thumbnail": null,
- "tolerance": 0.0,
- "uoms": [
- {
- "conversion_factor": 1.0,
- "uom": "Nos"
- }
- ],
- "valuation_method": null,
- "valuation_rate": 0.0,
- "variant_based_on": null,
- "variant_of": null,
- "warranty_period": null,
- "web_long_description": null,
- "website_image": null,
- "website_item_groups": [],
- "website_specifications": [],
- "website_warehouse": null,
- "weight_uom": null,
- "weightage": 0
- }
-]
diff --git a/erpnext/demo/data/employee.json b/erpnext/demo/data/employee.json
deleted file mode 100644
index 2d2dbe8..0000000
--- a/erpnext/demo/data/employee.json
+++ /dev/null
@@ -1,92 +0,0 @@
-[
- {
- "date_of_birth": "1982-01-03",
- "date_of_joining": "2001-10-10",
- "employee_name": "Diana Prince",
- "first_name": "Diana",
- "last_name": "Prince",
- "gender": "Female",
- "user_id": "DianaPrince@example.com"
- },
- {
- "date_of_birth": "1959-02-03",
- "date_of_joining": "1976-09-16",
- "employee_name": "Zatanna Zatara",
- "gender": "Female",
- "user_id": "ZatannaZatara@example.com",
- "first_name": "Zatanna",
- "last_name": "Zatara"
- },
- {
- "date_of_birth": "1982-03-03",
- "date_of_joining": "2000-06-16",
- "employee_name": "Holly Granger",
- "gender": "Female",
- "user_id": "HollyGranger@example.com",
- "first_name": "Holly",
- "last_name": "Granger"
- },
- {
- "date_of_birth": "1945-04-04",
- "date_of_joining": "1969-07-01",
- "employee_name": "Neptunia Aquaria",
- "gender": "Female",
- "user_id": "NeptuniaAquaria@example.com",
- "first_name": "Neptunia",
- "last_name": "Aquaria"
- },
- {
- "date_of_birth": "1978-05-03",
- "date_of_joining": "1999-12-24",
- "employee_name": "Arthur Curry",
- "gender": "Male",
- "user_id": "ArthurCurry@example.com",
- "first_name": "Arthur",
- "last_name": "Curry"
- },
- {
- "date_of_birth": "1964-06-03",
- "date_of_joining": "1981-08-05",
- "employee_name": "Thalia Al Ghul",
- "gender": "Female",
- "user_id": "ThaliaAlGhul@example.com",
- "first_name": "Thalia",
- "last_name": "Al Ghul"
- },
- {
- "date_of_birth": "1982-07-03",
- "date_of_joining": "2006-06-10",
- "employee_name": "Maxwell Lord",
- "gender": "Male",
- "user_id": "MaxwellLord@example.com",
- "first_name": "Maxwell",
- "last_name": "Lord"
- },
- {
- "date_of_birth": "1969-08-03",
- "date_of_joining": "1993-10-21",
- "employee_name": "Grace Choi",
- "gender": "Female",
- "user_id": "GraceChoi@example.com",
- "first_name": "Grace",
- "last_name": "Choi"
- },
- {
- "date_of_birth": "1982-09-03",
- "date_of_joining": "2005-09-06",
- "employee_name": "Vandal Savage",
- "gender": "Male",
- "user_id": "VandalSavage@example.com",
- "first_name": "Vandal",
- "last_name": "Savage"
- },
- {
- "date_of_birth": "1985-10-03",
- "date_of_joining": "2007-12-25",
- "employee_name": "Caitlin Snow",
- "gender": "Female",
- "user_id": "CaitlinSnow@example.com",
- "first_name": "Caitlin",
- "last_name": "Snow"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/grading_scale.json b/erpnext/demo/data/grading_scale.json
deleted file mode 100644
index 0760919..0000000
--- a/erpnext/demo/data/grading_scale.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
- {
- "doctype": "Grading Scale",
- "grading_scale_name": "Standard Grading",
- "description": "Standard Grading Scale",
- "intervals": [
- {"threshold": 100.0, "grade_code": "A", "grade_description": "Excellent"},
- {"threshold": 89.9, "grade_code": "B+", "grade_description": "Close to Excellence"},
- {"threshold": 80.0, "grade_code": "B", "grade_description": "Good"},
- {"threshold": 69.9, "grade_code": "C+", "grade_description": "Almost Good"},
- {"threshold": 60.0, "grade_code": "C", "grade_description": "Average"},
- {"threshold": 50.0, "grade_code": "D+", "grade_description": "Have to Work"},
- {"threshold": 40.0, "grade_code": "D", "grade_description": "Not met Baseline Expectations"},
- {"threshold": 0.0, "grade_code": "F", "grade_description": "Have to work a lot"}
- ]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/instructor.json b/erpnext/demo/data/instructor.json
deleted file mode 100644
index a25d163..0000000
--- a/erpnext/demo/data/instructor.json
+++ /dev/null
@@ -1,128 +0,0 @@
-[
- {
- "doctype": "Instructor",
- "instructor_name": "Eddie Jessup",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "William Dyer",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Alastor Moody",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Charles Xavier",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Cuthbert Calculus",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Reed Richards",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Urban Chronotis",
- "naming_series": "INS/",
- "department": "Physics"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "River Song",
- "naming_series": "INS/",
- "department": "Physics"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Yana",
- "naming_series": "INS/",
- "department": "Physics"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Neil Lasrado",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Deepshi Garg",
- "naming_series": "INS/",
- "department": "Chemistry"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Shubham Saxena",
- "naming_series": "INS/",
- "department": "Physics"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Rushabh Mehta",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Umari Syed",
- "naming_series": "INS/",
- "department": "Chemistry"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Aman Singh",
- "naming_series": "INS/",
- "department": "Physics"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Nabin",
- "naming_series": "INS/",
- "department": "Chemistry"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Kanchan Chauhan",
- "naming_series": "INS/",
- "department": "Information Technology"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Valmik Jangla",
- "naming_series": "INS/",
- "department": "Chemistry"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Amit Jain",
- "naming_series": "INS/",
- "department": "Physics"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Shreyas P",
- "naming_series": "INS/",
- "department": "Chemistry"
- },
- {
- "doctype": "Instructor",
- "instructor_name": "Rohit",
- "naming_series": "INS/",
- "department": "Information Technology"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/item.json b/erpnext/demo/data/item.json
deleted file mode 100644
index 1d4ed34..0000000
--- a/erpnext/demo/data/item.json
+++ /dev/null
@@ -1,493 +0,0 @@
-[
- {
- "item_defaults": [
- {
- "default_supplier": "Asiatic Solutions",
- "default_warehouse": "Stores"
- }
- ],
- "description": "For Upper Bearing",
- "image": "/assets/erpnext_demo/images/disc.png",
- "item_code": "Disc Collars",
- "item_group": "Raw Material",
- "item_name": "Disc Collars"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Nan Duskin",
- "default_warehouse": "Stores"
- }
- ],
- "description": "CAST IRON, MCMASTER PART NO. 3710T13",
- "image": "/assets/erpnext_demo/images/bearing.jpg",
- "item_code": "Bearing Block",
- "item_group": "Raw Material",
- "item_name": "Bearing Block"
- },
- {
- "item_defaults": [
- {
- "default_supplier": null,
- "default_warehouse": "Finished Goods"
- }
- ],
- "description": "Wind Mill C Series for Commercial Use 18ft",
- "image": "/assets/erpnext_demo/images/wind-turbine-2.png",
- "item_code": "Wind MIll C Series",
- "item_group": "Products",
- "item_name": "Wind MIll C Series"
- },
- {
- "item_defaults": [
- {
- "default_supplier": null,
- "default_warehouse": "Finished Goods"
- }
- ],
- "description": "Wind Mill A Series for Home Use 9ft",
- "image": "/assets/erpnext_demo/images/wind-turbine.png",
- "item_code": "Wind Mill A Series",
- "item_group": "Products",
- "item_name": "Wind Mill A Series"
- },
- {
- "item_defaults": [
- {
- "default_supplier": null,
- "default_warehouse": "Finished Goods"
- }
- ],
- "description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->",
- "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
- "item_code": "Wind Turbine",
- "item_group": "Products",
- "item_name": "Wind Turbine",
- "has_variants": 1,
- "has_serial_no": 1,
- "attributes": [
- {
- "attribute": "Size"
- }
- ]
- },
- {
- "item_defaults": [
- {
- "default_supplier": "HomeBase",
- "default_warehouse": "Stores"
- }
- ],
- "description": "1.5 in. Diameter x 36 in. Mild Steel Tubing",
- "image": null,
- "item_code": "Bearing Pipe",
- "item_group": "Raw Material",
- "item_name": "Bearing Pipe"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "New World Realty",
- "default_warehouse": "Stores"
- }
- ],
- "description": "1/32 in. x 24 in. x 47 in. HDPE Opaque Sheet",
- "image": null,
- "item_code": "Wing Sheet",
- "item_group": "Raw Material",
- "item_name": "Wing Sheet"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Eagle Hardware",
- "default_warehouse": "Stores"
- }
- ],
- "description": "3/16 in. x 6 in. x 6 in. Low Carbon Steel Plate",
- "image": null,
- "item_code": "Upper Bearing Plate",
- "item_group": "Raw Material",
- "item_name": "Upper Bearing Plate"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Asiatic Solutions",
- "default_warehouse": "Stores"
- }
- ],
- "description": "Bearing Assembly",
- "image": null,
- "item_code": "Bearing Assembly",
- "item_group": "Sub Assemblies",
- "item_name": "Bearing Assembly"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "HomeBase",
- "default_warehouse": "Stores"
- }
- ],
- "description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood",
- "image": null,
- "item_code": "Base Plate",
- "item_group": "Raw Material",
- "item_name": "Base Plate",
- "is_sub_contracted_item": 1
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Scott Ties",
- "default_warehouse": "Stores"
- }
- ],
- "description": "N/A",
- "image": null,
- "item_code": "Stand",
- "item_group": "Raw Material",
- "item_name": "Stand"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Eagle Hardware",
- "default_warehouse": "Stores"
- }
- ],
- "description": "1 in. x 3 in. x 1 ft. Multipurpose Al Alloy Bar",
- "image": null,
- "item_code": "Bearing Collar",
- "item_group": "Raw Material",
- "item_name": "Bearing Collar"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Eagle Hardware",
- "default_warehouse": "Stores"
- }
- ],
- "description": "1/4 in. x 6 in. x 6 in. Mild Steel Plate",
- "image": null,
- "item_code": "Base Bearing Plate",
- "item_group": "Raw Material",
- "item_name": "Base Bearing Plate"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "HomeBase",
- "default_warehouse": "Stores"
- }
- ],
- "description": "15/32 in. x 4 ft. x 8 ft. 3-Ply Rtd Sheathing",
- "image": null,
- "item_code": "External Disc",
- "item_group": "Raw Material",
- "item_name": "External Disc"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Eagle Hardware",
- "default_warehouse": "Stores"
- }
- ],
- "description": "1.25 in. Diameter x 6 ft. Mild Steel Tubing",
- "image": null,
- "item_code": "Shaft",
- "item_group": "Raw Material",
- "item_name": "Shaft"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "Ks Merchandise",
- "default_warehouse": "Stores"
- }
- ],
- "description": "1/2 in. x 2 ft. x 4 ft. Pine Plywood",
- "image": null,
- "item_code": "Blade Rib",
- "item_group": "Raw Material",
- "item_name": "Blade Rib"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "HomeBase",
- "default_warehouse": "Stores"
- }
- ],
- "description": "For Bearing Collar",
- "image": null,
- "item_code": "Internal Disc",
- "item_group": "Raw Material",
- "item_name": "Internal Disc"
- },
- {
- "item_defaults": [
- {
- "default_supplier": null,
- "default_warehouse": "Finished Goods"
- }
- ],
- "description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Small</p>",
- "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
- "item_code": "Wind Turbine-S",
- "item_group": "Products",
- "item_name": "Wind Turbine-S",
- "variant_of": "Wind Turbine",
- "valuation_rate": 300,
- "attributes": [
- {
- "attribute": "Size",
- "attribute_value": "Small"
- }
- ]
- },
- {
- "item_defaults": [
- {
- "default_supplier": null,
- "default_warehouse": "Finished Goods"
- }
- ],
- "description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Medium</p>",
- "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
- "item_code": "Wind Turbine-M",
- "item_group": "Products",
- "item_name": "Wind Turbine-M",
- "variant_of": "Wind Turbine",
- "valuation_rate": 300,
- "attributes": [
- {
- "attribute": "Size",
- "attribute_value": "Medium"
- }
- ]
- },
- {
- "item_defaults": [
- {
- "default_supplier": null,
- "default_warehouse": "Finished Goods"
- }
- ],
- "description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Large</p>",
- "image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
- "item_code": "Wind Turbine-L",
- "item_group": "Products",
- "item_name": "Wind Turbine-L",
- "variant_of": "Wind Turbine",
- "valuation_rate": 300,
- "attributes": [
- {
- "attribute": "Size",
- "attribute_value": "Large"
- }
- ]
- },
- {
- "is_stock_item": 0,
- "description": "Wind Mill A Series with Spare Bearing",
- "item_code": "Wind Mill A Series with Spare Bearing",
- "item_group": "Products",
- "item_name": "Wind Mill A Series with Spare Bearing"
- },
- {
- "item_defaults": [
- {
- "default_supplier": "HomeBase",
- "default_warehouse": "Stores"
- }
- ],
- "description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood",
- "image": null,
- "item_code": "Base Plate Un Painted",
- "item_group": "Raw Material",
- "item_name": "Base Plate Un Painted"
- },
- {
- "is_fixed_asset": 1,
- "asset_category": "Furnitures",
- "is_stock_item": 0,
- "description": "Table",
- "item_code": "Table",
- "item_name": "Table",
- "item_group": "Products"
- },
- {
- "is_fixed_asset": 1,
- "asset_category": "Furnitures",
- "is_stock_item": 0,
- "description": "Chair",
- "item_code": "Chair",
- "item_name": "Chair",
- "item_group": "Products"
- },
- {
- "is_fixed_asset": 1,
- "asset_category": "Electronic Equipments",
- "is_stock_item": 0,
- "description": "Computer",
- "item_code": "Computer",
- "item_name": "Computer",
- "item_group": "Products"
- },
- {
- "is_fixed_asset": 1,
- "asset_category": "Electronic Equipments",
- "is_stock_item": 0,
- "description": "Mobile",
- "item_code": "Mobile",
- "item_name": "Mobile",
- "item_group": "Products"
- },
- {
- "is_fixed_asset": 1,
- "asset_category": "Softwares",
- "is_stock_item": 0,
- "description": "ERP",
- "item_code": "ERP",
- "item_name": "ERP",
- "item_group": "All Item Groups"
- },
- {
- "is_fixed_asset": 1,
- "asset_category": "Softwares",
- "is_stock_item": 0,
- "description": "Autocad",
- "item_code": "Autocad",
- "item_name": "Autocad",
- "item_group": "All Item Groups"
- },
- {
- "is_stock_item": 1,
- "has_batch_no": 1,
- "create_new_batch": 1,
- "valuation_rate": 200,
- "item_defaults": [
- {
- "default_warehouse": "Stores"
- }
- ],
- "description": "Corrugated Box",
- "item_code": "Corrugated Box",
- "item_name": "Corrugated Box",
- "item_group": "All Item Groups"
- },
- {
- "item_defaults": [
- {
- "default_warehouse": "Finished Goods"
- }
- ],
- "is_stock_item": 1,
- "description": "OnePlus 6",
- "item_code": "OnePlus 6",
- "item_name": "OnePlus 6",
- "item_group": "Products",
- "domain": "Retail"
- },
- {
- "item_defaults": [
- {
- "default_warehouse": "Finished Goods"
- }
- ],
- "is_stock_item": 1,
- "description": "OnePlus 6T",
- "item_code": "OnePlus 6T",
- "item_name": "OnePlus 6T",
- "item_group": "Products",
- "domain": "Retail"
- },
- {
- "item_defaults": [
- {
- "default_warehouse": "Finished Goods"
- }
- ],
- "is_stock_item": 1,
- "description": "Xiaomi Poco F1",
- "item_code": "Xiaomi Poco F1",
- "item_name": "Xiaomi Poco F1",
- "item_group": "Products",
- "domain": "Retail"
- },
- {
- "item_defaults": [
- {
- "default_warehouse": "Finished Goods"
- }
- ],
- "is_stock_item": 1,
- "description": "Iphone XS",
- "item_code": "Iphone XS",
- "item_name": "Iphone XS",
- "item_group": "Products",
- "domain": "Retail"
- },
- {
- "item_defaults": [
- {
- "default_warehouse": "Finished Goods"
- }
- ],
- "is_stock_item": 1,
- "description": "Samsung Galaxy S9",
- "item_code": "Samsung Galaxy S9",
- "item_name": "Samsung Galaxy S9",
- "item_group": "Products",
- "domain": "Retail"
- },
- {
- "item_defaults": [
- {
- "default_warehouse": "Finished Goods"
- }
- ],
- "is_stock_item": 1,
- "description": "Sony Bluetooth Headphone",
- "item_code": "Sony Bluetooth Headphone",
- "item_name": "Sony Bluetooth Headphone",
- "item_group": "Products",
- "domain": "Retail"
- },
- {
- "is_stock_item": 0,
- "description": "Samsung Phone Repair",
- "item_code": "Samsung Phone Repair",
- "item_name": "Samsung Phone Repair",
- "item_group": "Services",
- "domain": "Retail"
- },
- {
- "is_stock_item": 0,
- "description": "OnePlus Phone Repair",
- "item_code": "OnePlus Phone Repair",
- "item_name": "OnePlus Phone Repair",
- "item_group": "Services",
- "domain": "Retail"
- },
- {
- "is_stock_item": 0,
- "description": "Xiaomi Phone Repair",
- "item_code": "Xiaomi Phone Repair",
- "item_name": "Xiaomi Phone Repair",
- "item_group": "Services",
- "domain": "Retail"
- },
- {
- "is_stock_item": 0,
- "description": "Apple Phone Repair",
- "item_code": "Apple Phone Repair",
- "item_name": "Apple Phone Repair",
- "item_group": "Services",
- "domain": "Retail"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/item_education.json b/erpnext/demo/data/item_education.json
deleted file mode 100644
index 40e4701..0000000
--- a/erpnext/demo/data/item_education.json
+++ /dev/null
@@ -1,137 +0,0 @@
-[
- {
- "default_supplier": "Asiatic Solutions",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Books",
- "item_group": "Raw Material",
- "item_name": "Books"
- },
- {
- "default_supplier": "HomeBase",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Pencil",
- "item_group": "Raw Material",
- "item_name": "Pencil"
- },
- {
- "default_supplier": "New World Realty",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Tables",
- "item_group": "Raw Material",
- "item_name": "Tables"
- },
- {
- "default_supplier": "Eagle Hardware",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Chair",
- "item_group": "Raw Material",
- "item_name": "Chair"
- },
- {
- "default_supplier": "Asiatic Solutions",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Black Board",
- "item_group": "Sub Assemblies",
- "item_name": "Black Board"
- },
- {
- "default_supplier": "HomeBase",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Chalk",
- "item_group": "Raw Material",
- "item_name": "Chalk"
- },
- {
- "default_supplier": "HomeBase",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Notepad",
- "item_group": "Raw Material",
- "item_name": "Notepad"
- },
- {
- "default_supplier": "Ks Merchandise",
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Uniform",
- "item_group": "Raw Material",
- "item_name": "Uniform"
- },
- {
- "is_stock_item": 0,
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "description": "Computer",
- "item_code": "Computer",
- "item_name": "Computer",
- "item_group": "Products"
- },
- {
- "is_stock_item": 0,
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "description": "Mobile",
- "item_code": "Mobile",
- "item_name": "Mobile",
- "item_group": "Products"
- },
- {
- "is_stock_item": 0,
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "description": "ERP",
- "item_code": "ERP",
- "item_name": "ERP",
- "item_group": "All Item Groups"
- },
- {
- "is_stock_item": 0,
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "description": "Autocad",
- "item_code": "Autocad",
- "item_name": "Autocad",
- "item_group": "All Item Groups"
- },
- {
- "item_defaults": [{
- "default_warehouse": "Stores",
- "company": "Whitmore College"
- }],
- "item_code": "Service",
- "item_group": "Services",
- "item_name": "Service",
- "has_variants": 0,
- "is_stock_item": 0
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/lead.json b/erpnext/demo/data/lead.json
deleted file mode 100644
index ff78877..0000000
--- a/erpnext/demo/data/lead.json
+++ /dev/null
@@ -1,127 +0,0 @@
-[
- {
- "company_name": "Zany Brainy",
- "email_id": "MartLakeman@example.com",
- "lead_name": "Mart Lakeman"
- },
- {
- "company_name": "Patterson-Fletcher",
- "email_id": "SagaLundqvist@example.com",
- "lead_name": "Saga Lundqvist"
- },
- {
- "company_name": "Griff's Hamburgers",
- "email_id": "AdnaSjoberg@example.com",
- "lead_name": "Adna Sj\u00f6berg"
- },
- {
- "company_name": "Rhodes Furniture",
- "email_id": "IdaDSvendsen@example.com",
- "lead_name": "Ida Svendsen"
- },
- {
- "company_name": "Burger Chef",
- "email_id": "EmppuHameenniemi@example.com",
- "lead_name": "Emppu H\u00e4meenniemi"
- },
- {
- "company_name": "Stratabiz",
- "email_id": "EugenioPisano@example.com",
- "lead_name": "Eugenio Pisano"
- },
- {
- "company_name": "Home Quarters Warehouse",
- "email_id": "SemharHagos@example.com",
- "lead_name": "Semhar Hagos"
- },
- {
- "company_name": "Enviro Architectural Designs",
- "email_id": "BranimiraIvankovic@example.com",
- "lead_name": "Branimira Ivankovi\u0107"
- },
- {
- "company_name": "Ideal Garden Management",
- "email_id": "ShellyLFields@example.com",
- "lead_name": "Shelly Fields"
- },
- {
- "company_name": "Listen Up",
- "email_id": "LeoMikulic@example.com",
- "lead_name": "Leo Mikuli\u0107"
- },
- {
- "company_name": "I. Magnin",
- "email_id": "DenisaJarosova@example.com",
- "lead_name": "Denisa Jaro\u0161ov\u00e1"
- },
- {
- "company_name": "First Rate Choice",
- "email_id": "JanekRutkowski@example.com",
- "lead_name": "Janek Rutkowski"
- },
- {
- "company_name": "Multi Tech Development",
- "email_id": "mm@example.com",
- "lead_name": "\u7f8e\u6708 \u5b87\u85e4"
- },
- {
- "company_name": "National Auto Parts",
- "email_id": "dd@example.com",
- "lead_name": "\u0414\u0430\u043d\u0438\u0438\u043b \u0410\u0444\u0430\u043d\u0430\u0441\u044c\u0435\u0432"
- },
- {
- "company_name": "Integra Investment Plan",
- "email_id": "ZorislavPetkovic@example.com",
- "lead_name": "Zorislav Petkovi\u0107"
- },
- {
- "company_name": "The Lawn Guru",
- "email_id": "NanaoNiwa@example.com",
- "lead_name": "Nanao Niwa"
- },
- {
- "company_name": "Buena Vista Realty Service",
- "email_id": "HreiarJorundsson@example.com",
- "lead_name": "Hrei\u00f0ar J\u00f6rundsson"
- },
- {
- "company_name": "Bountiful Harvest Health Food Store",
- "email_id": "ChuThiBichLai@example.com",
- "lead_name": "Lai Chu"
- },
- {
- "company_name": "P. Samuels Men's Clothiers",
- "email_id": "VictorAksakov@example.com",
- "lead_name": "Victor Aksakov"
- },
- {
- "company_name": "Vinyl Fever",
- "email_id": "SaidalimBisliev@example.com",
- "lead_name": "Saidalim Bisliev"
- },
- {
- "company_name": "Garden Master",
- "email_id": "TotteJakobsson@example.com",
- "lead_name": "Totte Jakobsson"
- },
- {
- "company_name": "Big Apple",
- "email_id": "NanaArmasRobles@example.com",
- "lead_name": "Nan\u00e1 Armas"
- },
- {
- "company_name": "Monk House Sales",
- "email_id": "WalerianDuda@example.com",
- "lead_name": "Walerian Duda"
- },
- {
- "company_name": "ManCharm",
- "email_id": "Moarimikashi@example.com",
- "lead_name": "Moarimikashi"
- },
- {
- "company_name": "Custom Lawn Care",
- "email_id": "DobromilDabrowski@example.com",
- "lead_name": "Dobromi\u0142 D\u0105browski"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/location.json b/erpnext/demo/data/location.json
deleted file mode 100644
index b521aa0..0000000
--- a/erpnext/demo/data/location.json
+++ /dev/null
@@ -1,22 +0,0 @@
-[
- {
- "location_name": "Main Location",
- "latitude": 40.0,
- "longitude": 20.0
- },
- {
- "location_name": "Avg Location",
- "latitude": 63.0,
- "longitude": 99.3
- },
- {
- "location_name": "Zany Location",
- "latitude": 47.5,
- "longitude": 10.0
- },
- {
- "location_name": "Fletcher Location",
- "latitude": 100.90,
- "longitude": 80
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/operation.json b/erpnext/demo/data/operation.json
deleted file mode 100644
index 47f26d1..0000000
--- a/erpnext/demo/data/operation.json
+++ /dev/null
@@ -1,32 +0,0 @@
-[
- {
- "description": "Setup Fixtures for Assembly",
- "name": "Setup Fixtures",
- "workstation": "Assembly Station 1"
- },
- {
- "description": "Assemble Unit as per Standard Operating Procedures",
- "name": "Assembly Operation",
- "workstation": "Assembly Station 1"
- },
- {
- "description": "Final Testing Checklist",
- "name": "Testing",
- "workstation": "Packing and Testing Station"
- },
- {
- "description": "Final Packing and add Instructions",
- "name": "Packing",
- "workstation": "Packing and Testing Station"
- },
- {
- "description": "Prepare frame for assembly",
- "name": "Prepare Frame",
- "workstation": "Drilling Machine 1"
- },
- {
- "description": "Connect wires",
- "name": "Wiring",
- "workstation": "Assembly Station 1"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/patient.json b/erpnext/demo/data/patient.json
deleted file mode 100644
index 6d95a20..0000000
--- a/erpnext/demo/data/patient.json
+++ /dev/null
@@ -1,27 +0,0 @@
-[
- {
- "patient_name": "lila",
- "gender": "Female"
- },
- {
- "patient_name": "charline",
- "gender": "Female"
- },
- {
- "patient_name": "soren",
- "last_name": "le gall",
- "gender": "Male"
- },
- {
- "patient_name": "fanny",
- "gender": "Female"
- },
- {
- "patient_name": "julie",
- "gender": "Female"
- },
- {
- "patient_name": "louka",
- "gender": "Male"
- }
-]
diff --git a/erpnext/demo/data/practitioner.json b/erpnext/demo/data/practitioner.json
deleted file mode 100644
index 39c960f..0000000
--- a/erpnext/demo/data/practitioner.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
- {
- "doctype": "Healthcare Practitioner",
- "first_name": "Eddie Jessup",
- "department": "Pathology"
- },
- {
- "doctype": "Healthcare Practitioner",
- "first_name": "Deepshi Garg",
- "department": "ENT"
- },
- {
- "doctype": "Healthcare Practitioner",
- "first_name": "Amit Jain",
- "department": "Microbiology"
- }
-]
diff --git a/erpnext/demo/data/program.json b/erpnext/demo/data/program.json
deleted file mode 100644
index 9c2ec77..0000000
--- a/erpnext/demo/data/program.json
+++ /dev/null
@@ -1,46 +0,0 @@
-[
- {
- "doctype": "Program",
- "name": "MCA",
- "program_name": "Masters of Computer Applications",
- "program_code": "MCA",
- "department": "Information Technology",
- "courses": [
- { "course": "MCA4010" },
- { "course": "MCA4020" },
- { "course": "MCA4030" }
- ]
- },
- {
- "doctype": "Program",
- "name": "BCA",
- "program_name": "Bachelor of Computer Applications",
- "program_code": "BCA",
- "department": "Information Technology",
- "courses": [
- { "course": "BCA2030" },
- { "course": "BCA1030" },
- { "course": "BCA2020" },
- { "course": "BCA1040" },
- { "course": "BCA1010" },
- { "course": "BCA2010" },
- { "course": "BCA1020" }
- ]
- },
- {
- "doctype": "Program",
- "name": "BBA",
- "program_name": "Bachelor of Business Administration",
- "program_code": "BBA",
- "department": "Management Studies",
- "courses": [
- { "course": "BBA 101" },
- { "course": "BBA 102" },
- { "course": "BBA 103" },
- { "course": "BBA 301" },
- { "course": "BBA 302" },
- { "course": "BBA 304" },
- { "course": "BBA 505" }
- ]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/random_student_data.json b/erpnext/demo/data/random_student_data.json
deleted file mode 100644
index babcc71..0000000
--- a/erpnext/demo/data/random_student_data.json
+++ /dev/null
@@ -1,1604 +0,0 @@
-[
-{
-"first_name": "amanda",
-"last_name": "edwards",
-"image": "https://randomuser.me/api/portraits/women/55.jpg",
-"gender": "Female"
-},
-{
-"first_name": "abbie",
-"last_name": "johnston",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "heather",
-"last_name": "nelson",
-"image": "https://randomuser.me/api/portraits/women/13.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maxwell",
-"last_name": "gilbert",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "molly",
-"last_name": "ramirez",
-"image": "https://randomuser.me/api/portraits/women/71.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ian",
-"last_name": "barrett",
-"image": "https://randomuser.me/api/portraits/men/68.jpg",
-"gender": "Male"
-},
-{
-"first_name": "kim",
-"last_name": "hudson",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "bruce",
-"last_name": "murray",
-"image": "https://randomuser.me/api/portraits/men/59.jpg",
-"gender": "Male"
-},
-{
-"first_name": "henry",
-"last_name": "powell",
-"image": "https://randomuser.me/api/portraits/men/88.jpg",
-"gender": "Male"
-},
-{
-"first_name": "chris",
-"last_name": "foster",
-"image": "https://randomuser.me/api/portraits/men/5.jpg",
-"gender": "Male"
-},
-{
-"first_name": "billy",
-"last_name": "kim",
-"image": "https://randomuser.me/api/portraits/men/91.jpg",
-"gender": "Male"
-},
-{
-"first_name": "samuel",
-"last_name": "harper",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jayden",
-"last_name": "kelly",
-"image": "https://randomuser.me/api/portraits/men/31.jpg",
-"gender": "Male"
-},
-{
-"first_name": "grace",
-"last_name": "berry",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ronnie",
-"last_name": "nelson",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "harvey",
-"last_name": "harper",
-"image": "https://randomuser.me/api/portraits/men/68.jpg",
-"gender": "Male"
-},
-{
-"first_name": "maya",
-"last_name": "fernandez",
-"image": "https://randomuser.me/api/portraits/women/79.jpg",
-"gender": "Female"
-},
-{
-"first_name": "faith",
-"last_name": "lewis",
-"image": "https://randomuser.me/api/portraits/women/84.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kirk",
-"last_name": "macrae",
-"image": "https://randomuser.me/api/portraits/men/13.jpg",
-"gender": "Male"
-},
-{
-"first_name": "tracy",
-"last_name": "holt",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mandy",
-"last_name": "dean",
-"image": "https://randomuser.me/api/portraits/women/0.jpg",
-"gender": "Female"
-},
-{
-"first_name": "sam",
-"last_name": "dunn",
-"image": "https://randomuser.me/api/portraits/women/12.jpg",
-"gender": "Female"
-},
-{
-"first_name": "zoe",
-"last_name": "fleming",
-"image": "https://randomuser.me/api/portraits/women/9.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jeffrey",
-"last_name": "stewart",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "dick",
-"last_name": "ryan",
-"image": "https://randomuser.me/api/portraits/men/63.jpg",
-"gender": "Male"
-},
-{
-"first_name": "carl",
-"last_name": "neal",
-"image": "https://randomuser.me/api/portraits/men/41.jpg",
-"gender": "Male"
-},
-{
-"first_name": "scarlett",
-"last_name": "ruiz",
-"image": "https://randomuser.me/api/portraits/women/24.jpg",
-"gender": "Female"
-},
-{
-"first_name": "rene",
-"last_name": "hughes",
-"image": "https://randomuser.me/api/portraits/men/3.jpg",
-"gender": "Male"
-},
-{
-"first_name": "greg",
-"last_name": "montgomery",
-"image": "https://randomuser.me/api/portraits/men/12.jpg",
-"gender": "Male"
-},
-{
-"first_name": "matt",
-"last_name": "lane",
-"image": "https://randomuser.me/api/portraits/men/85.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eleanor",
-"last_name": "pearson",
-"image": "https://randomuser.me/api/portraits/women/61.jpg",
-"gender": "Female"
-},
-{
-"first_name": "theodore",
-"last_name": "burton",
-"image": "https://randomuser.me/api/portraits/men/81.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jesus",
-"last_name": "hunt",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "taylor",
-"last_name": "alvarez",
-"image": "https://randomuser.me/api/portraits/men/0.jpg",
-"gender": "Male"
-},
-{
-"first_name": "barbara",
-"last_name": "lucas",
-"image": "https://randomuser.me/api/portraits/women/21.jpg",
-"gender": "Female"
-},
-{
-"first_name": "nicky",
-"last_name": "simmons",
-"image": "https://randomuser.me/api/portraits/women/29.jpg",
-"gender": "Female"
-},
-{
-"first_name": "arthur",
-"last_name": "obrien",
-"image": "https://randomuser.me/api/portraits/men/11.jpg",
-"gender": "Male"
-},
-{
-"first_name": "donna",
-"last_name": "holmes",
-"image": "https://randomuser.me/api/portraits/women/33.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mitchell",
-"last_name": "castro",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "byron",
-"last_name": "marshall",
-"image": "https://randomuser.me/api/portraits/men/57.jpg",
-"gender": "Male"
-},
-{
-"first_name": "larry",
-"last_name": "king",
-"image": "https://randomuser.me/api/portraits/men/58.jpg",
-"gender": "Male"
-},
-{
-"first_name": "deborah",
-"last_name": "fuller",
-"image": "https://randomuser.me/api/portraits/women/50.jpg",
-"gender": "Female"
-},
-{
-"first_name": "eleanor",
-"last_name": "elliott",
-"image": "https://randomuser.me/api/portraits/women/80.jpg",
-"gender": "Female"
-},
-{
-"first_name": "derrick",
-"last_name": "shaw",
-"image": "https://randomuser.me/api/portraits/men/78.jpg",
-"gender": "Male"
-},
-{
-"first_name": "barbara",
-"last_name": "lynch",
-"image": "https://randomuser.me/api/portraits/women/15.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elijah",
-"last_name": "allen",
-"image": "https://randomuser.me/api/portraits/men/43.jpg",
-"gender": "Male"
-},
-{
-"first_name": "nicholas",
-"last_name": "harper",
-"image": "https://randomuser.me/api/portraits/men/2.jpg",
-"gender": "Male"
-},
-{
-"first_name": "sofia",
-"last_name": "riley",
-"image": "https://randomuser.me/api/portraits/women/96.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jar",
-"last_name": "hunt",
-"image": "https://randomuser.me/api/portraits/men/72.jpg",
-"gender": "Male"
-},
-{
-"first_name": "philip",
-"last_name": "rose",
-"image": "https://randomuser.me/api/portraits/men/16.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ella",
-"last_name": "moore",
-"image": "https://randomuser.me/api/portraits/women/83.jpg",
-"gender": "Female"
-},
-{
-"first_name": "seth",
-"last_name": "tucker",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "abby",
-"last_name": "gonzalez",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "noah",
-"last_name": "williamson",
-"image": "https://randomuser.me/api/portraits/men/54.jpg",
-"gender": "Male"
-},
-{
-"first_name": "cathy",
-"last_name": "gray",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "barb",
-"last_name": "snyder",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "rosalyn",
-"last_name": "hale",
-"image": "https://randomuser.me/api/portraits/women/64.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jessica",
-"last_name": "armstrong",
-"image": "https://randomuser.me/api/portraits/women/95.jpg",
-"gender": "Female"
-},
-{
-"first_name": "vicki",
-"last_name": "wheeler",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "luke",
-"last_name": "fisher",
-"image": "https://randomuser.me/api/portraits/men/77.jpg",
-"gender": "Male"
-},
-{
-"first_name": "joey",
-"last_name": "wheeler",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "victoria",
-"last_name": "jimenez",
-"image": "https://randomuser.me/api/portraits/women/25.jpg",
-"gender": "Female"
-},
-{
-"first_name": "daryl",
-"last_name": "patterson",
-"image": "https://randomuser.me/api/portraits/men/30.jpg",
-"gender": "Male"
-},
-{
-"first_name": "dwayne",
-"last_name": "jensen",
-"image": "https://randomuser.me/api/portraits/men/71.jpg",
-"gender": "Male"
-},
-{
-"first_name": "herbert",
-"last_name": "silva",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "walter",
-"last_name": "walker",
-"image": "https://randomuser.me/api/portraits/men/91.jpg",
-"gender": "Male"
-},
-{
-"first_name": "logan",
-"last_name": "banks",
-"image": "https://randomuser.me/api/portraits/men/67.jpg",
-"gender": "Male"
-},
-{
-"first_name": "shawn",
-"last_name": "harvey",
-"image": "https://randomuser.me/api/portraits/men/87.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lawrence",
-"last_name": "bradley",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jack",
-"last_name": "fleming",
-"image": "https://randomuser.me/api/portraits/men/37.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jackson",
-"last_name": "boyd",
-"image": "https://randomuser.me/api/portraits/men/68.jpg",
-"gender": "Male"
-},
-{
-"first_name": "cecil",
-"last_name": "webb",
-"image": "https://randomuser.me/api/portraits/men/9.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eliza",
-"last_name": "mills",
-"image": "https://randomuser.me/api/portraits/women/20.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jenny",
-"last_name": "frazier",
-"image": "https://randomuser.me/api/portraits/women/61.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kent",
-"last_name": "butler",
-"image": "https://randomuser.me/api/portraits/men/64.jpg",
-"gender": "Male"
-},
-{
-"first_name": "rose",
-"last_name": "perry",
-"image": "https://randomuser.me/api/portraits/women/74.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jack",
-"last_name": "king",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elmer",
-"last_name": "williams",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "vanessa",
-"last_name": "torres",
-"image": "https://randomuser.me/api/portraits/women/41.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tyrone",
-"last_name": "coleman",
-"image": "https://randomuser.me/api/portraits/men/59.jpg",
-"gender": "Male"
-},
-{
-"first_name": "julie",
-"last_name": "bradley",
-"image": "https://randomuser.me/api/portraits/women/50.jpg",
-"gender": "Female"
-},
-{
-"first_name": "fernando",
-"last_name": "castro",
-"image": "https://randomuser.me/api/portraits/men/44.jpg",
-"gender": "Male"
-},
-{
-"first_name": "sara",
-"last_name": "craig",
-"image": "https://randomuser.me/api/portraits/women/8.jpg",
-"gender": "Female"
-},
-{
-"first_name": "steven",
-"last_name": "stone",
-"image": "https://randomuser.me/api/portraits/men/47.jpg",
-"gender": "Male"
-},
-{
-"first_name": "barb",
-"last_name": "rodriquez",
-"image": "https://randomuser.me/api/portraits/women/73.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charlie",
-"last_name": "king",
-"image": "https://randomuser.me/api/portraits/men/79.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jessica",
-"last_name": "davis",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lewis",
-"last_name": "watson",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "charlotte",
-"last_name": "johnson",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "danielle",
-"last_name": "bell",
-"image": "https://randomuser.me/api/portraits/women/54.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kristin",
-"last_name": "dixon",
-"image": "https://randomuser.me/api/portraits/women/23.jpg",
-"gender": "Female"
-},
-{
-"first_name": "andrea",
-"last_name": "thompson",
-"image": "https://randomuser.me/api/portraits/women/54.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ashley",
-"last_name": "andrews",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "sharon",
-"last_name": "martinez",
-"image": "https://randomuser.me/api/portraits/women/6.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tristan",
-"last_name": "cunningham",
-"image": "https://randomuser.me/api/portraits/men/62.jpg",
-"gender": "Male"
-},
-{
-"first_name": "carol",
-"last_name": "chavez",
-"image": "https://randomuser.me/api/portraits/women/85.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lauren",
-"last_name": "hudson",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "guy",
-"last_name": "robertson",
-"image": "https://randomuser.me/api/portraits/men/78.jpg",
-"gender": "Male"
-},
-{
-"first_name": "debra",
-"last_name": "long",
-"image": "https://randomuser.me/api/portraits/women/23.jpg",
-"gender": "Female"
-},
-{
-"first_name": "taylor",
-"last_name": "carpenter",
-"image": "https://randomuser.me/api/portraits/men/0.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eetu",
-"last_name": "annala",
-"image": "https://randomuser.me/api/portraits/men/31.jpg",
-"gender": "Male"
-},
-{
-"first_name": "oliver",
-"last_name": "moilanen",
-"image": "https://randomuser.me/api/portraits/men/14.jpg",
-"gender": "Male"
-},
-{
-"first_name": "leo",
-"last_name": "maunu",
-"image": "https://randomuser.me/api/portraits/men/72.jpg",
-"gender": "Male"
-},
-{
-"first_name": "iiris",
-"last_name": "kalas",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "aada",
-"last_name": "kinnunen",
-"image": "https://randomuser.me/api/portraits/women/64.jpg",
-"gender": "Female"
-},
-{
-"first_name": "topias",
-"last_name": "walli",
-"image": "https://randomuser.me/api/portraits/men/58.jpg",
-"gender": "Male"
-},
-{
-"first_name": "viivi",
-"last_name": "toivonen",
-"image": "https://randomuser.me/api/portraits/women/16.jpg",
-"gender": "Female"
-},
-{
-"first_name": "iina",
-"last_name": "makinen",
-"image": "https://randomuser.me/api/portraits/women/44.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lumi",
-"last_name": "tuominen",
-"image": "https://randomuser.me/api/portraits/women/11.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ellen",
-"last_name": "koski",
-"image": "https://randomuser.me/api/portraits/women/22.jpg",
-"gender": "Female"
-},
-{
-"first_name": "onni",
-"last_name": "laurila",
-"image": "https://randomuser.me/api/portraits/men/74.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eevi",
-"last_name": "niskanen",
-"image": "https://randomuser.me/api/portraits/women/72.jpg",
-"gender": "Female"
-},
-{
-"first_name": "julius",
-"last_name": "maijala",
-"image": "https://randomuser.me/api/portraits/men/8.jpg",
-"gender": "Male"
-},
-{
-"first_name": "sofia",
-"last_name": "tuomi",
-"image": "https://randomuser.me/api/portraits/women/1.jpg",
-"gender": "Female"
-},
-{
-"first_name": "oliver",
-"last_name": "jarvela",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "luukas",
-"last_name": "mikkola",
-"image": "https://randomuser.me/api/portraits/men/90.jpg",
-"gender": "Male"
-},
-{
-"first_name": "amanda",
-"last_name": "anttila",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ella",
-"last_name": "sakala",
-"image": "https://randomuser.me/api/portraits/women/79.jpg",
-"gender": "Female"
-},
-{
-"first_name": "siiri",
-"last_name": "kinnunen",
-"image": "https://randomuser.me/api/portraits/women/37.jpg",
-"gender": "Female"
-},
-{
-"first_name": "joona",
-"last_name": "korhonen",
-"image": "https://randomuser.me/api/portraits/men/87.jpg",
-"gender": "Male"
-},
-{
-"first_name": "topias",
-"last_name": "korpi",
-"image": "https://randomuser.me/api/portraits/men/75.jpg",
-"gender": "Male"
-},
-{
-"first_name": "mikael",
-"last_name": "remes",
-"image": "https://randomuser.me/api/portraits/men/89.jpg",
-"gender": "Male"
-},
-{
-"first_name": "veera",
-"last_name": "peltola",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emil",
-"last_name": "makela",
-"image": "https://randomuser.me/api/portraits/men/98.jpg",
-"gender": "Male"
-},
-{
-"first_name": "luukas",
-"last_name": "kujala",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemil",
-"last_name": "honkala",
-"image": "https://randomuser.me/api/portraits/men/85.jpg",
-"gender": "Male"
-},
-{
-"first_name": "peetu",
-"last_name": "kalm",
-"image": "https://randomuser.me/api/portraits/men/17.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemeli",
-"last_name": "lehtonen",
-"image": "https://randomuser.me/api/portraits/men/55.jpg",
-"gender": "Male"
-},
-{
-"first_name": "viivi",
-"last_name": "koistinen",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elli",
-"last_name": "savela",
-"image": "https://randomuser.me/api/portraits/women/77.jpg",
-"gender": "Female"
-},
-{
-"first_name": "venla",
-"last_name": "walli",
-"image": "https://randomuser.me/api/portraits/women/52.jpg",
-"gender": "Female"
-},
-{
-"first_name": "amanda",
-"last_name": "wuollet",
-"image": "https://randomuser.me/api/portraits/women/11.jpg",
-"gender": "Female"
-},
-{
-"first_name": "valtteri",
-"last_name": "hokkanen",
-"image": "https://randomuser.me/api/portraits/men/30.jpg",
-"gender": "Male"
-},
-{
-"first_name": "veera",
-"last_name": "maki",
-"image": "https://randomuser.me/api/portraits/women/34.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kerttu",
-"last_name": "maunu",
-"image": "https://randomuser.me/api/portraits/women/1.jpg",
-"gender": "Female"
-},
-{
-"first_name": "nella",
-"last_name": "hanka",
-"image": "https://randomuser.me/api/portraits/women/70.jpg",
-"gender": "Female"
-},
-{
-"first_name": "iiris",
-"last_name": "hakala",
-"image": "https://randomuser.me/api/portraits/women/33.jpg",
-"gender": "Female"
-},
-{
-"first_name": "viivi",
-"last_name": "ojala",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "iina",
-"last_name": "peura",
-"image": "https://randomuser.me/api/portraits/women/22.jpg",
-"gender": "Female"
-},
-{
-"first_name": "samuel",
-"last_name": "mattila",
-"image": "https://randomuser.me/api/portraits/men/88.jpg",
-"gender": "Male"
-},
-{
-"first_name": "julius",
-"last_name": "kumpula",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "nooa",
-"last_name": "haapala",
-"image": "https://randomuser.me/api/portraits/men/77.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elias",
-"last_name": "leppo",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "niklas",
-"last_name": "elo",
-"image": "https://randomuser.me/api/portraits/men/64.jpg",
-"gender": "Male"
-},
-{
-"first_name": "olivia",
-"last_name": "nurmi",
-"image": "https://randomuser.me/api/portraits/women/82.jpg",
-"gender": "Female"
-},
-{
-"first_name": "milja",
-"last_name": "lassila",
-"image": "https://randomuser.me/api/portraits/women/47.jpg",
-"gender": "Female"
-},
-{
-"first_name": "daniel",
-"last_name": "kalas",
-"image": "https://randomuser.me/api/portraits/men/53.jpg",
-"gender": "Male"
-},
-{
-"first_name": "enni",
-"last_name": "ramo",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "matilda",
-"last_name": "salmi",
-"image": "https://randomuser.me/api/portraits/women/84.jpg",
-"gender": "Female"
-},
-{
-"first_name": "valtteri",
-"last_name": "wirta",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "julius",
-"last_name": "maijala",
-"image": "https://randomuser.me/api/portraits/men/39.jpg",
-"gender": "Male"
-},
-{
-"first_name": "kerttu",
-"last_name": "peltola",
-"image": "https://randomuser.me/api/portraits/women/39.jpg",
-"gender": "Female"
-},
-{
-"first_name": "aada",
-"last_name": "kokko",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elsa",
-"last_name": "niska",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ella",
-"last_name": "kalm",
-"image": "https://randomuser.me/api/portraits/women/61.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lilja",
-"last_name": "heinonen",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "akseli",
-"last_name": "laakso",
-"image": "https://randomuser.me/api/portraits/men/64.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lotta",
-"last_name": "saarela",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "leo",
-"last_name": "polon",
-"image": "https://randomuser.me/api/portraits/men/5.jpg",
-"gender": "Male"
-},
-{
-"first_name": "aleksi",
-"last_name": "wuollet",
-"image": "https://randomuser.me/api/portraits/men/87.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemil",
-"last_name": "kalas",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "emmi",
-"last_name": "koistinen",
-"image": "https://randomuser.me/api/portraits/women/66.jpg",
-"gender": "Female"
-},
-{
-"first_name": "väinö",
-"last_name": "halla",
-"image": "https://randomuser.me/api/portraits/men/65.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemil",
-"last_name": "heikkila",
-"image": "https://randomuser.me/api/portraits/men/18.jpg",
-"gender": "Male"
-},
-{
-"first_name": "amanda",
-"last_name": "lakso",
-"image": "https://randomuser.me/api/portraits/women/29.jpg",
-"gender": "Female"
-},
-{
-"first_name": "vilho",
-"last_name": "kivela",
-"image": "https://randomuser.me/api/portraits/men/19.jpg",
-"gender": "Male"
-},
-{
-"first_name": "peppi",
-"last_name": "lehtinen",
-"image": "https://randomuser.me/api/portraits/women/80.jpg",
-"gender": "Female"
-},
-{
-"first_name": "onni",
-"last_name": "lehtinen",
-"image": "https://randomuser.me/api/portraits/men/0.jpg",
-"gender": "Male"
-},
-{
-"first_name": "onni",
-"last_name": "ahonen",
-"image": "https://randomuser.me/api/portraits/men/49.jpg",
-"gender": "Male"
-},
-{
-"first_name": "venla",
-"last_name": "ranta",
-"image": "https://randomuser.me/api/portraits/women/0.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ronja",
-"last_name": "korhonen",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emmi",
-"last_name": "niva",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "oskari",
-"last_name": "leppanen",
-"image": "https://randomuser.me/api/portraits/men/43.jpg",
-"gender": "Male"
-},
-{
-"first_name": "arttu",
-"last_name": "heinonen",
-"image": "https://randomuser.me/api/portraits/men/94.jpg",
-"gender": "Male"
-},
-{
-"first_name": "toivo",
-"last_name": "makela",
-"image": "https://randomuser.me/api/portraits/men/23.jpg",
-"gender": "Male"
-},
-{
-"first_name": "otto",
-"last_name": "leino",
-"image": "https://randomuser.me/api/portraits/men/51.jpg",
-"gender": "Male"
-},
-{
-"first_name": "milla",
-"last_name": "kokko",
-"image": "https://randomuser.me/api/portraits/women/66.jpg",
-"gender": "Female"
-},
-{
-"first_name": "konsta",
-"last_name": "lehto",
-"image": "https://randomuser.me/api/portraits/men/29.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eeli",
-"last_name": "heikkinen",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "matilda",
-"last_name": "tanner",
-"image": "https://randomuser.me/api/portraits/women/2.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elias",
-"last_name": "kivisto",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "akseli",
-"last_name": "wirta",
-"image": "https://randomuser.me/api/portraits/men/90.jpg",
-"gender": "Male"
-},
-{
-"first_name": "leevi",
-"last_name": "kallio",
-"image": "https://randomuser.me/api/portraits/men/89.jpg",
-"gender": "Male"
-},
-{
-"first_name": "emilia",
-"last_name": "pelto",
-"image": "https://randomuser.me/api/portraits/women/0.jpg",
-"gender": "Female"
-},
-{
-"first_name": "niilo",
-"last_name": "keranen",
-"image": "https://randomuser.me/api/portraits/men/29.jpg",
-"gender": "Male"
-},
-{
-"first_name": "mikael",
-"last_name": "wainio",
-"image": "https://randomuser.me/api/portraits/men/85.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elias",
-"last_name": "saksa",
-"image": "https://randomuser.me/api/portraits/men/53.jpg",
-"gender": "Male"
-},
-{
-"first_name": "aatu",
-"last_name": "erkkila",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "arttu",
-"last_name": "jarvela",
-"image": "https://randomuser.me/api/portraits/men/49.jpg",
-"gender": "Male"
-},
-{
-"first_name": "matilda",
-"last_name": "lassila",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "alisa",
-"last_name": "waara",
-"image": "https://randomuser.me/api/portraits/women/67.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emilia",
-"last_name": "saksa",
-"image": "https://randomuser.me/api/portraits/women/66.jpg",
-"gender": "Female"
-},
-{
-"first_name": "valtteri",
-"last_name": "tikkanen",
-"image": "https://randomuser.me/api/portraits/men/88.jpg",
-"gender": "Male"
-},
-{
-"first_name": "konsta",
-"last_name": "rantala",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "minttu",
-"last_name": "murto",
-"image": "https://randomuser.me/api/portraits/women/14.jpg",
-"gender": "Female"
-},
-{
-"first_name": "vilma",
-"last_name": "hatala",
-"image": "https://randomuser.me/api/portraits/women/60.jpg",
-"gender": "Female"
-},
-{
-"first_name": "anni",
-"last_name": "linna",
-"image": "https://randomuser.me/api/portraits/women/59.jpg",
-"gender": "Female"
-},
-{
-"first_name": "niklas",
-"last_name": "hautala",
-"image": "https://randomuser.me/api/portraits/men/7.jpg",
-"gender": "Male"
-},
-{
-"first_name": "niilo",
-"last_name": "lehtinen",
-"image": "https://randomuser.me/api/portraits/men/54.jpg",
-"gender": "Male"
-},
-{
-"first_name": "oona",
-"last_name": "saarinen",
-"image": "https://randomuser.me/api/portraits/women/71.jpg",
-"gender": "Female"
-},
-{
-"first_name": "constance",
-"last_name": "marie",
-"image": "https://randomuser.me/api/portraits/women/40.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charles",
-"last_name": "pierre",
-"image": "https://randomuser.me/api/portraits/men/96.jpg",
-"gender": "Male"
-},
-{
-"first_name": "bérénice",
-"last_name": "leclerc",
-"image": "https://randomuser.me/api/portraits/women/39.jpg",
-"gender": "Female"
-},
-{
-"first_name": "clémence",
-"last_name": "arnaud",
-"image": "https://randomuser.me/api/portraits/women/48.jpg",
-"gender": "Female"
-},
-{
-"first_name": "melvin",
-"last_name": "lemoine",
-"image": "https://randomuser.me/api/portraits/men/47.jpg",
-"gender": "Male"
-},
-{
-"first_name": "marceau",
-"last_name": "joly",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "garance",
-"last_name": "mathieu",
-"image": "https://randomuser.me/api/portraits/women/87.jpg",
-"gender": "Female"
-},
-{
-"first_name": "angèle",
-"last_name": "perrin",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "pauline",
-"last_name": "simon",
-"image": "https://randomuser.me/api/portraits/women/82.jpg",
-"gender": "Female"
-},
-{
-"first_name": "apolline",
-"last_name": "laurent",
-"image": "https://randomuser.me/api/portraits/women/27.jpg",
-"gender": "Female"
-},
-{
-"first_name": "luca",
-"last_name": "lefevre",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "bastien",
-"last_name": "roger",
-"image": "https://randomuser.me/api/portraits/men/73.jpg",
-"gender": "Male"
-},
-{
-"first_name": "marie",
-"last_name": "rodriguez",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tristan",
-"last_name": "renaud",
-"image": "https://randomuser.me/api/portraits/men/41.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eva",
-"last_name": "philippe",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "coline",
-"last_name": "dufour",
-"image": "https://randomuser.me/api/portraits/women/64.jpg",
-"gender": "Female"
-},
-{
-"first_name": "marilou",
-"last_name": "adam",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lia",
-"last_name": "renard",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "timothee",
-"last_name": "rolland",
-"image": "https://randomuser.me/api/portraits/men/75.jpg",
-"gender": "Male"
-},
-{
-"first_name": "hélèna",
-"last_name": "boyer",
-"image": "https://randomuser.me/api/portraits/women/8.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mélody",
-"last_name": "andre",
-"image": "https://randomuser.me/api/portraits/women/75.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jeanne",
-"last_name": "duval",
-"image": "https://randomuser.me/api/portraits/women/44.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elias",
-"last_name": "dupont",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "estelle",
-"last_name": "bernard",
-"image": "https://randomuser.me/api/portraits/women/23.jpg",
-"gender": "Female"
-},
-{
-"first_name": "roxane",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/women/14.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maëva",
-"last_name": "guerin",
-"image": "https://randomuser.me/api/portraits/women/44.jpg",
-"gender": "Female"
-},
-{
-"first_name": "liam",
-"last_name": "carpentier",
-"image": "https://randomuser.me/api/portraits/men/41.jpg",
-"gender": "Male"
-},
-{
-"first_name": "théo",
-"last_name": "gaillard",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "angelina",
-"last_name": "clement",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emma",
-"last_name": "bertrand",
-"image": "https://randomuser.me/api/portraits/women/86.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charles",
-"last_name": "rolland",
-"image": "https://randomuser.me/api/portraits/men/14.jpg",
-"gender": "Male"
-},
-{
-"first_name": "nolan",
-"last_name": "gautier",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "agathe",
-"last_name": "menard",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "gaëtan",
-"last_name": "leclerc",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "clarisse",
-"last_name": "lemaire",
-"image": "https://randomuser.me/api/portraits/women/21.jpg",
-"gender": "Female"
-},
-{
-"first_name": "samuel",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/men/16.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eden",
-"last_name": "fontai",
-"image": "https://randomuser.me/api/portraits/women/17.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maëva",
-"last_name": "pierre",
-"image": "https://randomuser.me/api/portraits/women/19.jpg",
-"gender": "Female"
-},
-{
-"first_name": "thomas",
-"last_name": "barbier",
-"image": "https://randomuser.me/api/portraits/men/31.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lily",
-"last_name": "lefebvre",
-"image": "https://randomuser.me/api/portraits/women/76.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lise",
-"last_name": "perez",
-"image": "https://randomuser.me/api/portraits/women/74.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mila",
-"last_name": "moulin",
-"image": "https://randomuser.me/api/portraits/women/43.jpg",
-"gender": "Female"
-},
-{
-"first_name": "dylan",
-"last_name": "picard",
-"image": "https://randomuser.me/api/portraits/men/37.jpg",
-"gender": "Male"
-},
-{
-"first_name": "amandine",
-"last_name": "rodriguez",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "diego",
-"last_name": "girard",
-"image": "https://randomuser.me/api/portraits/men/84.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elouan",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/men/94.jpg",
-"gender": "Male"
-},
-{
-"first_name": "apolline",
-"last_name": "fleury",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "coline",
-"last_name": "menard",
-"image": "https://randomuser.me/api/portraits/women/83.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maëly",
-"last_name": "le gall",
-"image": "https://randomuser.me/api/portraits/women/60.jpg",
-"gender": "Female"
-},
-{
-"first_name": "justin",
-"last_name": "robert",
-"image": "https://randomuser.me/api/portraits/men/20.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ryan",
-"last_name": "faure",
-"image": "https://randomuser.me/api/portraits/men/16.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ninon",
-"last_name": "brunet",
-"image": "https://randomuser.me/api/portraits/women/68.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tessa",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/women/54.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ryan",
-"last_name": "bonnet",
-"image": "https://randomuser.me/api/portraits/men/28.jpg",
-"gender": "Male"
-},
-{
-"first_name": "aurélien",
-"last_name": "andre",
-"image": "https://randomuser.me/api/portraits/men/29.jpg",
-"gender": "Male"
-},
-{
-"first_name": "clément",
-"last_name": "dumas",
-"image": "https://randomuser.me/api/portraits/men/10.jpg",
-"gender": "Male"
-},
-{
-"first_name": "alexis",
-"last_name": "fournier",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "valentin",
-"last_name": "lecomte",
-"image": "https://randomuser.me/api/portraits/men/44.jpg",
-"gender": "Male"
-},
-{
-"first_name": "florian",
-"last_name": "olivier",
-"image": "https://randomuser.me/api/portraits/men/36.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ewen",
-"last_name": "lefebvre",
-"image": "https://randomuser.me/api/portraits/men/32.jpg",
-"gender": "Male"
-},
-{
-"first_name": "titouan",
-"last_name": "charles",
-"image": "https://randomuser.me/api/portraits/men/59.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lila",
-"last_name": "aubert",
-"image": "https://randomuser.me/api/portraits/women/6.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charline",
-"last_name": "caron",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "soren",
-"last_name": "le gall",
-"image": "https://randomuser.me/api/portraits/men/77.jpg",
-"gender": "Male"
-},
-{
-"first_name": "fanny",
-"last_name": "louis",
-"image": "https://randomuser.me/api/portraits/women/90.jpg",
-"gender": "Female"
-},
-{
-"first_name": "julie",
-"last_name": "adam",
-"image": "https://randomuser.me/api/portraits/women/34.jpg",
-"gender": "Female"
-},
-{
-"first_name": "louka",
-"last_name": "boyer",
-"image": "https://randomuser.me/api/portraits/men/98.jpg",
-"gender": "Male"
-}
-]
diff --git a/erpnext/demo/data/room.json b/erpnext/demo/data/room.json
deleted file mode 100644
index 82f0868..0000000
--- a/erpnext/demo/data/room.json
+++ /dev/null
@@ -1,122 +0,0 @@
-[
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 1",
- "room_number": "101",
- "seating_capacity": 80
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 2",
- "room_number": "102",
- "seating_capacity": 80
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 3",
- "room_number": "103",
- "seating_capacity": 80
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 4",
- "room_number": "104",
- "seating_capacity": 80
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 4",
- "room_number": "104",
- "seating_capacity": 80
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 5",
- "room_number": "201",
- "seating_capacity": 120
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 6",
- "room_number": "202",
- "seating_capacity": 120
- },
- {
- "doctype": "Room",
- "room_name": "Lecture Hall 7",
- "room_number": "203",
- "seating_capacity": 120
- },
- {
- "doctype": "Room",
- "room_name": "Computer Lab 1",
- "room_number": "301",
- "seating_capacity": 40
- },
- {
- "doctype": "Room",
- "room_name": "Computer Lab 2",
- "room_number": "302",
- "seating_capacity": 60
- },
- {
- "doctype": "Room",
- "room_name": "Seminar Hall 1",
- "room_number": "303",
- "seating_capacity": 240
- },
- {
- "doctype": "Room",
- "room_name": "Auditorium",
- "room_number": "400",
- "seating_capacity": 450
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 1",
- "room_number": "560",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 2",
- "room_number": "561",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 2",
- "room_number": "562",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 3",
- "room_number": "563",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 4",
- "room_number": "564",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 5",
- "room_number": "565",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 6",
- "room_number": "566",
- "seating_capacity": 70
- },
- {
- "doctype": "Room",
- "room_name": "Exam hall 7",
- "room_number": "567",
- "seating_capacity": 70
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/student_batch_name.json b/erpnext/demo/data/student_batch_name.json
deleted file mode 100644
index ef3f18d..0000000
--- a/erpnext/demo/data/student_batch_name.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "doctype": "Student Batch Name",
- "batch_name": "Section-A"
- },
- {
- "doctype": "Student Batch Name",
- "batch_name": "Section-B"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/user.json b/erpnext/demo/data/user.json
deleted file mode 100644
index 9ee5e78..0000000
--- a/erpnext/demo/data/user.json
+++ /dev/null
@@ -1,112 +0,0 @@
-[
- {
- "email": "test_demo@erpnext.com",
- "first_name": "Test",
- "last_name": "User"
- },
- {
- "email": "DianaPrince@example.com",
- "first_name": "Diana",
- "last_name": "Prince"
- },
- {
- "email": "ZatannaZatara@example.com",
- "first_name": "Zatanna",
- "last_name": "Zatara"
- },
- {
- "email": "HollyGranger@example.com",
- "first_name": "Holly",
- "last_name": "Granger"
- },
- {
- "email": "NeptuniaAquaria@example.com",
- "first_name": "Neptunia",
- "last_name": "Aquaria"
- },
- {
- "email": "ArthurCurry@example.com",
- "first_name": "Arthur",
- "last_name": "Curry"
- },
- {
- "email": "ThaliaAlGhul@example.com",
- "first_name": "Thalia",
- "last_name": "Al Ghul"
- },
- {
- "email": "MaxwellLord@example.com",
- "first_name": "Maxwell",
- "last_name": "Lord"
- },
- {
- "email": "GraceChoi@example.com",
- "first_name": "Grace",
- "last_name": "Choi"
- },
- {
- "email": "VandalSavage@example.com",
- "first_name": "Vandal",
- "last_name": "Savage"
- },
- {
- "email": "CaitlinSnow@example.com",
- "first_name": "Caitlin",
- "last_name": "Snow"
- },
- {
- "email": "RipHunter@example.com",
- "first_name": "Rip",
- "last_name": "Hunter"
- },
- {
- "email": "NicholasFury@example.com",
- "first_name": "Nicholas",
- "last_name": "Fury"
- },
- {
- "email": "PeterParker@example.com",
- "first_name": "Peter",
- "last_name": "Parker"
- },
- {
- "email": "JohnConstantine@example.com",
- "first_name": "John",
- "last_name": "Constantine"
- },
- {
- "email": "HalJordan@example.com",
- "first_name": "Hal",
- "last_name": "Jordan"
- },
- {
- "email": "VictorStone@example.com",
- "first_name": "Victor",
- "last_name": "Stone"
- },
- {
- "email": "BruceWayne@example.com",
- "first_name": "Bruce",
- "last_name": "Wayne"
- },
- {
- "email": "ClarkKent@example.com",
- "first_name": "Clark",
- "last_name": "Kent"
- },
- {
- "email": "BarryAllen@example.com",
- "first_name": "Barry",
- "last_name": "Allen"
- },
- {
- "email": "KaraZorEl@example.com",
- "first_name": "Kara",
- "last_name": "Zor El"
- },
- {
- "email": "demo@erpnext.com",
- "first_name": "Demo",
- "last_name": "User"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/demo.py b/erpnext/demo/demo.py
deleted file mode 100644
index 4a18a99..0000000
--- a/erpnext/demo/demo.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import sys
-
-import frappe
-import frappe.utils
-
-import erpnext
-from erpnext.demo.setup import education, manufacture, retail, setup_data
-from erpnext.demo.user import accounts
-from erpnext.demo.user import education as edu
-from erpnext.demo.user import fixed_asset, hr, manufacturing, projects, purchase, sales, stock
-
-"""
-Make a demo
-
-1. Start with a fresh account
-
-bench --site demo.erpnext.dev reinstall
-
-2. Install Demo
-
-bench --site demo.erpnext.dev execute erpnext.demo.demo.make
-
-3. If Demo breaks, to continue
-
-bench --site demo.erpnext.dev execute erpnext.demo.demo.simulate
-
-"""
-
-def make(domain='Manufacturing', days=100):
- frappe.flags.domain = domain
- frappe.flags.mute_emails = True
- setup_data.setup(domain)
- if domain== 'Manufacturing':
- manufacture.setup_data()
- elif domain == "Retail":
- retail.setup_data()
- elif domain== 'Education':
- education.setup_data()
-
- site = frappe.local.site
- frappe.destroy()
- frappe.init(site)
- frappe.connect()
-
- simulate(domain, days)
-
-def simulate(domain='Manufacturing', days=100):
- runs_for = frappe.flags.runs_for or days
- frappe.flags.company = erpnext.get_default_company()
- frappe.flags.mute_emails = True
-
- if not frappe.flags.start_date:
- # start date = 100 days back
- frappe.flags.start_date = frappe.utils.add_days(frappe.utils.nowdate(),
- -1 * runs_for)
-
- current_date = frappe.utils.getdate(frappe.flags.start_date)
-
- # continue?
- demo_last_date = frappe.db.get_global('demo_last_date')
- if demo_last_date:
- current_date = frappe.utils.add_days(frappe.utils.getdate(demo_last_date), 1)
-
- # run till today
- if not runs_for:
- runs_for = frappe.utils.date_diff(frappe.utils.nowdate(), current_date)
- # runs_for = 100
-
- fixed_asset.work()
- for i in range(runs_for):
- sys.stdout.write("\rSimulating {0}: Day {1}".format(
- current_date.strftime("%Y-%m-%d"), i))
- sys.stdout.flush()
- frappe.flags.current_date = current_date
- if current_date.weekday() in (5, 6):
- current_date = frappe.utils.add_days(current_date, 1)
- continue
- try:
- hr.work()
- purchase.work()
- stock.work()
- accounts.work()
- projects.run_projects(current_date)
- sales.work(domain)
- # run_messages()
-
- if domain=='Manufacturing':
- manufacturing.work()
- elif domain=='Education':
- edu.work()
-
- except Exception:
- frappe.db.set_global('demo_last_date', current_date)
- raise
- finally:
- current_date = frappe.utils.add_days(current_date, 1)
- frappe.db.commit()
diff --git a/erpnext/demo/domains.py b/erpnext/demo/domains.py
deleted file mode 100644
index 346787e..0000000
--- a/erpnext/demo/domains.py
+++ /dev/null
@@ -1,20 +0,0 @@
-data = {
- 'Manufacturing': {
- 'company_name': 'Wind Power LLC'
- },
- 'Retail': {
- 'company_name': 'Mobile Next',
- },
- 'Distribution': {
- 'company_name': 'Soltice Hardware',
- },
- 'Services': {
- 'company_name': 'Acme Consulting'
- },
- 'Education': {
- 'company_name': 'Whitmore College'
- },
- 'Non Profit': {
- 'company_name': 'Erpnext Foundation'
- }
-}
diff --git a/erpnext/demo/setup/__init__.py b/erpnext/demo/setup/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/demo/setup/__init__.py
+++ /dev/null
diff --git a/erpnext/demo/setup/education.py b/erpnext/demo/setup/education.py
deleted file mode 100644
index eb833f4..0000000
--- a/erpnext/demo/setup/education.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import json
-import random
-from datetime import datetime
-
-import frappe
-from frappe.utils.make_random import get_random
-
-from erpnext.demo.setup.setup_data import import_json
-
-
-def setup_data():
- frappe.flags.mute_emails = True
- make_masters()
- setup_item()
- make_student_applicants()
- make_student_group()
- make_fees_category()
- make_fees_structure()
- make_assessment_groups()
- frappe.db.commit()
- frappe.clear_cache()
-
-def make_masters():
- import_json("Room")
- import_json("Department")
- import_json("Instructor")
- import_json("Course")
- import_json("Program")
- import_json("Student Batch Name")
- import_json("Assessment Criteria")
- import_json("Grading Scale")
- frappe.db.commit()
-
-def setup_item():
- items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item_education.json')).read())
- for i in items:
- item = frappe.new_doc('Item')
- item.update(i)
- item.min_order_qty = random.randint(10, 30)
- item.item_defaults[0].default_warehouse = frappe.get_all('Warehouse',
- filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)[0].name
- item.insert()
-
-def make_student_applicants():
- blood_group = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]
- male_names = []
- female_names = []
-
- file_path = get_json_path("Random Student Data")
- with open(file_path, "r") as open_file:
- random_student_data = json.loads(open_file.read())
- count = 1
-
- for d in random_student_data:
- if d.get('gender') == "Male":
- male_names.append(d.get('first_name').title())
-
- if d.get('gender') == "Female":
- female_names.append(d.get('first_name').title())
-
- for idx, d in enumerate(random_student_data):
- student_applicant = frappe.new_doc("Student Applicant")
- student_applicant.first_name = d.get('first_name').title()
- student_applicant.last_name = d.get('last_name').title()
- student_applicant.image = d.get('image')
- student_applicant.gender = d.get('gender')
- student_applicant.program = get_random("Program")
- student_applicant.blood_group = random.choice(blood_group)
- year = random.randint(1990, 1998)
- month = random.randint(1, 12)
- day = random.randint(1, 28)
- student_applicant.date_of_birth = datetime(year, month, day)
- student_applicant.mother_name = random.choice(female_names) + " " + d.get('last_name').title()
- student_applicant.father_name = random.choice(male_names) + " " + d.get('last_name').title()
- if student_applicant.gender == "Male":
- student_applicant.middle_name = random.choice(male_names)
- else:
- student_applicant.middle_name = random.choice(female_names)
- student_applicant.student_email_id = d.get('first_name') + "_" + \
- student_applicant.middle_name + "_" + d.get('last_name') + "@example.com"
- if count <5:
- student_applicant.insert()
- frappe.db.commit()
- else:
- student_applicant.submit()
- frappe.db.commit()
- count+=1
-
-def make_student_group():
- for term in frappe.db.get_list("Academic Term"):
- for program in frappe.db.get_list("Program"):
- sg_tool = frappe.new_doc("Student Group Creation Tool")
- sg_tool.academic_year = "2017-18"
- sg_tool.academic_term = term.name
- sg_tool.program = program.name
- for d in sg_tool.get_courses():
- d = frappe._dict(d)
- student_group = frappe.new_doc("Student Group")
- student_group.student_group_name = d.student_group_name
- student_group.group_based_on = d.group_based_on
- student_group.program = program.name
- student_group.course = d.course
- student_group.batch = d.batch
- student_group.academic_term = term.name
- student_group.academic_year = "2017-18"
- student_group.save()
- frappe.db.commit()
-
-def make_fees_category():
- fee_type = ["Tuition Fee", "Hostel Fee", "Logistics Fee",
- "Medical Fee", "Mess Fee", "Security Deposit"]
-
- fee_desc = {"Tuition Fee" : "Curricular activities which includes books, notebooks and faculty charges" ,
- "Hostel Fee" : "Stay of students in institute premises",
- "Logistics Fee" : "Lodging boarding of the students" ,
- "Medical Fee" : "Medical welfare of the students",
- "Mess Fee" : "Food and beverages for your ward",
- "Security Deposit" : "In case your child is found to have damaged institutes property"
- }
-
- for i in fee_type:
- fee_category = frappe.new_doc("Fee Category")
- fee_category.category_name = i
- fee_category.description = fee_desc[i]
- fee_category.insert()
- frappe.db.commit()
-
-def make_fees_structure():
- for d in frappe.db.get_list("Program"):
- program = frappe.get_doc("Program", d.name)
- for academic_term in ["2017-18 (Semester 1)", "2017-18 (Semester 2)", "2017-18 (Semester 3)"]:
- fee_structure = frappe.new_doc("Fee Structure")
- fee_structure.program = d.name
- fee_structure.academic_term = random.choice(frappe.db.get_list("Academic Term")).name
- for j in range(1,4):
- temp = {"fees_category": random.choice(frappe.db.get_list("Fee Category")).name , "amount" : random.randint(500,1000)}
- fee_structure.append("components", temp)
- fee_structure.insert()
- program.append("fees", {"academic_term": academic_term, "fee_structure": fee_structure.name, "amount": fee_structure.total_amount})
- program.save()
- frappe.db.commit()
-
-def make_assessment_groups():
- for year in frappe.db.get_list("Academic Year"):
- ag = frappe.new_doc('Assessment Group')
- ag.assessment_group_name = year.name
- ag.parent_assessment_group = "All Assessment Groups"
- ag.is_group = 1
- ag.insert()
- for term in frappe.db.get_list("Academic Term", filters = {"academic_year": year.name}):
- ag1 = frappe.new_doc('Assessment Group')
- ag1.assessment_group_name = term.name
- ag1.parent_assessment_group = ag.name
- ag1.is_group = 1
- ag1.insert()
- for assessment_group in ['Term I', 'Term II']:
- ag2 = frappe.new_doc('Assessment Group')
- ag2.assessment_group_name = ag1.name + " " + assessment_group
- ag2.parent_assessment_group = ag1.name
- ag2.insert()
- frappe.db.commit()
-
-
-def get_json_path(doctype):
- return frappe.get_app_path('erpnext', 'demo', 'data', frappe.scrub(doctype) + '.json')
-
-def weighted_choice(weights):
- totals = []
- running_total = 0
-
- for w in weights:
- running_total += w
- totals.append(running_total)
-
- rnd = random.random() * running_total
- for i, total in enumerate(totals):
- if rnd < total:
- return i
diff --git a/erpnext/demo/setup/manufacture.py b/erpnext/demo/setup/manufacture.py
deleted file mode 100644
index fe1a1fb..0000000
--- a/erpnext/demo/setup/manufacture.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import json
-import random
-
-import frappe
-from frappe.utils import add_days, nowdate
-
-from erpnext.demo.domains import data
-from erpnext.demo.setup.setup_data import import_json
-
-
-def setup_data():
- import_json("Location")
- import_json("Asset Category")
- setup_item()
- setup_workstation()
- setup_asset()
- import_json('Operation')
- setup_item_price()
- show_item_groups_in_website()
- import_json('BOM', submit=True)
- frappe.db.commit()
- frappe.clear_cache()
-
-def setup_workstation():
- workstations = [u'Drilling Machine 1', u'Lathe 1', u'Assembly Station 1', u'Assembly Station 2', u'Packing and Testing Station']
- for w in workstations:
- frappe.get_doc({
- "doctype": "Workstation",
- "workstation_name": w,
- "holiday_list": frappe.get_all("Holiday List")[0].name,
- "hour_rate_consumable": int(random.random() * 20),
- "hour_rate_electricity": int(random.random() * 10),
- "hour_rate_labour": int(random.random() * 40),
- "hour_rate_rent": int(random.random() * 10),
- "working_hours": [
- {
- "enabled": 1,
- "start_time": "8:00:00",
- "end_time": "15:00:00"
- }
- ]
- }).insert()
-
-def show_item_groups_in_website():
- """set show_in_website=1 for Item Groups"""
- products = frappe.get_doc("Item Group", "Products")
- products.show_in_website = 1
- products.route = 'products'
- products.save()
-
-def setup_asset():
- assets = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'asset.json')).read())
- for d in assets:
- asset = frappe.new_doc('Asset')
- asset.update(d)
- asset.purchase_date = add_days(nowdate(), -random.randint(20, 1500))
- asset.next_depreciation_date = add_days(asset.purchase_date, 30)
- asset.warehouse = "Stores - WPL"
- asset.set_missing_values()
- asset.make_depreciation_schedule()
- asset.flags.ignore_validate = True
- asset.flags.ignore_mandatory = True
- asset.save()
- asset.submit()
-
-def setup_item():
- items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item.json')).read())
- for i in items:
- item = frappe.new_doc('Item')
- item.update(i)
- if hasattr(item, 'item_defaults') and item.item_defaults[0].default_warehouse:
- item.item_defaults[0].company = data.get("Manufacturing").get('company_name')
- warehouse = frappe.get_all('Warehouse', filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)
- if warehouse:
- item.item_defaults[0].default_warehouse = warehouse[0].name
- item.insert()
-
-def setup_product_bundle():
- frappe.get_doc({
- 'doctype': 'Product Bundle',
- 'new_item_code': 'Wind Mill A Series with Spare Bearing',
- 'items': [
- {'item_code': 'Wind Mill A Series', 'qty': 1},
- {'item_code': 'Bearing Collar', 'qty': 1},
- {'item_code': 'Bearing Assembly', 'qty': 1},
- ]
- }).insert()
-
-def setup_item_price():
- frappe.db.sql("delete from `tabItem Price`")
-
- standard_selling = {
- "Base Bearing Plate": 28,
- "Base Plate": 21,
- "Bearing Assembly": 300,
- "Bearing Block": 14,
- "Bearing Collar": 103.6,
- "Bearing Pipe": 63,
- "Blade Rib": 46.2,
- "Disc Collars": 42,
- "External Disc": 56,
- "Internal Disc": 70,
- "Shaft": 340,
- "Stand": 400,
- "Upper Bearing Plate": 300,
- "Wind Mill A Series": 320,
- "Wind Mill A Series with Spare Bearing": 750,
- "Wind MIll C Series": 400,
- "Wind Turbine": 400,
- "Wing Sheet": 30.8
- }
-
- standard_buying = {
- "Base Bearing Plate": 20,
- "Base Plate": 28,
- "Base Plate Un Painted": 16,
- "Bearing Block": 13,
- "Bearing Collar": 96.4,
- "Bearing Pipe": 55,
- "Blade Rib": 38,
- "Disc Collars": 34,
- "External Disc": 50,
- "Internal Disc": 60,
- "Shaft": 250,
- "Stand": 300,
- "Upper Bearing Plate": 200,
- "Wing Sheet": 25
- }
-
- for price_list in ("standard_buying", "standard_selling"):
- for item, rate in locals().get(price_list).items():
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": price_list.replace("_", " ").title(),
- "item_code": item,
- "selling": 1 if price_list=="standard_selling" else 0,
- "buying": 1 if price_list=="standard_buying" else 0,
- "price_list_rate": rate,
- "currency": "USD"
- }).insert()
diff --git a/erpnext/demo/setup/retail.py b/erpnext/demo/setup/retail.py
deleted file mode 100644
index 0469264..0000000
--- a/erpnext/demo/setup/retail.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import json
-
-import frappe
-
-from erpnext.demo.domains import data
-
-
-def setup_data():
- setup_item()
- setup_item_price()
- frappe.db.commit()
- frappe.clear_cache()
-
-def setup_item():
- items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item.json')).read())
- for i in items:
- if not i.get("domain") == "Retail": continue
- item = frappe.new_doc('Item')
- item.update(i)
- if hasattr(item, 'item_defaults') and item.item_defaults[0].default_warehouse:
- item.item_defaults[0].company = data.get("Retail").get('company_name')
- warehouse = frappe.get_all('Warehouse', filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)
- if warehouse:
- item.item_defaults[0].default_warehouse = warehouse[0].name
- item.insert()
-
-def setup_item_price():
- frappe.db.sql("delete from `tabItem Price`")
-
- standard_selling = {
- "OnePlus 6": 579,
- "OnePlus 6T": 600,
- "Xiaomi Poco F1": 300,
- "Iphone XS": 999,
- "Samsung Galaxy S9": 720,
- "Sony Bluetooth Headphone": 99,
- "Xiaomi Phone Repair": 10,
- "Samsung Phone Repair": 20,
- "OnePlus Phone Repair": 15,
- "Apple Phone Repair": 30,
- }
-
- standard_buying = {
- "OnePlus 6": 300,
- "OnePlus 6T": 350,
- "Xiaomi Poco F1": 200,
- "Iphone XS": 600,
- "Samsung Galaxy S9": 500,
- "Sony Bluetooth Headphone": 69
- }
-
- for price_list in ("standard_buying", "standard_selling"):
- for item, rate in locals().get(price_list).items():
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": price_list.replace("_", " ").title(),
- "item_code": item,
- "selling": 1 if price_list=="standard_selling" else 0,
- "buying": 1 if price_list=="standard_buying" else 0,
- "price_list_rate": rate,
- "currency": "USD"
- }).insert()
diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py
deleted file mode 100644
index 7137c6e..0000000
--- a/erpnext/demo/setup/setup_data.py
+++ /dev/null
@@ -1,447 +0,0 @@
-import json
-import random
-
-import frappe
-from frappe import _
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from frappe.utils import cstr, flt, now_datetime, random_string
-from frappe.utils.make_random import add_random_children, get_random
-from frappe.utils.nestedset import get_root_of
-
-import erpnext
-from erpnext.demo.domains import data
-
-
-def setup(domain):
- frappe.flags.in_demo = 1
- complete_setup(domain)
- setup_demo_page()
- setup_fiscal_year()
- setup_holiday_list()
- setup_user()
- setup_employee()
- setup_user_roles(domain)
- setup_role_permissions()
- setup_custom_field_for_domain()
-
- employees = frappe.get_all('Employee', fields=['name', 'date_of_joining'])
-
- # monthly salary
- setup_salary_structure(employees[:5], 0)
-
- # based on timesheet
- setup_salary_structure(employees[5:], 1)
-
- setup_leave_allocation()
- setup_customer()
- setup_supplier()
- setup_warehouse()
- import_json('Address')
- import_json('Contact')
- import_json('Lead')
- setup_currency_exchange()
- #setup_mode_of_payment()
- setup_account_to_expense_type()
- setup_budget()
- setup_pos_profile()
-
- frappe.db.commit()
- frappe.clear_cache()
-
-def complete_setup(domain='Manufacturing'):
- print("Complete Setup...")
- from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
-
- if not frappe.get_all('Company', limit=1):
- setup_complete({
- "full_name": "Test User",
- "email": "test_demo@erpnext.com",
- "company_tagline": 'Awesome Products and Services',
- "password": "demo",
- "fy_start_date": "2015-01-01",
- "fy_end_date": "2015-12-31",
- "bank_account": "National Bank",
- "domains": [domain],
- "company_name": data.get(domain).get('company_name'),
- "chart_of_accounts": "Standard",
- "company_abbr": ''.join([d[0] for d in data.get(domain).get('company_name').split()]).upper(),
- "currency": 'USD',
- "timezone": 'America/New_York',
- "country": 'United States',
- "language": "english"
- })
-
- company = erpnext.get_default_company()
-
- if company:
- company_doc = frappe.get_doc("Company", company)
- company_doc.db_set('default_payroll_payable_account',
- frappe.db.get_value('Account', dict(account_name='Payroll Payable')))
-
-def setup_demo_page():
- # home page should always be "start"
- website_settings = frappe.get_doc("Website Settings", "Website Settings")
- website_settings.home_page = "demo"
- website_settings.save()
-
-def setup_fiscal_year():
- fiscal_year = None
- for year in range(2010, now_datetime().year + 1, 1):
- try:
- fiscal_year = frappe.get_doc({
- "doctype": "Fiscal Year",
- "year": cstr(year),
- "year_start_date": "{0}-01-01".format(year),
- "year_end_date": "{0}-12-31".format(year)
- }).insert()
- except frappe.DuplicateEntryError:
- pass
-
- # set the last fiscal year (current year) as default
- if fiscal_year:
- fiscal_year.set_as_default()
-
-def setup_holiday_list():
- """Setup Holiday List for the current year"""
- year = now_datetime().year
- holiday_list = frappe.get_doc({
- "doctype": "Holiday List",
- "holiday_list_name": str(year),
- "from_date": "{0}-01-01".format(year),
- "to_date": "{0}-12-31".format(year),
- })
- holiday_list.insert()
- holiday_list.weekly_off = "Saturday"
- holiday_list.get_weekly_off_dates()
- holiday_list.weekly_off = "Sunday"
- holiday_list.get_weekly_off_dates()
- holiday_list.save()
-
- frappe.set_value("Company", erpnext.get_default_company(), "default_holiday_list", holiday_list.name)
-
-
-def setup_user():
- frappe.db.sql('delete from tabUser where name not in ("Guest", "Administrator")')
- for u in json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'user.json')).read()):
- user = frappe.new_doc("User")
- user.update(u)
- user.flags.no_welcome_mail = True
- user.new_password = 'Demo1234567!!!'
- user.insert()
-
-def setup_employee():
- frappe.db.set_value("HR Settings", None, "emp_created_by", "Naming Series")
- frappe.db.commit()
-
- for d in frappe.get_all('Salary Component'):
- salary_component = frappe.get_doc('Salary Component', d.name)
- salary_component.append('accounts', dict(
- company=erpnext.get_default_company(),
- account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
- ))
- salary_component.save()
-
- import_json('Employee')
- holiday_list = frappe.db.get_value("Holiday List", {"holiday_list_name": str(now_datetime().year)}, 'name')
- frappe.db.sql('''update tabEmployee set holiday_list={0}'''.format(holiday_list))
-
-def setup_salary_structure(employees, salary_slip_based_on_timesheet=0):
- ss = frappe.new_doc('Salary Structure')
- ss.name = "Sample Salary Structure - " + random_string(5)
- ss.salary_slip_based_on_timesheet = salary_slip_based_on_timesheet
-
- if salary_slip_based_on_timesheet:
- ss.salary_component = 'Basic'
- ss.hour_rate = flt(random.random() * 10, 2)
- else:
- ss.payroll_frequency = 'Monthly'
-
- ss.payment_account = frappe.get_value('Account',
- {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
-
- ss.append('earnings', {
- 'salary_component': 'Basic',
- "abbr":'B',
- 'formula': 'base*.2',
- 'amount_based_on_formula': 1,
- "idx": 1
- })
- ss.append('deductions', {
- 'salary_component': 'Income Tax',
- "abbr":'IT',
- 'condition': 'base > 10000',
- 'formula': 'base*.1',
- "idx": 1
- })
- ss.insert()
- ss.submit()
-
- for e in employees:
- sa = frappe.new_doc("Salary Structure Assignment")
- sa.employee = e.name
- sa.salary_structure = ss.name
- sa.from_date = "2015-01-01"
- sa.base = random.random() * 10000
- sa.insert()
- sa.submit()
-
- return ss
-
-def setup_user_roles(domain):
- user = frappe.get_doc('User', 'demo@erpnext.com')
- user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager',
- 'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User',
- 'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager',
- 'Support Team')
-
- if domain == "Education":
- user.add_roles('Academics User')
-
- if not frappe.db.get_global('demo_hr_user'):
- user = frappe.get_doc('User', 'CaitlinSnow@example.com')
- user.add_roles('HR User', 'HR Manager', 'Accounts User')
- frappe.db.set_global('demo_hr_user', user.name)
- update_employee_department(user.name, 'Human Resources')
- for d in frappe.get_all('User Permission', filters={"user": "CaitlinSnow@example.com"}):
- frappe.delete_doc('User Permission', d.name)
-
- if not frappe.db.get_global('demo_sales_user_1'):
- user = frappe.get_doc('User', 'VandalSavage@example.com')
- user.add_roles('Sales User')
- update_employee_department(user.name, 'Sales')
- frappe.db.set_global('demo_sales_user_1', user.name)
-
- if not frappe.db.get_global('demo_sales_user_2'):
- user = frappe.get_doc('User', 'GraceChoi@example.com')
- user.add_roles('Sales User', 'Sales Manager', 'Accounts User')
- update_employee_department(user.name, 'Sales')
- frappe.db.set_global('demo_sales_user_2', user.name)
-
- if not frappe.db.get_global('demo_purchase_user'):
- user = frappe.get_doc('User', 'MaxwellLord@example.com')
- user.add_roles('Purchase User', 'Purchase Manager', 'Accounts User', 'Stock User')
- update_employee_department(user.name, 'Purchase')
- frappe.db.set_global('demo_purchase_user', user.name)
-
- if not frappe.db.get_global('demo_manufacturing_user'):
- user = frappe.get_doc('User', 'NeptuniaAquaria@example.com')
- user.add_roles('Manufacturing User', 'Stock Manager', 'Stock User', 'Purchase User', 'Accounts User')
- update_employee_department(user.name, 'Production')
- frappe.db.set_global('demo_manufacturing_user', user.name)
-
- if not frappe.db.get_global('demo_stock_user'):
- user = frappe.get_doc('User', 'HollyGranger@example.com')
- user.add_roles('Manufacturing User', 'Stock User', 'Purchase User', 'Accounts User')
- update_employee_department(user.name, 'Production')
- frappe.db.set_global('demo_stock_user', user.name)
-
- if not frappe.db.get_global('demo_accounts_user'):
- user = frappe.get_doc('User', 'BarryAllen@example.com')
- user.add_roles('Accounts User', 'Accounts Manager', 'Sales User', 'Purchase User')
- update_employee_department(user.name, 'Accounts')
- frappe.db.set_global('demo_accounts_user', user.name)
-
- if not frappe.db.get_global('demo_projects_user'):
- user = frappe.get_doc('User', 'PeterParker@example.com')
- user.add_roles('HR User', 'Projects User')
- update_employee_department(user.name, 'Management')
- frappe.db.set_global('demo_projects_user', user.name)
-
- if domain == "Education":
- if not frappe.db.get_global('demo_education_user'):
- user = frappe.get_doc('User', 'ArthurCurry@example.com')
- user.add_roles('Academics User')
- update_employee_department(user.name, 'Management')
- frappe.db.set_global('demo_education_user', user.name)
-
- #Add Expense Approver
- user = frappe.get_doc('User', 'ClarkKent@example.com')
- user.add_roles('Expense Approver')
-
-def setup_leave_allocation():
- year = now_datetime().year
- for employee in frappe.get_all('Employee', fields=['name']):
- leave_types = frappe.get_all("Leave Type", fields=['name', 'max_continuous_days_allowed'])
- for leave_type in leave_types:
- if not leave_type.max_continuous_days_allowed:
- leave_type.max_continuous_days_allowed = 10
-
- leave_allocation = frappe.get_doc({
- "doctype": "Leave Allocation",
- "employee": employee.name,
- "from_date": "{0}-01-01".format(year),
- "to_date": "{0}-12-31".format(year),
- "leave_type": leave_type.name,
- "new_leaves_allocated": random.randint(1, int(leave_type.max_continuous_days_allowed))
- })
- leave_allocation.insert()
- leave_allocation.submit()
- frappe.db.commit()
-
-def setup_customer():
- customers = [u'Asian Junction', u'Life Plan Counselling', u'Two Pesos', u'Mr Fables', u'Intelacard', u'Big D Supermarkets', u'Adaptas', u'Nelson Brothers', u'Landskip Yard Care', u'Buttrey Food & Drug', u'Fayva', u'Asian Fusion', u'Crafts Canada', u'Consumers and Consumers Express', u'Netobill', u'Choices', u'Chi-Chis', u'Red Food', u'Endicott Shoes', u'Hind Enterprises']
- for c in customers:
- frappe.get_doc({
- "doctype": "Customer",
- "customer_name": c,
- "customer_group": "Commercial",
- "customer_type": random.choice(["Company", "Individual"]),
- "territory": "Rest Of The World"
- }).insert()
-
-def setup_supplier():
- suppliers = [u'Helios Air', u'Ks Merchandise', u'HomeBase', u'Scott Ties', u'Reliable Investments', u'Nan Duskin', u'Rainbow Records', u'New World Realty', u'Asiatic Solutions', u'Eagle Hardware', u'Modern Electricals']
- for s in suppliers:
- frappe.get_doc({
- "doctype": "Supplier",
- "supplier_name": s,
- "supplier_group": random.choice(["Services", "Raw Material"]),
- }).insert()
-
-def setup_warehouse():
- w = frappe.new_doc('Warehouse')
- w.warehouse_name = 'Supplier'
- w.insert()
-
-def setup_currency_exchange():
- frappe.get_doc({
- 'doctype': 'Currency Exchange',
- 'from_currency': 'EUR',
- 'to_currency': 'USD',
- 'exchange_rate': 1.13
- }).insert()
-
- frappe.get_doc({
- 'doctype': 'Currency Exchange',
- 'from_currency': 'CNY',
- 'to_currency': 'USD',
- 'exchange_rate': 0.16
- }).insert()
-
-def setup_mode_of_payment():
- company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr")
- account_dict = {'Cash': 'Cash - '+ company_abbr , 'Bank': 'National Bank - '+ company_abbr}
- for payment_mode in frappe.get_all('Mode of Payment', fields = ["name", "type"]):
- if payment_mode.type:
- mop = frappe.get_doc('Mode of Payment', payment_mode.name)
- mop.append('accounts', {
- 'company': erpnext.get_default_company(),
- 'default_account': account_dict.get(payment_mode.type)
- })
- mop.save(ignore_permissions=True)
-
-def setup_account():
- frappe.flags.in_import = True
- data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',
- 'account.json')).read())
- for d in data:
- doc = frappe.new_doc('Account')
- doc.update(d)
- doc.parent_account = frappe.db.get_value('Account', {'account_name': doc.parent_account})
- doc.insert()
-
- frappe.flags.in_import = False
-
-def setup_account_to_expense_type():
- company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr")
- expense_types = [{'name': _('Calls'), "account": "Sales Expenses - "+ company_abbr},
- {'name': _('Food'), "account": "Entertainment Expenses - "+ company_abbr},
- {'name': _('Medical'), "account": "Utility Expenses - "+ company_abbr},
- {'name': _('Others'), "account": "Miscellaneous Expenses - "+ company_abbr},
- {'name': _('Travel'), "account": "Travel Expenses - "+ company_abbr}]
-
- for expense_type in expense_types:
- doc = frappe.get_doc("Expense Claim Type", expense_type["name"])
- doc.append("accounts", {
- "company" : erpnext.get_default_company(),
- "default_account" : expense_type["account"]
- })
- doc.save(ignore_permissions=True)
-
-def setup_budget():
- fiscal_years = frappe.get_all("Fiscal Year", order_by="year_start_date")[-2:]
-
- for fy in fiscal_years:
- budget = frappe.new_doc("Budget")
- budget.cost_center = get_random("Cost Center")
- budget.fiscal_year = fy.name
- budget.action_if_annual_budget_exceeded = "Warn"
- expense_ledger_count = frappe.db.count("Account", {"is_group": "0", "root_type": "Expense"})
-
- add_random_children(budget, "accounts", rows=random.randint(10, expense_ledger_count),
- randomize = {
- "account": ("Account", {"is_group": "0", "root_type": "Expense"})
- }, unique="account")
-
- for d in budget.accounts:
- d.budget_amount = random.randint(5, 100) * 10000
-
- budget.save()
- budget.submit()
-
-def setup_pos_profile():
- company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr")
- pos = frappe.new_doc('POS Profile')
- pos.user = frappe.db.get_global('demo_accounts_user')
- pos.name = "Demo POS Profile"
- pos.naming_series = 'SINV-'
- pos.update_stock = 0
- pos.write_off_account = 'Cost of Goods Sold - '+ company_abbr
- pos.write_off_cost_center = 'Main - '+ company_abbr
- pos.customer_group = get_root_of('Customer Group')
- pos.territory = get_root_of('Territory')
-
- pos.append('payments', {
- 'mode_of_payment': frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
- 'amount': 0.0,
- 'default': 1
- })
-
- pos.insert()
-
-def setup_role_permissions():
- role_permissions = {'Batch': ['Accounts User', 'Item Manager']}
- for doctype, roles in role_permissions.items():
- for role in roles:
- if not frappe.db.get_value('Custom DocPerm',
- {'parent': doctype, 'role': role}):
- frappe.get_doc({
- 'doctype': 'Custom DocPerm',
- 'role': role,
- 'read': 1,
- 'write': 1,
- 'create': 1,
- 'delete': 1,
- 'parent': doctype
- }).insert(ignore_permissions=True)
-
-def import_json(doctype, submit=False, values=None):
- frappe.flags.in_import = True
- data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',
- frappe.scrub(doctype) + '.json')).read())
- for d in data:
- doc = frappe.new_doc(doctype)
- doc.update(d)
- doc.insert()
- if submit:
- doc.submit()
-
- frappe.db.commit()
-
- frappe.flags.in_import = False
-
-def update_employee_department(user_id, department):
- employee = frappe.db.get_value('Employee', {"user_id": user_id}, 'name')
- department = frappe.db.get_value('Department', {'department_name': department}, 'name')
- frappe.db.set_value('Employee', employee, 'department', department)
-
-def setup_custom_field_for_domain():
- field = {
- "Item": [
- dict(fieldname='domain', label='Domain',
- fieldtype='Select', hidden=1, default="Manufacturing",
- options="Manufacturing\nService\nDistribution\nRetail"
- )
- ]
- }
- create_custom_fields(field)
diff --git a/erpnext/demo/user/accounts.py b/erpnext/demo/user/accounts.py
deleted file mode 100644
index 273a3f9..0000000
--- a/erpnext/demo/user/accounts.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-
-import frappe
-from frappe.desk import query_report
-from frappe.utils import random_string
-from frappe.utils.make_random import get_random
-
-import erpnext
-from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry_against_invoice
-from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-from erpnext.accounts.doctype.payment_request.payment_request import (
- make_payment_entry,
- make_payment_request,
-)
-from erpnext.demo.user.sales import make_sales_order
-from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
-from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
-
-
-def work():
- frappe.set_user(frappe.db.get_global('demo_accounts_user'))
-
- if random.random() <= 0.6:
- report = "Ordered Items to be Billed"
- for so in list(set([r[0] for r in query_report.run(report)["result"]
- if r[0]!="Total"]))[:random.randint(1, 5)]:
- try:
- si = frappe.get_doc(make_sales_invoice(so))
- si.posting_date = frappe.flags.current_date
- for d in si.get("items"):
- if not d.income_account:
- d.income_account = "Sales - {}".format(frappe.get_cached_value('Company', si.company, 'abbr'))
- si.insert()
- si.submit()
- frappe.db.commit()
- except frappe.ValidationError:
- pass
-
- if random.random() <= 0.6:
- report = "Received Items to be Billed"
- for pr in list(set([r[0] for r in query_report.run(report)["result"]
- if r[0]!="Total"]))[:random.randint(1, 5)]:
- try:
- pi = frappe.get_doc(make_purchase_invoice(pr))
- pi.posting_date = frappe.flags.current_date
- pi.bill_no = random_string(6)
- pi.insert()
- pi.submit()
- frappe.db.commit()
- except frappe.ValidationError:
- pass
-
-
- if random.random() < 0.5:
- make_payment_entries("Sales Invoice", "Accounts Receivable")
-
- if random.random() < 0.5:
- make_payment_entries("Purchase Invoice", "Accounts Payable")
-
- if random.random() < 0.4:
- #make payment request against sales invoice
- sales_invoice_name = get_random("Sales Invoice", filters={"docstatus": 1})
- if sales_invoice_name:
- si = frappe.get_doc("Sales Invoice", sales_invoice_name)
- if si.outstanding_amount > 0:
- payment_request = make_payment_request(dt="Sales Invoice", dn=si.name, recipient_id=si.contact_email,
- submit_doc=True, mute_email=True, use_dummy_message=True)
-
- payment_entry = frappe.get_doc(make_payment_entry(payment_request.name))
- payment_entry.posting_date = frappe.flags.current_date
- payment_entry.submit()
-
- make_pos_invoice()
-
-def make_payment_entries(ref_doctype, report):
-
- outstanding_invoices = frappe.get_all(ref_doctype, fields=["name"],
- filters={
- "company": erpnext.get_default_company(),
- "outstanding_amount": (">", 0.0)
- })
-
- # make Payment Entry
- for inv in outstanding_invoices[:random.randint(1, 2)]:
- pe = get_payment_entry(ref_doctype, inv.name)
- pe.posting_date = frappe.flags.current_date
- pe.reference_no = random_string(6)
- pe.reference_date = frappe.flags.current_date
- pe.insert()
- pe.submit()
- frappe.db.commit()
- outstanding_invoices.remove(inv)
-
- # make payment via JV
- for inv in outstanding_invoices[:1]:
- jv = frappe.get_doc(get_payment_entry_against_invoice(ref_doctype, inv.name))
- jv.posting_date = frappe.flags.current_date
- jv.cheque_no = random_string(6)
- jv.cheque_date = frappe.flags.current_date
- jv.insert()
- jv.submit()
- frappe.db.commit()
-
-def make_pos_invoice():
- make_sales_order()
-
- for data in frappe.get_all('Sales Order', fields=["name"],
- filters = [["per_billed", "<", "100"]]):
- si = frappe.get_doc(make_sales_invoice(data.name))
- si.is_pos =1
- si.posting_date = frappe.flags.current_date
- for d in si.get("items"):
- if not d.income_account:
- d.income_account = "Sales - {}".format(frappe.get_cached_value('Company', si.company, 'abbr'))
- si.set_missing_values()
- make_payment_entries_for_pos_invoice(si)
- si.insert()
- si.submit()
-
-def make_payment_entries_for_pos_invoice(si):
- for data in si.payments:
- data.amount = si.outstanding_amount
- return
diff --git a/erpnext/demo/user/education.py b/erpnext/demo/user/education.py
deleted file mode 100644
index 47519c1..0000000
--- a/erpnext/demo/user/education.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-from datetime import timedelta
-
-import frappe
-from frappe.utils import cstr
-from frappe.utils.make_random import get_random
-
-from erpnext.education.api import (
- collect_fees,
- enroll_student,
- get_course,
- get_fee_schedule,
- get_student_group_students,
- make_attendance_records,
-)
-
-
-def work():
- frappe.set_user(frappe.db.get_global('demo_education_user'))
- for d in range(20):
- approve_random_student_applicant()
- enroll_random_student(frappe.flags.current_date)
- # if frappe.flags.current_date.weekday()== 0:
- # make_course_schedule(frappe.flags.current_date, frappe.utils.add_days(frappe.flags.current_date, 5))
- mark_student_attendance(frappe.flags.current_date)
- # make_assessment_plan()
- make_fees()
-
-def approve_random_student_applicant():
- random_student = get_random("Student Applicant", {"application_status": "Applied"})
- if random_student:
- status = ["Approved", "Rejected"]
- frappe.db.set_value("Student Applicant", random_student, "application_status", status[weighted_choice([9,3])])
-
-def enroll_random_student(current_date):
- batch = ["Section-A", "Section-B"]
- random_student = get_random("Student Applicant", {"application_status": "Approved"})
- if random_student:
- enrollment = enroll_student(random_student)
- enrollment.academic_year = get_random("Academic Year")
- enrollment.enrollment_date = current_date
- enrollment.student_batch_name = batch[weighted_choice([9,3])]
- fee_schedule = get_fee_schedule(enrollment.program)
- for fee in fee_schedule:
- enrollment.append("fees", fee)
- enrolled_courses = get_course(enrollment.program)
- for course in enrolled_courses:
- enrollment.append("courses", course)
- enrollment.submit()
- frappe.db.commit()
- assign_student_group(enrollment.student, enrollment.student_name, enrollment.program,
- enrolled_courses, enrollment.student_batch_name)
-
-def assign_student_group(student, student_name, program, courses, batch):
- course_list = [d["course"] for d in courses]
- for d in frappe.get_list("Student Group", fields=("name"), filters={"program": program, "course":("in", course_list), "disabled": 0}):
- student_group = frappe.get_doc("Student Group", d.name)
- student_group.append("students", {"student": student, "student_name": student_name,
- "group_roll_number":len(student_group.students)+1, "active":1})
- student_group.save()
- student_batch = frappe.get_list("Student Group", fields=("name"), filters={"program": program, "group_based_on":"Batch", "batch":batch, "disabled": 0})[0]
- student_batch_doc = frappe.get_doc("Student Group", student_batch.name)
- student_batch_doc.append("students", {"student": student, "student_name": student_name,
- "group_roll_number":len(student_batch_doc.students)+1, "active":1})
- student_batch_doc.save()
- frappe.db.commit()
-
-def mark_student_attendance(current_date):
- status = ["Present", "Absent"]
- for d in frappe.db.get_list("Student Group", filters={"group_based_on": "Batch", "disabled": 0}):
- students = get_student_group_students(d.name)
- for stud in students:
- make_attendance_records(stud.student, stud.student_name, status[weighted_choice([9,4])], None, d.name, current_date)
-
-def make_fees():
- for d in range(1,10):
- random_fee = get_random("Fees", {"paid_amount": 0})
- collect_fees(random_fee, frappe.db.get_value("Fees", random_fee, "outstanding_amount"))
-
-def make_assessment_plan(date):
- for d in range(1,4):
- random_group = get_random("Student Group", {"group_based_on": "Course", "disabled": 0}, True)
- doc = frappe.new_doc("Assessment Plan")
- doc.student_group = random_group.name
- doc.course = random_group.course
- doc.assessment_group = get_random("Assessment Group", {"is_group": 0, "parent": "2017-18 (Semester 2)"})
- doc.grading_scale = get_random("Grading Scale")
- doc.maximum_assessment_score = 100
-
-def make_course_schedule(start_date, end_date):
- for d in frappe.db.get_list("Student Group"):
- cs = frappe.new_doc("Scheduling Tool")
- cs.student_group = d.name
- cs.room = get_random("Room")
- cs.instructor = get_random("Instructor")
- cs.course_start_date = cstr(start_date)
- cs.course_end_date = cstr(end_date)
- day = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
- for x in range(3):
- random_day = random.choice(day)
- cs.day = random_day
- cs.from_time = timedelta(hours=(random.randrange(7, 17,1)))
- cs.to_time = cs.from_time + timedelta(hours=1)
- cs.schedule_course()
- day.remove(random_day)
-
-
-def weighted_choice(weights):
- totals = []
- running_total = 0
-
- for w in weights:
- running_total += w
- totals.append(running_total)
-
- rnd = random.random() * running_total
- for i, total in enumerate(totals):
- if rnd < total:
- return i
diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py
deleted file mode 100644
index 72cd420..0000000
--- a/erpnext/demo/user/fixed_asset.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-from frappe.utils.make_random import get_random
-
-from erpnext.assets.doctype.asset.asset import make_sales_invoice
-from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset
-
-
-def work():
- frappe.set_user(frappe.db.get_global('demo_accounts_user'))
-
- # Enable booking asset depreciation entry automatically
- frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
-
- # post depreciation entries as on today
- post_depreciation_entries()
-
- # scrap a random asset
- frappe.db.set_value("Company", "Wind Power LLC", "disposal_account", "Gain/Loss on Asset Disposal - WPL")
-
- asset = get_random_asset()
- scrap_asset(asset.name)
-
- # Sell a random asset
- sell_an_asset()
-
-
-def sell_an_asset():
- asset = get_random_asset()
- si = make_sales_invoice(asset.name, asset.item_code, "Wind Power LLC")
- si.customer = get_random("Customer")
- si.get("items")[0].rate = asset.value_after_depreciation * 0.8 \
- if asset.value_after_depreciation else asset.gross_purchase_amount * 0.9
- si.save()
- si.submit()
-
-
-def get_random_asset():
- return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount
- from `tabAsset`
- where docstatus=1 and status not in ("Scrapped", "Sold") order by rand() limit 1""", as_dict=1)[0]
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
deleted file mode 100644
index f84a853..0000000
--- a/erpnext/demo/user/hr.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import datetime
-import random
-
-import frappe
-from frappe.utils import add_days, get_last_day, getdate, random_string
-from frappe.utils.make_random import get_random
-
-import erpnext
-from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
-from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account
-from erpnext.hr.doctype.leave_application.leave_application import (
- AttendanceAlreadyMarkedError,
- OverlapError,
- get_leave_balance_on,
-)
-from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
-from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
-
-
-def work():
- frappe.set_user(frappe.db.get_global('demo_hr_user'))
- year, month = frappe.flags.current_date.strftime("%Y-%m").split("-")
- setup_department_approvers()
- mark_attendance()
- make_leave_application()
-
- # payroll entry
- if not frappe.db.sql('select name from `tabSalary Slip` where month(adddate(start_date, interval 1 month))=month(curdate())'):
- # based on frequency
- payroll_entry = get_payroll_entry()
- payroll_entry.salary_slip_based_on_timesheet = 0
- payroll_entry.save()
- payroll_entry.create_salary_slips()
- payroll_entry.submit_salary_slips()
- payroll_entry.make_accrual_jv_entry()
- payroll_entry.submit()
- # payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date,
- # reference_number=random_string(10))
-
- # based on timesheet
- payroll_entry = get_payroll_entry()
- payroll_entry.salary_slip_based_on_timesheet = 1
- payroll_entry.save()
- payroll_entry.create_salary_slips()
- payroll_entry.submit_salary_slips()
- payroll_entry.make_accrual_jv_entry()
- payroll_entry.submit()
- # payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date,
- # reference_number=random_string(10))
-
- if frappe.db.get_global('demo_hr_user'):
- make_timesheet_records()
-
- #expense claim
- expense_claim = frappe.new_doc("Expense Claim")
- expense_claim.extend('expenses', get_expenses())
- expense_claim.employee = get_random("Employee")
- expense_claim.company = frappe.flags.company
- expense_claim.payable_account = get_payable_account(expense_claim.company)
- expense_claim.posting_date = frappe.flags.current_date
- expense_claim.expense_approver = frappe.db.get_global('demo_hr_user')
- expense_claim.save()
-
- rand = random.random()
-
- if rand < 0.4:
- update_sanctioned_amount(expense_claim)
- expense_claim.approval_status = 'Approved'
- expense_claim.submit()
-
- if random.randint(0, 1):
- #make journal entry against expense claim
- je = frappe.get_doc(make_bank_entry("Expense Claim", expense_claim.name))
- je.posting_date = frappe.flags.current_date
- je.cheque_no = random_string(10)
- je.cheque_date = frappe.flags.current_date
- je.flags.ignore_permissions = 1
- je.submit()
-
-def get_payroll_entry():
- # process payroll for previous month
- payroll_entry = frappe.new_doc("Payroll Entry")
- payroll_entry.company = frappe.flags.company
- payroll_entry.payroll_frequency = 'Monthly'
-
- # select a posting date from the previous month
- payroll_entry.posting_date = get_last_day(getdate(frappe.flags.current_date) - datetime.timedelta(days=10))
- payroll_entry.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
-
- payroll_entry.set_start_end_dates()
- return payroll_entry
-
-def get_expenses():
- expenses = []
- expese_types = frappe.db.sql("""select ect.name, eca.default_account from `tabExpense Claim Type` ect,
- `tabExpense Claim Account` eca where eca.parent=ect.name
- and eca.company=%s """, frappe.flags.company,as_dict=1)
-
- for expense_type in expese_types[:random.randint(1,4)]:
- claim_amount = random.randint(1,20)*10
-
- expenses.append({
- "expense_date": frappe.flags.current_date,
- "expense_type": expense_type.name,
- "default_account": expense_type.default_account or "Miscellaneous Expenses - WPL",
- "amount": claim_amount,
- "sanctioned_amount": claim_amount
- })
-
- return expenses
-
-def update_sanctioned_amount(expense_claim):
- for expense in expense_claim.expenses:
- sanctioned_amount = random.randint(1,20)*10
-
- if sanctioned_amount < expense.amount:
- expense.sanctioned_amount = sanctioned_amount
-
-def get_timesheet_based_salary_slip_employee():
- sal_struct = frappe.db.sql("""
- select name from `tabSalary Structure`
- where salary_slip_based_on_timesheet = 1
- and docstatus != 2""")
- if sal_struct:
- employees = frappe.db.sql("""
- select employee from `tabSalary Structure Assignment`
- where salary_structure IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True)
- return employees
- else:
- return []
-
-def make_timesheet_records():
- employees = get_timesheet_based_salary_slip_employee()
- for e in employees:
- ts = make_timesheet(e.employee, simulate = True, billable = 1, activity_type=get_random("Activity Type"), company=frappe.flags.company)
- frappe.db.commit()
-
- rand = random.random()
- if rand >= 0.3:
- make_salary_slip_for_timesheet(ts.name)
-
- rand = random.random()
- if rand >= 0.2:
- make_sales_invoice_for_timesheet(ts.name)
-
-def make_salary_slip_for_timesheet(name):
- salary_slip = make_salary_slip(name)
- salary_slip.insert()
- salary_slip.submit()
- frappe.db.commit()
-
-def make_sales_invoice_for_timesheet(name):
- sales_invoice = make_sales_invoice(name)
- sales_invoice.customer = get_random("Customer")
- sales_invoice.append('items', {
- 'item_code': get_random("Item", {"has_variants": 0, "is_stock_item": 0,
- "is_fixed_asset": 0}),
- 'qty': 1,
- 'rate': 1000
- })
- sales_invoice.flags.ignore_permissions = 1
- sales_invoice.set_missing_values()
- sales_invoice.calculate_taxes_and_totals()
- sales_invoice.insert()
- sales_invoice.submit()
- frappe.db.commit()
-
-def make_leave_application():
- allocated_leaves = frappe.get_all("Leave Allocation", fields=['employee', 'leave_type'])
-
- for allocated_leave in allocated_leaves:
- leave_balance = get_leave_balance_on(allocated_leave.employee, allocated_leave.leave_type, frappe.flags.current_date,
- consider_all_leaves_in_the_allocation_period=True)
- if leave_balance != 0:
- if leave_balance == 1:
- to_date = frappe.flags.current_date
- else:
- to_date = add_days(frappe.flags.current_date, random.randint(0, leave_balance-1))
-
- leave_application = frappe.get_doc({
- "doctype": "Leave Application",
- "employee": allocated_leave.employee,
- "from_date": frappe.flags.current_date,
- "to_date": to_date,
- "leave_type": allocated_leave.leave_type,
- })
- try:
- leave_application.insert()
- leave_application.submit()
- frappe.db.commit()
- except (OverlapError, AttendanceAlreadyMarkedError):
- frappe.db.rollback()
-
-def mark_attendance():
- attendance_date = frappe.flags.current_date
- for employee in frappe.get_all('Employee', fields=['name'], filters = {'status': 'Active'}):
-
- if not frappe.db.get_value("Attendance", {"employee": employee.name, "attendance_date": attendance_date}):
- attendance = frappe.get_doc({
- "doctype": "Attendance",
- "employee": employee.name,
- "attendance_date": attendance_date
- })
-
- leave = frappe.db.sql("""select name from `tabLeave Application`
- where employee = %s and %s between from_date and to_date
- and docstatus = 1""", (employee.name, attendance_date))
-
- if leave:
- attendance.status = "Absent"
- else:
- attendance.status = "Present"
- attendance.save()
- attendance.submit()
- frappe.db.commit()
-
-def setup_department_approvers():
- for d in frappe.get_all('Department', filters={'department_name': ['!=', 'All Departments']}):
- doc = frappe.get_doc('Department', d.name)
- doc.append("leave_approvers", {'approver': frappe.session.user})
- doc.append("expense_approvers", {'approver': frappe.session.user})
- doc.flags.ignore_mandatory = True
- doc.save()
diff --git a/erpnext/demo/user/manufacturing.py b/erpnext/demo/user/manufacturing.py
deleted file mode 100644
index 6b61776..0000000
--- a/erpnext/demo/user/manufacturing.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-from datetime import timedelta
-
-import frappe
-from frappe.desk import query_report
-from frappe.utils.make_random import how_many
-
-import erpnext
-from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
-
-
-def work():
- if random.random() < 0.3: return
-
- frappe.set_user(frappe.db.get_global('demo_manufacturing_user'))
- if not frappe.get_all('Sales Order'): return
-
- ppt = frappe.new_doc("Production Plan")
- ppt.company = erpnext.get_default_company()
- # ppt.use_multi_level_bom = 1 #refactored
- ppt.get_items_from = "Sales Order"
- # ppt.purchase_request_for_warehouse = "Stores - WPL" # refactored
- ppt.run_method("get_open_sales_orders")
- if not ppt.get("sales_orders"): return
- ppt.run_method("get_items")
- ppt.run_method("raise_material_requests")
- ppt.save()
- ppt.submit()
- ppt.run_method("raise_work_orders")
- frappe.db.commit()
-
- # submit work orders
- for pro in frappe.db.get_values("Work Order", {"docstatus": 0}, "name"):
- b = frappe.get_doc("Work Order", pro[0])
- b.wip_warehouse = "Work in Progress - WPL"
- b.submit()
- frappe.db.commit()
-
- # submit material requests
- for pro in frappe.db.get_values("Material Request", {"docstatus": 0}, "name"):
- b = frappe.get_doc("Material Request", pro[0])
- b.submit()
- frappe.db.commit()
-
- # stores -> wip
- if random.random() < 0.4:
- for pro in query_report.run("Open Work Orders")["result"][:how_many("Stock Entry for WIP")]:
- make_stock_entry_from_pro(pro[0], "Material Transfer for Manufacture")
-
- # wip -> fg
- if random.random() < 0.4:
- for pro in query_report.run("Work Orders in Progress")["result"][:how_many("Stock Entry for FG")]:
- make_stock_entry_from_pro(pro[0], "Manufacture")
-
- for bom in frappe.get_all('BOM', fields=['item'], filters = {'with_operations': 1}):
- pro_order = make_wo_order_test_record(item=bom.item, qty=2,
- source_warehouse="Stores - WPL", wip_warehouse = "Work in Progress - WPL",
- fg_warehouse = "Stores - WPL", company = erpnext.get_default_company(),
- stock_uom = frappe.db.get_value('Item', bom.item, 'stock_uom'),
- planned_start_date = frappe.flags.current_date)
-
- # submit job card
- if random.random() < 0.4:
- submit_job_cards()
-
-def make_stock_entry_from_pro(pro_id, purpose):
- from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry
- from erpnext.stock.doctype.stock_entry.stock_entry import (
- DuplicateEntryForWorkOrderError,
- IncorrectValuationRateError,
- OperationsNotCompleteError,
- )
- from erpnext.stock.stock_ledger import NegativeStockError
-
- try:
- st = frappe.get_doc(make_stock_entry(pro_id, purpose))
- st.posting_date = frappe.flags.current_date
- st.fiscal_year = str(frappe.flags.current_date.year)
- for d in st.get("items"):
- d.cost_center = "Main - " + frappe.get_cached_value('Company', st.company, 'abbr')
- st.insert()
- frappe.db.commit()
- st.submit()
- frappe.db.commit()
- except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError,
- OperationsNotCompleteError):
- frappe.db.rollback()
-
-def submit_job_cards():
- work_orders = frappe.get_all("Work Order", ["name", "creation"], {"docstatus": 1, "status": "Not Started"})
- work_order = random.choice(work_orders)
- # for work_order in work_orders:
- start_date = work_order.creation
- work_order = frappe.get_doc("Work Order", work_order.name)
- job = frappe.get_all("Job Card", ["name", "operation", "work_order"],
- {"docstatus": 0, "work_order": work_order.name})
-
- if not job: return
- job_map = {}
- for d in job:
- job_map[d.operation] = frappe.get_doc("Job Card", d.name)
-
- for operation in work_order.operations:
- job = job_map[operation.operation]
- job_time_log = frappe.new_doc("Job Card Time Log")
- job_time_log.from_time = start_date
- minutes = operation.get("time_in_mins")
- job_time_log.time_in_mins = random.randint(int(minutes/2), minutes)
- job_time_log.to_time = job_time_log.from_time + \
- timedelta(minutes=job_time_log.time_in_mins)
- job_time_log.parent = job.name
- job_time_log.parenttype = 'Job Card'
- job_time_log.parentfield = 'time_logs'
- job_time_log.completed_qty = work_order.qty
- job_time_log.save(ignore_permissions=True)
- job.time_logs.append(job_time_log)
- job.save(ignore_permissions=True)
- job.submit()
- start_date = job_time_log.to_time
diff --git a/erpnext/demo/user/projects.py b/erpnext/demo/user/projects.py
deleted file mode 100644
index 1203be4..0000000
--- a/erpnext/demo/user/projects.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-from frappe.utils import flt
-from frappe.utils.make_random import get_random
-
-import erpnext
-from erpnext.demo.user.hr import make_sales_invoice_for_timesheet
-from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
-
-
-def run_projects(current_date):
- frappe.set_user(frappe.db.get_global('demo_projects_user'))
- if frappe.db.get_global('demo_projects_user'):
- make_project(current_date)
- make_timesheet_for_projects(current_date)
- close_tasks(current_date)
-
-def make_timesheet_for_projects(current_date ):
- for data in frappe.get_all("Task", ["name", "project"], {"status": "Open", "exp_end_date": ("<", current_date)}):
- employee = get_random("Employee")
- ts = make_timesheet(employee, simulate = True, billable = 1, company = erpnext.get_default_company(),
- activity_type=get_random("Activity Type"), project=data.project, task =data.name)
-
- if flt(ts.total_billable_amount) > 0.0:
- make_sales_invoice_for_timesheet(ts.name)
- frappe.db.commit()
-
-def close_tasks(current_date):
- for task in frappe.get_all("Task", ["name"], {"status": "Open", "exp_end_date": ("<", current_date)}):
- task = frappe.get_doc("Task", task.name)
- task.status = "Completed"
- task.save()
-
-def make_project(current_date):
- if not frappe.db.exists('Project',
- "New Product Development " + current_date.strftime("%Y-%m-%d")):
- project = frappe.get_doc({
- "doctype": "Project",
- "project_name": "New Product Development " + current_date.strftime("%Y-%m-%d"),
- })
- project.insert()
diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py
deleted file mode 100644
index 61f081c..0000000
--- a/erpnext/demo/user/purchase.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import json
-import random
-
-import frappe
-from frappe.desk import query_report
-from frappe.utils.make_random import get_random, how_many
-
-import erpnext
-from erpnext.accounts.party import get_party_account_currency
-from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
- make_supplier_quotation_from_rfq,
-)
-from erpnext.exceptions import InvalidCurrency
-from erpnext.setup.utils import get_exchange_rate
-from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
-
-
-def work():
- frappe.set_user(frappe.db.get_global('demo_purchase_user'))
-
- if random.random() < 0.6:
- report = "Items To Be Requested"
- for row in query_report.run(report)["result"][:random.randint(1, 5)]:
- item_code, qty = row[0], abs(row[-1])
-
- mr = make_material_request(item_code, qty)
-
- if random.random() < 0.6:
- for mr in frappe.get_all('Material Request',
- filters={'material_request_type': 'Purchase', 'status': 'Open'},
- limit=random.randint(1,6)):
- if not frappe.get_all('Request for Quotation',
- filters={'material_request': mr.name}, limit=1):
- rfq = make_request_for_quotation(mr.name)
- rfq.transaction_date = frappe.flags.current_date
- add_suppliers(rfq)
- rfq.save()
- rfq.submit()
-
- # Make suppier quotation from RFQ against each supplier.
- if random.random() < 0.6:
- for rfq in frappe.get_all('Request for Quotation',
- filters={'status': 'Open'}, limit=random.randint(1, 6)):
- if not frappe.get_all('Supplier Quotation',
- filters={'request_for_quotation': rfq.name}, limit=1):
- rfq = frappe.get_doc('Request for Quotation', rfq.name)
-
- for supplier in rfq.suppliers:
- supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier)
- supplier_quotation.save()
- supplier_quotation.submit()
-
- # get supplier details
- supplier = get_random("Supplier")
-
- company_currency = frappe.get_cached_value('Company', erpnext.get_default_company(), "default_currency")
- party_account_currency = get_party_account_currency("Supplier", supplier, erpnext.get_default_company())
- if company_currency == party_account_currency:
- exchange_rate = 1
- else:
- exchange_rate = get_exchange_rate(party_account_currency, company_currency, args="for_buying")
-
- # make supplier quotations
- if random.random() < 0.5:
- from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation
-
- report = "Material Requests for which Supplier Quotations are not created"
- for row in query_report.run(report)["result"][:random.randint(1, 3)]:
- if row[0] != "Total":
- sq = frappe.get_doc(make_supplier_quotation(row[0]))
- sq.transaction_date = frappe.flags.current_date
- sq.supplier = supplier
- sq.currency = party_account_currency or company_currency
- sq.conversion_rate = exchange_rate
- sq.insert()
- sq.submit()
- frappe.db.commit()
-
- # make purchase orders
- if random.random() < 0.5:
- from erpnext.stock.doctype.material_request.material_request import make_purchase_order
- report = "Requested Items To Be Ordered"
- for row in query_report.run(report)["result"][:how_many("Purchase Order")]:
- if row[0] != "Total":
- try:
- po = frappe.get_doc(make_purchase_order(row[0]))
- po.supplier = supplier
- po.currency = party_account_currency or company_currency
- po.conversion_rate = exchange_rate
- po.transaction_date = frappe.flags.current_date
- po.insert()
- po.submit()
- except Exception:
- pass
- else:
- frappe.db.commit()
-
- if random.random() < 0.5:
- make_subcontract()
-
-def make_material_request(item_code, qty):
- mr = frappe.new_doc("Material Request")
-
- variant_of = frappe.db.get_value('Item', item_code, 'variant_of') or item_code
-
- if frappe.db.get_value('BOM', {'item': variant_of, 'is_default': 1, 'is_active': 1}):
- mr.material_request_type = 'Manufacture'
- else:
- mr.material_request_type = "Purchase"
-
- mr.transaction_date = frappe.flags.current_date
- mr.schedule_date = frappe.utils.add_days(mr.transaction_date, 7)
-
- mr.append("items", {
- "doctype": "Material Request Item",
- "schedule_date": frappe.utils.add_days(mr.transaction_date, 7),
- "item_code": item_code,
- "qty": qty
- })
- mr.insert()
- mr.submit()
- return mr
-
-def add_suppliers(rfq):
- for i in range(2):
- supplier = get_random("Supplier")
- if supplier not in [d.supplier for d in rfq.get('suppliers')]:
- rfq.append("suppliers", { "supplier": supplier })
-
-def make_subcontract():
- from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
- item_code = get_random("Item", {"is_sub_contracted_item": 1})
- if item_code:
- # make sub-contract PO
- po = frappe.new_doc("Purchase Order")
- po.is_subcontracted = "Yes"
- po.supplier = get_random("Supplier")
- po.transaction_date = frappe.flags.current_date # added
- po.schedule_date = frappe.utils.add_days(frappe.flags.current_date, 7)
-
- item_code = get_random("Item", {"is_sub_contracted_item": 1})
-
- po.append("items", {
- "item_code": item_code,
- "schedule_date": frappe.utils.add_days(frappe.flags.current_date, 7),
- "qty": random.randint(10, 30)
- })
- po.set_missing_values()
- try:
- po.insert()
- except InvalidCurrency:
- return
-
- po.submit()
-
- # make material request for
- make_material_request(po.items[0].item_code, po.items[0].qty)
-
- # transfer material for sub-contract
- rm_items = get_rm_item(po.items[0], po.supplied_items[0])
- stock_entry = frappe.get_doc(make_rm_stock_entry(po.name, json.dumps([rm_items])))
- stock_entry.from_warehouse = "Stores - WPL"
- stock_entry.to_warehouse = "Supplier - WPL"
- stock_entry.insert()
-
-def get_rm_item(items, supplied_items):
- return {
- "item_code": items.get("item_code"),
- "rm_item_code": supplied_items.get("rm_item_code"),
- "item_name": supplied_items.get("rm_item_code"),
- "qty": supplied_items.get("required_qty") + random.randint(3,10),
- "amount": supplied_items.get("amount"),
- "warehouse": supplied_items.get("reserve_warehouse"),
- "rate": supplied_items.get("rate"),
- "stock_uom": supplied_items.get("stock_uom")
- }
diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py
deleted file mode 100644
index ef6e4c4..0000000
--- a/erpnext/demo/user/sales.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-
-import frappe
-from frappe.utils import flt
-from frappe.utils.make_random import add_random_children, get_random
-
-import erpnext
-from erpnext.accounts.doctype.payment_request.payment_request import (
- make_payment_entry,
- make_payment_request,
-)
-from erpnext.accounts.party import get_party_account_currency
-from erpnext.setup.utils import get_exchange_rate
-
-
-def work(domain="Manufacturing"):
- frappe.set_user(frappe.db.get_global('demo_sales_user_2'))
-
- for i in range(random.randint(1,7)):
- if random.random() < 0.5:
- make_opportunity(domain)
-
- for i in range(random.randint(1,3)):
- if random.random() < 0.5:
- make_quotation(domain)
-
- try:
- lost_reason = frappe.get_doc({
- "doctype": "Opportunity Lost Reason",
- "lost_reason": "Did not ask"
- })
- lost_reason.save(ignore_permissions=True)
- except frappe.exceptions.DuplicateEntryError:
- pass
-
- # lost quotations / inquiries
- if random.random() < 0.3:
- for i in range(random.randint(1,3)):
- quotation = get_random('Quotation', doc=True)
- if quotation and quotation.status == 'Submitted':
- quotation.declare_order_lost([{'lost_reason': 'Did not ask'}])
-
- for i in range(random.randint(1,3)):
- opportunity = get_random('Opportunity', doc=True)
- if opportunity and opportunity.status in ('Open', 'Replied'):
- opportunity.declare_enquiry_lost([{'lost_reason': 'Did not ask'}])
-
- for i in range(random.randint(1,3)):
- if random.random() < 0.6:
- make_sales_order()
-
- if random.random() < 0.5:
- #make payment request against Sales Order
- sales_order_name = get_random("Sales Order", filters={"docstatus": 1})
- try:
- if sales_order_name:
- so = frappe.get_doc("Sales Order", sales_order_name)
- if flt(so.per_billed) != 100:
- payment_request = make_payment_request(dt="Sales Order", dn=so.name, recipient_id=so.contact_email,
- submit_doc=True, mute_email=True, use_dummy_message=True)
-
- payment_entry = frappe.get_doc(make_payment_entry(payment_request.name))
- payment_entry.posting_date = frappe.flags.current_date
- payment_entry.submit()
- except Exception:
- pass
-
-def make_opportunity(domain):
- b = frappe.get_doc({
- "doctype": "Opportunity",
- "opportunity_from": "Customer",
- "party_name": frappe.get_value("Customer", get_random("Customer"), 'name'),
- "opportunity_type": "Sales",
- "with_items": 1,
- "transaction_date": frappe.flags.current_date,
- })
-
- add_random_children(b, "items", rows=4, randomize = {
- "qty": (1, 5),
- "item_code": ("Item", {"has_variants": 0, "is_fixed_asset": 0, "domain": domain})
- }, unique="item_code")
-
- b.insert()
- frappe.db.commit()
-
-def make_quotation(domain):
- # get open opportunites
- opportunity = get_random("Opportunity", {"status": "Open", "with_items": 1})
-
- if opportunity:
- from erpnext.crm.doctype.opportunity.opportunity import make_quotation
- qtn = frappe.get_doc(make_quotation(opportunity))
- qtn.insert()
- frappe.db.commit()
- qtn.submit()
- frappe.db.commit()
- else:
- # make new directly
-
- # get customer, currency and exchange_rate
- customer = get_random("Customer")
-
- company_currency = frappe.get_cached_value('Company', erpnext.get_default_company(), "default_currency")
- party_account_currency = get_party_account_currency("Customer", customer, erpnext.get_default_company())
- if company_currency == party_account_currency:
- exchange_rate = 1
- else:
- exchange_rate = get_exchange_rate(party_account_currency, company_currency, args="for_selling")
-
- qtn = frappe.get_doc({
- "creation": frappe.flags.current_date,
- "doctype": "Quotation",
- "quotation_to": "Customer",
- "party_name": customer,
- "currency": party_account_currency or company_currency,
- "conversion_rate": exchange_rate,
- "order_type": "Sales",
- "transaction_date": frappe.flags.current_date,
- })
-
- add_random_children(qtn, "items", rows=3, randomize = {
- "qty": (1, 5),
- "item_code": ("Item", {"has_variants": "0", "is_fixed_asset": 0, "domain": domain})
- }, unique="item_code")
-
- qtn.insert()
- frappe.db.commit()
- qtn.submit()
- frappe.db.commit()
-
-def make_sales_order():
- q = get_random("Quotation", {"status": "Submitted"})
- if q:
- from erpnext.selling.doctype.quotation.quotation import make_sales_order as mso
- so = frappe.get_doc(mso(q))
- so.transaction_date = frappe.flags.current_date
- so.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10)
- so.insert()
- frappe.db.commit()
- so.submit()
- frappe.db.commit()
diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py
deleted file mode 100644
index de37975..0000000
--- a/erpnext/demo/user/stock.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-
-import frappe
-from frappe.desk import query_report
-
-import erpnext
-from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError
-from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
-from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
-from erpnext.stock.doctype.serial_no.serial_no import SerialNoQtyError, SerialNoRequiredError
-from erpnext.stock.stock_ledger import NegativeStockError
-
-
-def work():
- frappe.set_user(frappe.db.get_global('demo_manufacturing_user'))
-
- make_purchase_receipt()
- make_delivery_note()
- make_stock_reconciliation()
- submit_draft_stock_entries()
- make_sales_return_records()
- make_purchase_return_records()
-
-def make_purchase_receipt():
- if random.random() < 0.6:
- from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
- report = "Purchase Order Items To Be Received"
- po_list =list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="Total"]))[:random.randint(1, 10)]
- for po in po_list:
- pr = frappe.get_doc(make_purchase_receipt(po))
-
- if pr.is_subcontracted=="Yes":
- pr.supplier_warehouse = "Supplier - WPL"
-
- pr.posting_date = frappe.flags.current_date
- pr.insert()
- try:
- pr.submit()
- except NegativeStockError:
- print('Negative stock for {0}'.format(po))
- pass
- frappe.db.commit()
-
-def make_delivery_note():
- # make purchase requests
-
- # make delivery notes (if possible)
- if random.random() < 0.6:
- from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
- report = "Ordered Items To Be Delivered"
- for so in list(set([r[0] for r in query_report.run(report)["result"]
- if r[0]!="Total"]))[:random.randint(1, 3)]:
- dn = frappe.get_doc(make_delivery_note(so))
- dn.posting_date = frappe.flags.current_date
- for d in dn.get("items"):
- if not d.expense_account:
- d.expense_account = ("Cost of Goods Sold - {0}".format(
- frappe.get_cached_value('Company', dn.company, 'abbr')))
-
- try:
- dn.insert()
- dn.submit()
- frappe.db.commit()
- except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError):
- frappe.db.rollback()
-
-def make_stock_reconciliation():
- # random set some items as damaged
- from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
- EmptyStockReconciliationItemsError,
- OpeningEntryAccountError,
- )
-
- if random.random() < 0.4:
- stock_reco = frappe.new_doc("Stock Reconciliation")
- stock_reco.posting_date = frappe.flags.current_date
- stock_reco.company = erpnext.get_default_company()
- stock_reco.get_items_for("Stores - WPL")
- if stock_reco.items:
- for item in stock_reco.items:
- if item.qty:
- item.qty = item.qty - round(random.randint(1, item.qty))
- try:
- stock_reco.insert(ignore_permissions=True, ignore_mandatory=True)
- stock_reco.submit()
- frappe.db.commit()
- except OpeningEntryAccountError:
- frappe.db.rollback()
- except EmptyStockReconciliationItemsError:
- frappe.db.rollback()
-
-def submit_draft_stock_entries():
- from erpnext.stock.doctype.stock_entry.stock_entry import (
- DuplicateEntryForWorkOrderError,
- IncorrectValuationRateError,
- OperationsNotCompleteError,
- )
-
- # try posting older drafts (if exists)
- frappe.db.commit()
- for st in frappe.db.get_values("Stock Entry", {"docstatus":0}, "name"):
- try:
- ste = frappe.get_doc("Stock Entry", st[0])
- ste.posting_date = frappe.flags.current_date
- ste.save()
- ste.submit()
- frappe.db.commit()
- except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError,
- OperationsNotCompleteError):
- frappe.db.rollback()
-
-def make_sales_return_records():
- if random.random() < 0.1:
- for data in frappe.get_all('Delivery Note', fields=["name"], filters={"docstatus": 1}):
- if random.random() < 0.1:
- try:
- dn = make_sales_return(data.name)
- dn.insert()
- dn.submit()
- frappe.db.commit()
- except Exception:
- frappe.db.rollback()
-
-def make_purchase_return_records():
- if random.random() < 0.1:
- for data in frappe.get_all('Purchase Receipt', fields=["name"], filters={"docstatus": 1}):
- if random.random() < 0.1:
- try:
- pr = make_purchase_return(data.name)
- pr.insert()
- pr.submit()
- frappe.db.commit()
- except Exception:
- frappe.db.rollback()
diff --git a/erpnext/domains/hospitality.py b/erpnext/domains/hospitality.py
deleted file mode 100644
index 09b98c2..0000000
--- a/erpnext/domains/hospitality.py
+++ /dev/null
@@ -1,35 +0,0 @@
-data = {
- 'desktop_icons': [
- 'Restaurant',
- 'Hotels',
- 'Accounts',
- 'Buying',
- 'Stock',
- 'HR',
- 'Project',
- 'ToDo'
- ],
- 'restricted_roles': [
- 'Restaurant Manager',
- 'Hotel Manager',
- 'Hotel Reservation User'
- ],
- 'custom_fields': {
- 'Sales Invoice': [
- {
- 'fieldname': 'restaurant', 'fieldtype': 'Link', 'options': 'Restaurant',
- 'insert_after': 'customer_name', 'label': 'Restaurant',
- },
- {
- 'fieldname': 'restaurant_table', 'fieldtype': 'Link', 'options': 'Restaurant Table',
- 'insert_after': 'restaurant', 'label': 'Restaurant Table',
- }
- ],
- 'Price List': [
- {
- 'fieldname':'restaurant_menu', 'fieldtype':'Link', 'options':'Restaurant Menu', 'label':'Restaurant Menu',
- 'insert_after':'currency'
- }
- ]
- }
-}
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index d9013b0..636b948 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -201,8 +201,8 @@
conditions = get_event_conditions("Course Schedule", filters)
data = frappe.db.sql("""select name, course, color,
- timestamp(schedule_date, from_time) as from_datetime,
- timestamp(schedule_date, to_time) as to_datetime,
+ timestamp(schedule_date, from_time) as from_time,
+ timestamp(schedule_date, to_time) as to_time,
room, student_group, 0 as 'allDay'
from `tabCourse Schedule`
where ( schedule_date between %(start)s and %(end)s )
diff --git a/erpnext/education/doctype/course_schedule/course_schedule.py b/erpnext/education/doctype/course_schedule/course_schedule.py
index ffd323d..615d2c4 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule.py
+++ b/erpnext/education/doctype/course_schedule/course_schedule.py
@@ -3,6 +3,8 @@
# For license information, please see license.txt
+from datetime import datetime
+
import frappe
from frappe import _
from frappe.model.document import Document
@@ -30,6 +32,14 @@
if self.from_time > self.to_time:
frappe.throw(_("From Time cannot be greater than To Time."))
+ """Handles specicfic case to update schedule date in calendar """
+ if isinstance(self.from_time, str):
+ try:
+ datetime_obj = datetime.strptime(self.from_time, '%Y-%m-%d %H:%M:%S')
+ self.schedule_date = datetime_obj
+ except ValueError:
+ pass
+
def validate_overlap(self):
"""Validates overlap for Student Group, Instructor, Room"""
@@ -47,4 +57,4 @@
validate_overlap_for(self, "Assessment Plan", "student_group")
validate_overlap_for(self, "Assessment Plan", "room")
- validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
+ validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
\ No newline at end of file
diff --git a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js
index 803527e..cacd539 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js
+++ b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js
@@ -1,11 +1,10 @@
frappe.views.calendar["Course Schedule"] = {
field_map: {
- // from_datetime and to_datetime don't exist as docfields but are used in onload
- "start": "from_datetime",
- "end": "to_datetime",
+ "start": "from_time",
+ "end": "to_time",
"id": "name",
"title": "course",
- "allDay": "allDay"
+ "allDay": "allDay",
},
gantt: false,
order_by: "schedule_date",
diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.py b/erpnext/education/doctype/course_schedule/test_course_schedule.py
index a732419..56149af 100644
--- a/erpnext/education/doctype/course_schedule/test_course_schedule.py
+++ b/erpnext/education/doctype/course_schedule/test_course_schedule.py
@@ -6,6 +6,7 @@
import frappe
from frappe.utils import to_timedelta, today
+from frappe.utils.data import add_to_date
from erpnext.education.utils import OverlapError
@@ -39,6 +40,11 @@
make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time,
student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name)
+ def test_update_schedule_date(self):
+ doc = make_course_schedule_test_record(schedule_date= add_to_date(today(), days=1))
+ doc.schedule_date = add_to_date(doc.schedule_date, days=1)
+ doc.save()
+
def make_course_schedule_test_record(**args):
args = frappe._dict(args)
diff --git a/erpnext/education/workspace/education/education.json b/erpnext/education/workspace/education/education.json
index 1465295..0c7f198 100644
--- a/erpnext/education/workspace/education/education.json
+++ b/erpnext/education/workspace/education/education.json
@@ -5,7 +5,7 @@
"label": "Program Enrollments"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Education\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Program Enrollments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Instructor\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Program\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fees\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course Scheduling Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Attendance Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Student and Instructor\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Content Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Admission\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fees\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Schedule\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"LMS Activity\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Education\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Program Enrollments\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Instructor\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Program\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fees\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student Monthly Attendance Sheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Scheduling Tool\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student Attendance Tool\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Student and Instructor\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Content Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Admission\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fees\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Schedule\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"LMS Activity\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assessment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assessment Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 17:22:57.066401",
"docstatus": 0,
"doctype": "Workspace",
@@ -692,7 +692,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:15:57.929276",
+ "modified": "2022-01-13 17:29:13.676542",
"modified_by": "Administrator",
"module": "Education",
"name": "Education",
@@ -701,7 +701,7 @@
"public": 1,
"restrict_to_domain": "Education",
"roles": [],
- "sequence_id": 9,
+ "sequence_id": 9.0,
"shortcuts": [
{
"color": "Grey",
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
index c5b13c2..29bc36f 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
@@ -5,11 +5,11 @@
import csv
import math
import time
+from io import StringIO
import dateutil
import frappe
from frappe import _
-from six import StringIO
import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
index e242ace..a8119ac 100644
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
@@ -2,13 +2,14 @@
# For license information, please see license.txt
+from urllib.parse import urlencode
+
import frappe
import gocardless_pro
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url
-from six.moves.urllib.parse import urlencode
class GoCardlessSettings(Document):
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
index 8da52f4..309d2cb 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
@@ -2,12 +2,13 @@
# For license information, please see license.txt
+from urllib.parse import urlparse
+
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document
from frappe.utils.nestedset import get_root_of
-from six.moves.urllib.parse import urlparse
class WoocommerceSettings(Document):
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index d922d87..30d3948 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -1,10 +1,10 @@
import base64
import hashlib
import hmac
+from urllib.parse import urlparse
import frappe
from frappe import _
-from six.moves.urllib.parse import urlparse
from erpnext import get_default_company
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index 8e4f927..45077aa 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Marketplace\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Marketplace\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-08-20 19:30:48.138801",
"docstatus": 0,
"doctype": "Workspace",
@@ -41,17 +41,6 @@
"type": "Link"
},
{
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Shopify Settings",
- "link_count": 0,
- "link_to": "Shopify Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
@@ -112,7 +101,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:15:58.740247",
+ "modified": "2022-01-13 17:35:35.508718",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",
@@ -121,7 +110,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 10,
+ "sequence_id": 10.0,
"shortcuts": [],
"title": "ERPNext Integrations"
}
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e779e0f..102e1cc 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -67,7 +67,6 @@
domains = {
'Distribution': 'erpnext.domains.distribution',
'Education': 'erpnext.domains.education',
- 'Hospitality': 'erpnext.domains.hospitality',
'Manufacturing': 'erpnext.domains.manufacturing',
'Non Profit': 'erpnext.domains.non_profit',
'Retail': 'erpnext.domains.retail',
@@ -441,6 +440,7 @@
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
+ 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount',
'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code'
},
@@ -576,13 +576,6 @@
{'doctype': 'Donor Type', 'index': 10},
{'doctype': 'Membership Type', 'index': 11}
],
- "Hospitality": [
- {'doctype': 'Hotel Room', 'index': 0},
- {'doctype': 'Hotel Room Reservation', 'index': 1},
- {'doctype': 'Hotel Room Pricing', 'index': 2},
- {'doctype': 'Hotel Room Package', 'index': 3},
- {'doctype': 'Hotel Room Type', 'index': 4}
- ]
}
additional_timeline_content = {
diff --git a/erpnext/hotels/__init__.py b/erpnext/hotels/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/__init__.py b/erpnext/hotels/doctype/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room/__init__.py b/erpnext/hotels/doctype/hotel_room/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.js b/erpnext/hotels/doctype/hotel_room/hotel_room.js
deleted file mode 100644
index 76f22d5..0000000
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.json b/erpnext/hotels/doctype/hotel_room/hotel_room.json
deleted file mode 100644
index 2567c07..0000000
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "prompt",
- "beta": 1,
- "creation": "2017-12-08 12:33:56.320420",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hotel_room_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Hotel Room Type",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Type",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "capacity",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Capacity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "extra_bed_capacity",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Extra Bed Capacity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:10:50.670113",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Hotel Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py
deleted file mode 100644
index e4bd1c8..0000000
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.model.document import Document
-
-
-class HotelRoom(Document):
- def validate(self):
- if not self.capacity:
- self.capacity, self.extra_bed_capacity = frappe.db.get_value('Hotel Room Type',
- self.hotel_room_type, ['capacity', 'extra_bed_capacity'])
diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py
deleted file mode 100644
index 95efe2c..0000000
--- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_dependencies = ["Hotel Room Package"]
-test_records = [
- dict(doctype="Hotel Room", name="1001",
- hotel_room_type="Basic Room"),
- dict(doctype="Hotel Room", name="1002",
- hotel_room_type="Basic Room"),
- dict(doctype="Hotel Room", name="1003",
- hotel_room_type="Basic Room"),
- dict(doctype="Hotel Room", name="1004",
- hotel_room_type="Basic Room"),
- dict(doctype="Hotel Room", name="1005",
- hotel_room_type="Basic Room"),
- dict(doctype="Hotel Room", name="1006",
- hotel_room_type="Basic Room")
-]
-
-class TestHotelRoom(unittest.TestCase):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/__init__.py b/erpnext/hotels/doctype/hotel_room_amenity/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_amenity/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json
deleted file mode 100644
index 29a0407..0000000
--- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-12-08 12:35:36.572185",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "billable",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Billable",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-12-09 12:05:07.125687",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Amenity",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py
deleted file mode 100644
index 1664931..0000000
--- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomAmenity(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_package/__init__.py b/erpnext/hotels/doctype/hotel_room_package/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js
deleted file mode 100644
index 5b09ae5..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Package', {
- hotel_room_type: function(frm) {
- if (frm.doc.hotel_room_type) {
- frappe.model.with_doc('Hotel Room Type', frm.doc.hotel_room_type, () => {
- let hotel_room_type = frappe.get_doc('Hotel Room Type', frm.doc.hotel_room_type);
-
- // reset the amenities
- frm.doc.amenities = [];
-
- for (let amenity of hotel_room_type.amenities) {
- let d = frm.add_child('amenities');
- d.item = amenity.item;
- d.billable = amenity.billable;
- }
-
- frm.refresh();
- });
- }
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json
deleted file mode 100644
index 57dad44..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json
+++ /dev/null
@@ -1,215 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "prompt",
- "beta": 1,
- "creation": "2017-12-08 12:43:17.211064",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hotel_room_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Hotel Room Type",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Type",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amenities",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amenities",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Amenity",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:10:31.111952",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Package",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py
deleted file mode 100644
index aedc83a..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.model.document import Document
-
-
-class HotelRoomPackage(Document):
- def validate(self):
- if not self.item:
- item = frappe.get_doc(dict(
- doctype = 'Item',
- item_code = self.name,
- item_group = 'Products',
- is_stock_item = 0,
- stock_uom = 'Unit'
- ))
- item.insert()
- self.item = item.name
diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py
deleted file mode 100644
index 749731f..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_records = [
- dict(doctype='Item', item_code='Breakfast',
- item_group='Products', is_stock_item=0),
- dict(doctype='Item', item_code='Lunch',
- item_group='Products', is_stock_item=0),
- dict(doctype='Item', item_code='Dinner',
- item_group='Products', is_stock_item=0),
- dict(doctype='Item', item_code='WiFi',
- item_group='Products', is_stock_item=0),
- dict(doctype='Hotel Room Type', name="Delux Room",
- capacity=4,
- extra_bed_capacity=2,
- amenities = [
- dict(item='WiFi', billable=0)
- ]),
- dict(doctype='Hotel Room Type', name="Basic Room",
- capacity=4,
- extra_bed_capacity=2,
- amenities = [
- dict(item='Breakfast', billable=0)
- ]),
- dict(doctype="Hotel Room Package", name="Basic Room with Breakfast",
- hotel_room_type="Basic Room",
- amenities = [
- dict(item="Breakfast", billable=0)
- ]),
- dict(doctype="Hotel Room Package", name="Basic Room with Lunch",
- hotel_room_type="Basic Room",
- amenities = [
- dict(item="Breakfast", billable=0),
- dict(item="Lunch", billable=0)
- ]),
- dict(doctype="Hotel Room Package", name="Basic Room with Dinner",
- hotel_room_type="Basic Room",
- amenities = [
- dict(item="Breakfast", billable=0),
- dict(item="Dinner", billable=0)
- ])
-]
-
-class TestHotelRoomPackage(unittest.TestCase):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js
deleted file mode 100644
index 87bb192..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Pricing', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json
deleted file mode 100644
index 0f5a776..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json
+++ /dev/null
@@ -1,266 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "prompt",
- "beta": 1,
- "creation": "2017-12-08 12:51:47.088174",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Enabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "currency",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Currency",
- "length": 0,
- "no_copy": 0,
- "options": "Currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "from_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "From Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "to_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "To Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Pricing Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:10:41.559559",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Pricing",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Hotel Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py
deleted file mode 100644
index d28e573..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomPricing(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py
deleted file mode 100644
index 3455009..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_dependencies = ["Hotel Room Package"]
-test_records = [
- dict(doctype="Hotel Room Pricing", enabled=1,
- name="Winter 2017",
- from_date="2017-01-01", to_date="2017-01-10",
- items = [
- dict(item="Basic Room with Breakfast", rate=10000),
- dict(item="Basic Room with Lunch", rate=11000),
- dict(item="Basic Room with Dinner", rate=12000)
- ])
-]
-
-class TestHotelRoomPricing(unittest.TestCase):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json
deleted file mode 100644
index d6cd826..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-12-08 12:50:13.486090",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-12-09 12:04:58.641703",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Pricing Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py
deleted file mode 100644
index 2e6bb5f..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomPricingItem(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js
deleted file mode 100644
index f6decd9..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Pricing Package', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json
deleted file mode 100644
index 1e52932..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json
+++ /dev/null
@@ -1,173 +0,0 @@
-{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-12-08 12:50:13.486090",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "from_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "From Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "to_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "To Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hotel_room_package",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Hotel Room Package",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Package",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-11-04 03:34:02.551811",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Pricing Package",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py
deleted file mode 100644
index ebbdb6e..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomPricingPackage(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py
deleted file mode 100644
index 196e650..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestHotelRoomPricingPackage(unittest.TestCase):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js
deleted file mode 100644
index e58d763..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Reservation', {
- refresh: function(frm) {
- if(frm.doc.docstatus == 1){
- frm.add_custom_button(__('Create Invoice'), ()=> {
- frm.trigger("make_invoice");
- });
- }
- },
- from_date: function(frm) {
- frm.trigger("recalculate_rates");
- },
- to_date: function(frm) {
- frm.trigger("recalculate_rates");
- },
- recalculate_rates: function(frm) {
- if (!frm.doc.from_date || !frm.doc.to_date
- || !frm.doc.items.length){
- return;
- }
- frappe.call({
- "method": "erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation.get_room_rate",
- "args": {"hotel_room_reservation": frm.doc}
- }).done((r)=> {
- for (var i = 0; i < r.message.items.length; i++) {
- frm.doc.items[i].rate = r.message.items[i].rate;
- frm.doc.items[i].amount = r.message.items[i].amount;
- }
- frappe.run_serially([
- ()=> frm.set_value("net_total", r.message.net_total),
- ()=> frm.refresh_field("items")
- ]);
- });
- },
- make_invoice: function(frm) {
- frappe.model.with_doc("Hotel Settings", "Hotel Settings", ()=>{
- frappe.model.with_doctype("Sales Invoice", ()=>{
- let hotel_settings = frappe.get_doc("Hotel Settings", "Hotel Settings");
- let invoice = frappe.model.get_new_doc("Sales Invoice");
- invoice.customer = frm.doc.customer || hotel_settings.default_customer;
- if (hotel_settings.default_invoice_naming_series){
- invoice.naming_series = hotel_settings.default_invoice_naming_series;
- }
- for (let d of frm.doc.items){
- let invoice_item = frappe.model.add_child(invoice, "items")
- invoice_item.item_code = d.item;
- invoice_item.qty = d.qty;
- invoice_item.rate = d.rate;
- }
- if (hotel_settings.default_taxes_and_charges){
- invoice.taxes_and_charges = hotel_settings.default_taxes_and_charges;
- }
- frappe.set_route("Form", invoice.doctype, invoice.name);
- });
- });
- }
-});
-
-frappe.ui.form.on('Hotel Room Reservation Item', {
- item: function(frm, doctype, name) {
- frm.trigger("recalculate_rates");
- },
- qty: function(frm) {
- frm.trigger("recalculate_rates");
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json
deleted file mode 100644
index fd20efd..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json
+++ /dev/null
@@ -1,436 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "HTL-RES-.YYYY.-.#####",
- "beta": 1,
- "creation": "2017-12-08 13:01:34.829175",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "guest_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Guest Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Customer",
- "length": 0,
- "no_copy": 0,
- "options": "Customer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "from_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "From Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "to_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "To Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "late_checkin",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Late Checkin",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_6",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "Booked\nAdvance Paid\nInvoiced\nPaid\nCompleted\nCancelled",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Reservation Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "net_total",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Net Total",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Hotel Room Reservation",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-21 16:15:47.326951",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Reservation",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Hotel Reservation User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py
deleted file mode 100644
index 7725955..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import json
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import add_days, date_diff, flt
-
-
-class HotelRoomUnavailableError(frappe.ValidationError): pass
-class HotelRoomPricingNotSetError(frappe.ValidationError): pass
-
-class HotelRoomReservation(Document):
- def validate(self):
- self.total_rooms = {}
- self.set_rates()
- self.validate_availability()
-
- def validate_availability(self):
- for i in range(date_diff(self.to_date, self.from_date)):
- day = add_days(self.from_date, i)
- self.rooms_booked = {}
-
- for d in self.items:
- if not d.item in self.rooms_booked:
- self.rooms_booked[d.item] = 0
-
- room_type = frappe.db.get_value("Hotel Room Package",
- d.item, 'hotel_room_type')
- rooms_booked = get_rooms_booked(room_type, day, exclude_reservation=self.name) \
- + d.qty + self.rooms_booked.get(d.item)
- total_rooms = self.get_total_rooms(d.item)
- if total_rooms < rooms_booked:
- frappe.throw(_("Hotel Rooms of type {0} are unavailable on {1}").format(d.item,
- frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomUnavailableError)
-
- self.rooms_booked[d.item] += rooms_booked
-
- def get_total_rooms(self, item):
- if not item in self.total_rooms:
- self.total_rooms[item] = frappe.db.sql("""
- select count(*)
- from
- `tabHotel Room Package` package
- inner join
- `tabHotel Room` room on package.hotel_room_type = room.hotel_room_type
- where
- package.item = %s""", item)[0][0] or 0
-
- return self.total_rooms[item]
-
- def set_rates(self):
- self.net_total = 0
- for d in self.items:
- net_rate = 0.0
- for i in range(date_diff(self.to_date, self.from_date)):
- day = add_days(self.from_date, i)
- if not d.item:
- continue
- day_rate = frappe.db.sql("""
- select
- item.rate
- from
- `tabHotel Room Pricing Item` item,
- `tabHotel Room Pricing` pricing
- where
- item.parent = pricing.name
- and item.item = %s
- and %s between pricing.from_date
- and pricing.to_date""", (d.item, day))
-
- if day_rate:
- net_rate += day_rate[0][0]
- else:
- frappe.throw(
- _("Please set Hotel Room Rate on {}").format(
- frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomPricingNotSetError)
- d.rate = net_rate
- d.amount = net_rate * flt(d.qty)
- self.net_total += d.amount
-
-@frappe.whitelist()
-def get_room_rate(hotel_room_reservation):
- """Calculate rate for each day as it may belong to different Hotel Room Pricing Item"""
- doc = frappe.get_doc(json.loads(hotel_room_reservation))
- doc.set_rates()
- return doc.as_dict()
-
-def get_rooms_booked(room_type, day, exclude_reservation=None):
- exclude_condition = ''
- if exclude_reservation:
- exclude_condition = 'and reservation.name != {0}'.format(frappe.db.escape(exclude_reservation))
-
- return frappe.db.sql("""
- select sum(item.qty)
- from
- `tabHotel Room Package` room_package,
- `tabHotel Room Reservation Item` item,
- `tabHotel Room Reservation` reservation
- where
- item.parent = reservation.name
- and room_package.item = item.item
- and room_package.hotel_room_type = %s
- and reservation.docstatus = 1
- {exclude_condition}
- and %s between reservation.from_date
- and reservation.to_date""".format(exclude_condition=exclude_condition),
- (room_type, day))[0][0] or 0
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
deleted file mode 100644
index 7bde292..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
+++ /dev/null
@@ -1,9 +0,0 @@
-frappe.views.calendar["Hotel Room Reservation"] = {
- field_map: {
- "start": "from_date",
- "end": "to_date",
- "id": "name",
- "title": "guest_name",
- "status": "status"
- }
-}
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py
deleted file mode 100644
index bb32a27..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-
-from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import (
- HotelRoomPricingNotSetError,
- HotelRoomUnavailableError,
-)
-
-test_dependencies = ["Hotel Room Package", "Hotel Room Pricing", "Hotel Room"]
-
-class TestHotelRoomReservation(unittest.TestCase):
- def setUp(self):
- frappe.db.sql("delete from `tabHotel Room Reservation`")
- frappe.db.sql("delete from `tabHotel Room Reservation Item`")
-
- def test_reservation(self):
- reservation = make_reservation(
- from_date="2017-01-01",
- to_date="2017-01-03",
- items=[
- dict(item="Basic Room with Dinner", qty=2)
- ]
- )
- reservation.insert()
- self.assertEqual(reservation.net_total, 48000)
-
- def test_price_not_set(self):
- reservation = make_reservation(
- from_date="2016-01-01",
- to_date="2016-01-03",
- items=[
- dict(item="Basic Room with Dinner", qty=2)
- ]
- )
- self.assertRaises(HotelRoomPricingNotSetError, reservation.insert)
-
- def test_room_unavailable(self):
- reservation = make_reservation(
- from_date="2017-01-01",
- to_date="2017-01-03",
- items=[
- dict(item="Basic Room with Dinner", qty=2),
- ]
- )
- reservation.insert()
-
- reservation = make_reservation(
- from_date="2017-01-01",
- to_date="2017-01-03",
- items=[
- dict(item="Basic Room with Dinner", qty=20),
- ]
- )
- self.assertRaises(HotelRoomUnavailableError, reservation.insert)
-
-def make_reservation(**kwargs):
- kwargs["doctype"] = "Hotel Room Reservation"
- if not "guest_name" in kwargs:
- kwargs["guest_name"] = "Test Guest"
- doc = frappe.get_doc(kwargs)
- return doc
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json
deleted file mode 100644
index 2b7931e..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json
+++ /dev/null
@@ -1,195 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2017-12-08 12:58:21.733330",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "currency",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Currency",
- "length": 0,
- "no_copy": 0,
- "options": "Currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-12-09 12:04:34.562956",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Reservation Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py
deleted file mode 100644
index 41d86dd..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomReservationItem(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_type/__init__.py b/erpnext/hotels/doctype/hotel_room_type/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js
deleted file mode 100644
index d73835d..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Type', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json
deleted file mode 100644
index 3d26413..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json
+++ /dev/null
@@ -1,204 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "prompt",
- "beta": 1,
- "creation": "2017-12-08 12:38:29.485175",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "capacity",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Capacity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "extra_bed_capacity",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Extra Bed Capacity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_3",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amenities",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amenities",
- "length": 0,
- "no_copy": 0,
- "options": "Hotel Room Amenity",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:10:23.355486",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Type",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Hotel Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py
deleted file mode 100644
index 7ab529f..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomType(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py
deleted file mode 100644
index 8d1147d..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestHotelRoomType(unittest.TestCase):
- pass
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/hotel_settings.js
deleted file mode 100644
index 0b4a2c3..0000000
--- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Settings', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json b/erpnext/hotels/doctype/hotel_settings/hotel_settings.json
deleted file mode 100644
index d9f5572..0000000
--- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2017-12-08 17:50:24.523107",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_customer",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default Customer",
- "length": 0,
- "no_copy": 0,
- "options": "Customer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_taxes_and_charges",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Taxes and Charges",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Taxes and Charges Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_invoice_naming_series",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Invoice Naming Series",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:11:12.857308",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Settings",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Hotel Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py
deleted file mode 100644
index 8376d50..0000000
--- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelSettings(Document):
- pass
diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py
deleted file mode 100644
index e76c00c..0000000
--- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestHotelSettings(unittest.TestCase):
- pass
diff --git a/erpnext/hotels/report/__init__.py b/erpnext/hotels/report/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/report/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/report/hotel_room_occupancy/__init__.py b/erpnext/hotels/report/hotel_room_occupancy/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js
deleted file mode 100644
index 81efb2d..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-/* eslint-disable */
-
-frappe.query_reports["Hotel Room Occupancy"] = {
- "filters": [
- {
- "fieldname":"from_date",
- "label": __("From Date"),
- "fieldtype": "Date",
- "default": frappe.datetime.now_date(),
- "reqd":1
- },
- {
- "fieldname":"to_date",
- "label": __("To Date"),
- "fieldtype": "Date",
- "default": frappe.datetime.now_date(),
- "reqd":1
- }
- ]
-}
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json
deleted file mode 100644
index 782a48b..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "add_total_row": 1,
- "apply_user_permissions": 1,
- "creation": "2017-12-09 14:31:26.306705",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2017-12-09 14:31:26.306705",
- "modified_by": "Administrator",
- "module": "Hotels",
- "name": "Hotel Room Occupancy",
- "owner": "Administrator",
- "ref_doctype": "Hotel Room Reservation",
- "report_name": "Hotel Room Occupancy",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "System Manager"
- },
- {
- "role": "Hotel Reservation User"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
deleted file mode 100644
index c43589d..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.utils import add_days, date_diff
-
-from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import get_rooms_booked
-
-
-def execute(filters=None):
- columns = get_columns(filters)
- data = get_data(filters)
- return columns, data
-
-def get_columns(filters):
- columns = [
- dict(label=_("Room Type"), fieldname="room_type"),
- dict(label=_("Rooms Booked"), fieldtype="Int")
- ]
- return columns
-
-def get_data(filters):
- out = []
- for room_type in frappe.get_all('Hotel Room Type'):
- total_booked = 0
- for i in range(date_diff(filters.to_date, filters.from_date)):
- day = add_days(filters.from_date, i)
- total_booked += get_rooms_booked(room_type.name, day)
-
- out.append([room_type.name, total_booked])
-
- return out
diff --git a/erpnext/hr/doctype/appointment_letter/appointment_letter.json b/erpnext/hr/doctype/appointment_letter/appointment_letter.json
index c81b700..012f6b6 100644
--- a/erpnext/hr/doctype/appointment_letter/appointment_letter.json
+++ b/erpnext/hr/doctype/appointment_letter/appointment_letter.json
@@ -86,11 +86,12 @@
}
],
"links": [],
- "modified": "2020-01-21 17:30:36.334395",
+ "modified": "2022-01-18 19:27:35.649424",
"modified_by": "Administrator",
"module": "HR",
"name": "Appointment Letter",
"name_case": "Title Case",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -118,7 +119,10 @@
"write": 1
}
],
+ "search_fields": "applicant_name, company",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "applicant_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json
index c136fb2..5e50fe6 100644
--- a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json
+++ b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json
@@ -1,11 +1,12 @@
{
"actions": [],
- "autoname": "HR-APP-LETTER-TEMP-.#####",
+ "autoname": "field:template_name",
"creation": "2019-12-26 12:20:14.219578",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "template_name",
"introduction",
"terms",
"closing_notes"
@@ -29,13 +30,21 @@
"label": "Terms",
"options": "Appointment Letter content",
"reqd": 1
+ },
+ {
+ "fieldname": "template_name",
+ "fieldtype": "Data",
+ "label": "Template Name",
+ "reqd": 1,
+ "unique": 1
}
],
"links": [],
- "modified": "2020-01-21 17:00:46.779420",
+ "modified": "2022-01-18 19:25:14.614616",
"modified_by": "Administrator",
"module": "HR",
"name": "Appointment Letter Template",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -63,7 +72,10 @@
"write": 1
}
],
+ "search_fields": "template_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "template_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 7dcfac2..b1eaaf8 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -5,9 +5,9 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import cstr, formatdate, get_datetime, getdate, nowdate
+from frappe.utils import cint, cstr, formatdate, get_datetime, getdate, nowdate
-from erpnext.hr.utils import validate_active_employee
+from erpnext.hr.utils import get_holiday_dates_for_employee, validate_active_employee
class Attendance(Document):
@@ -171,7 +171,7 @@
})
@frappe.whitelist()
-def get_unmarked_days(employee, month):
+def get_unmarked_days(employee, month, exclude_holidays=0):
import calendar
month_map = get_month_map()
@@ -191,6 +191,11 @@
])
marked_days = [get_datetime(record.attendance_date) for record in records]
+ if cint(exclude_holidays):
+ holiday_dates = get_holiday_dates_for_employee(employee, month_start, month_end)
+ holidays = [get_datetime(record) for record in holiday_dates]
+ marked_days.extend(holidays)
+
unmarked_days = []
for date in dates_of_month:
diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js
index 6b3c29a..3a5c591 100644
--- a/erpnext/hr/doctype/attendance/attendance_list.js
+++ b/erpnext/hr/doctype/attendance/attendance_list.js
@@ -28,6 +28,7 @@
onchange: function() {
dialog.set_df_property("unmarked_days", "hidden", 1);
dialog.set_df_property("status", "hidden", 1);
+ dialog.set_df_property("exclude_holidays", "hidden", 1);
dialog.set_df_property("month", "value", '');
dialog.set_df_property("unmarked_days", "options", []);
dialog.no_unmarked_days_left = false;
@@ -42,9 +43,14 @@
onchange: function() {
if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
dialog.set_df_property("status", "hidden", 0);
+ dialog.set_df_property("exclude_holidays", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", []);
dialog.no_unmarked_days_left = false;
- me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options => {
+ me.get_multi_select_options(
+ dialog.fields_dict.employee.value,
+ dialog.fields_dict.month.value,
+ dialog.fields_dict.exclude_holidays.get_value()
+ ).then(options => {
if (options.length > 0) {
dialog.set_df_property("unmarked_days", "hidden", 0);
dialog.set_df_property("unmarked_days", "options", options);
@@ -65,6 +71,31 @@
},
{
+ label: __("Exclude Holidays"),
+ fieldtype: "Check",
+ fieldname: "exclude_holidays",
+ hidden: 1,
+ onchange: function() {
+ if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) {
+ dialog.set_df_property("status", "hidden", 0);
+ dialog.set_df_property("unmarked_days", "options", []);
+ dialog.no_unmarked_days_left = false;
+ me.get_multi_select_options(
+ dialog.fields_dict.employee.value,
+ dialog.fields_dict.month.value,
+ dialog.fields_dict.exclude_holidays.get_value()
+ ).then(options => {
+ if (options.length > 0) {
+ dialog.set_df_property("unmarked_days", "hidden", 0);
+ dialog.set_df_property("unmarked_days", "options", options);
+ } else {
+ dialog.no_unmarked_days_left = true;
+ }
+ });
+ }
+ }
+ },
+ {
label: __("Unmarked Attendance for days"),
fieldname: "unmarked_days",
fieldtype: "MultiCheck",
@@ -105,7 +136,7 @@
});
},
- get_multi_select_options: function(employee, month) {
+ get_multi_select_options: function(employee, month, exclude_holidays) {
return new Promise(resolve => {
frappe.call({
method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days',
@@ -113,6 +144,7 @@
args: {
employee: employee,
month: month,
+ exclude_holidays: exclude_holidays
}
}).then(r => {
var options = [];
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index a1247d9..fb62b04 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -21,7 +21,7 @@
},
{
'label': _('Lifecycle'),
- 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Grievance']
+ 'items': ['Employee Onboarding', 'Employee Transfer', 'Employee Promotion', 'Employee Grievance']
},
{
'label': _('Exit'),
diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
index 044a5a9..8474bd0 100644
--- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
+++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
@@ -62,6 +62,7 @@
},
{
"default": "0",
+ "depends_on": "eval:['Employee Onboarding', 'Employee Onboarding Template'].includes(doc.parenttype)",
"description": "Applicable in the case of Employee Onboarding",
"fieldname": "required_for_employee_creation",
"fieldtype": "Check",
@@ -93,7 +94,7 @@
],
"istable": 1,
"links": [],
- "modified": "2021-07-30 15:55:22.470102",
+ "modified": "2022-01-29 14:05:00.543122",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Boarding Activity",
@@ -102,5 +103,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
index 5d1a024..6fbb54d 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
@@ -3,12 +3,6 @@
frappe.ui.form.on('Employee Onboarding', {
setup: function(frm) {
- frm.add_fetch("employee_onboarding_template", "company", "company");
- frm.add_fetch("employee_onboarding_template", "department", "department");
- frm.add_fetch("employee_onboarding_template", "designation", "designation");
- frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade");
-
-
frm.set_query("job_applicant", function () {
return {
filters:{
@@ -71,5 +65,19 @@
}
});
}
+ },
+
+ job_applicant: function(frm) {
+ if (frm.doc.job_applicant) {
+ frappe.db.get_value('Employee', {'job_applicant': frm.doc.job_applicant}, 'name', (r) => {
+ if (r.name) {
+ frm.set_value('employee', r.name);
+ } else {
+ frm.set_value('employee', '');
+ }
+ });
+ } else {
+ frm.set_value('employee', '');
+ }
}
});
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index fd877a6..1d2ea0c 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -92,6 +92,7 @@
"options": "Employee Onboarding Template"
},
{
+ "fetch_from": "employee_onboarding_template.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
@@ -99,6 +100,7 @@
"reqd": 1
},
{
+ "fetch_from": "employee_onboarding_template.department",
"fieldname": "department",
"fieldtype": "Link",
"in_list_view": 1,
@@ -106,6 +108,7 @@
"options": "Department"
},
{
+ "fetch_from": "employee_onboarding_template.designation",
"fieldname": "designation",
"fieldtype": "Link",
"in_list_view": 1,
@@ -113,6 +116,7 @@
"options": "Designation"
},
{
+ "fetch_from": "employee_onboarding_template.employee_grade",
"fieldname": "employee_grade",
"fieldtype": "Link",
"label": "Employee Grade",
@@ -170,10 +174,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-07-30 14:55:04.560683",
+ "modified": "2022-01-29 12:33:57.120384",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Onboarding",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -194,6 +199,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index eba2a03..a0939a8 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -14,10 +14,15 @@
class EmployeeOnboarding(EmployeeBoardingController):
def validate(self):
super(EmployeeOnboarding, self).validate()
+ self.set_employee()
self.validate_duplicate_employee_onboarding()
+ def set_employee(self):
+ if not self.employee:
+ self.employee = frappe.db.get_value('Employee', {'job_applicant': self.job_applicant}, 'name')
+
def validate_duplicate_employee_onboarding(self):
- emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant})
+ emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)})
if emp_onboarding and emp_onboarding != self.name:
frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index cb1b560..2d129c8 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -19,7 +19,7 @@
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
- project = "Employee Onboarding : Test Researcher - test@researcher.com"
+ project = "Employee Onboarding : test@researcher.com"
frappe.db.sql("delete from tabProject where name=%s", project)
frappe.db.sql("delete from tabTask where project=%s", project)
@@ -27,7 +27,7 @@
onboarding = create_employee_onboarding()
project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
- self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
+ self.assertEqual(project_name, 'Employee Onboarding : test@researcher.com')
# don't allow making employee if onboarding is not complete
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
@@ -64,8 +64,8 @@
def get_job_applicant():
- if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
- return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
+ if frappe.db.exists('Job Applicant', 'test@researcher.com'):
+ return frappe.get_doc('Job Applicant', 'test@researcher.com')
applicant = frappe.new_doc('Job Applicant')
applicant.applicant_name = 'Test Researcher'
applicant.email_id = 'test@researcher.com'
diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py
index 4bb003d..a3b111c 100644
--- a/erpnext/hr/doctype/interview/interview.py
+++ b/erpnext/hr/doctype/interview/interview.py
@@ -7,7 +7,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import cstr, get_datetime, get_link_to_form
+from frappe.utils import cstr, flt, get_datetime, get_link_to_form
class DuplicateInterviewRoundError(frappe.ValidationError):
@@ -18,6 +18,7 @@
self.validate_duplicate_interview()
self.validate_designation()
self.validate_overlap()
+ self.set_average_rating()
def on_submit(self):
if self.status not in ['Cleared', 'Rejected']:
@@ -67,6 +68,13 @@
overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0]))
frappe.throw(overlapping_details, title=_('Overlap'))
+ def set_average_rating(self):
+ total_rating = 0
+ for entry in self.interview_details:
+ if entry.average_rating:
+ total_rating += entry.average_rating
+
+ self.average_rating = flt(total_rating / len(self.interview_details) if len(self.interview_details) else 0)
@frappe.whitelist()
def reschedule_interview(self, scheduled_on, from_time, to_time):
diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py
index 1a2257a..fdb11af 100644
--- a/erpnext/hr/doctype/interview/test_interview.py
+++ b/erpnext/hr/doctype/interview/test_interview.py
@@ -12,6 +12,7 @@
from erpnext.hr.doctype.designation.test_designation import create_designation
from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError
+from erpnext.hr.doctype.job_applicant.job_applicant import get_interview_details
from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant
@@ -70,6 +71,20 @@
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue("Subject: Interview Feedback Reminder" in email_queue[0].message)
+ def test_get_interview_details_for_applicant_dashboard(self):
+ job_applicant = create_job_applicant()
+ interview = create_interview_and_dependencies(job_applicant.name)
+
+ details = get_interview_details(job_applicant.name)
+ self.assertEqual(details.get('stars'), 5)
+ self.assertEqual(details.get('interviews').get(interview.name), {
+ 'name': interview.name,
+ 'interview_round': interview.interview_round,
+ 'expected_average_rating': interview.expected_average_rating * 5,
+ 'average_rating': interview.average_rating * 5,
+ 'status': 'Pending'
+ })
+
def tearDown(self):
frappe.db.rollback()
@@ -106,7 +121,8 @@
interview_round = frappe.new_doc("Interview Round")
interview_round.round_name = name
interview_round.interview_type = create_interview_type()
- interview_round.expected_average_rating = 4
+ # average rating = 4
+ interview_round.expected_average_rating = 0.8
if designation:
interview_round.designation = designation
diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
index d046458..2ff00c1 100644
--- a/erpnext/hr/doctype/interview_feedback/interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
@@ -57,7 +57,6 @@
def update_interview_details(self):
doc = frappe.get_doc('Interview', self.interview)
- total_rating = 0
if self.docstatus == 2:
for entry in doc.interview_details:
@@ -72,10 +71,6 @@
entry.comments = self.feedback
entry.result = self.result
- if entry.average_rating:
- total_rating += entry.average_rating
-
- doc.average_rating = flt(total_rating / len(doc.interview_details) if len(doc.interview_details) else 0)
doc.save()
doc.notify_update()
diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
index d2ec5b9..19c4642 100644
--- a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
@@ -24,7 +24,7 @@
create_skill_set(['Leadership'])
interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings)
- interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 4})
+ interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 0.8})
frappe.set_user(interviewer)
self.assertRaises(frappe.ValidationError, interview_feedback.save)
@@ -50,7 +50,7 @@
avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0)
- self.assertEqual(flt(avg_rating, 3), feedback_1.average_rating)
+ self.assertEqual(flt(avg_rating, 2), flt(feedback_1.average_rating, 2))
avg_on_interview_detail = frappe.db.get_value('Interview Detail', {
'parent': feedback_1.interview,
@@ -59,7 +59,7 @@
}, 'average_rating')
# 1. average should be reflected in Interview Detail.
- self.assertEqual(avg_on_interview_detail, feedback_1.average_rating)
+ self.assertEqual(flt(avg_on_interview_detail, 2), flt(feedback_1.average_rating, 2))
'''For Second Interviewer Feedback'''
interviewer = interview.interview_details[1].interviewer
@@ -97,5 +97,5 @@
skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"])
for d in skills:
- d["rating"] = random.randint(1, 5)
+ d["rating"] = random.random()
return skills
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js
index d7b1c6c..c1e8257 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.js
@@ -21,9 +21,9 @@
create_custom_buttons: function(frm) {
if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") {
- frm.add_custom_button(__("Create Interview"), function() {
+ frm.add_custom_button(__("Interview"), function() {
frm.events.create_dialog(frm);
- });
+ }, __("Create"));
}
if (!frm.doc.__islocal) {
@@ -40,10 +40,10 @@
frappe.route_options = {
"job_applicant": frm.doc.name,
"applicant_name": frm.doc.applicant_name,
- "designation": frm.doc.job_opening,
+ "designation": frm.doc.job_opening || frm.doc.designation,
};
frappe.new_doc("Job Offer");
- });
+ }, __("Create"));
}
}
},
@@ -55,13 +55,16 @@
job_applicant: frm.doc.name
},
callback: function(r) {
- $("div").remove(".form-dashboard-section.custom");
- frm.dashboard.add_section(
- frappe.render_template('job_applicant_dashboard', {
- data: r.message
- }),
- __("Interview Summary")
- );
+ if (r.message) {
+ $("div").remove(".form-dashboard-section.custom");
+ frm.dashboard.add_section(
+ frappe.render_template("job_applicant_dashboard", {
+ data: r.message.interviews,
+ number_of_stars: r.message.stars
+ }),
+ __("Interview Summary")
+ );
+ }
}
});
},
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json
index 200f675..66b609c 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.json
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.json
@@ -192,10 +192,11 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-09-29 23:06:10.904260",
+ "modified": "2022-01-12 16:28:53.196881",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Applicant",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -210,10 +211,11 @@
"write": 1
}
],
- "search_fields": "applicant_name",
+ "search_fields": "applicant_name, email_id, job_title, phone_number",
"sender_field": "email_id",
"sort_field": "modified",
"sort_order": "ASC",
+ "states": [],
"subject_field": "notes",
"title_field": "applicant_name"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index abaa50c..ccc21ce 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -7,6 +7,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.model.naming import append_number_if_name_exists
from frappe.utils import validate_email_address
from erpnext.hr.doctype.interview.interview import get_interviewers
@@ -21,10 +22,11 @@
self.get("__onload").job_offer = job_offer[0].name
def autoname(self):
- keys = filter(None, (self.applicant_name, self.email_id, self.job_title))
- if not keys:
- frappe.throw(_("Name or Email is mandatory"), frappe.NameError)
- self.name = " - ".join(keys)
+ self.name = self.email_id
+
+ # applicant can apply more than once for a different job title or reapply
+ if frappe.db.exists("Job Applicant", self.name):
+ self.name = append_number_if_name_exists("Job Applicant", self.name)
def validate(self):
if self.email_id:
@@ -79,8 +81,13 @@
fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"]
)
interview_detail_map = {}
+ meta = frappe.get_meta("Interview")
+ number_of_stars = meta.get_options("expected_average_rating") or 5
for detail in interview_details:
+ detail.expected_average_rating = detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
+ detail.average_rating = detail.average_rating * number_of_stars if detail.average_rating else 0
+
interview_detail_map[detail.name] = detail
- return interview_detail_map
+ return {"interviews": interview_detail_map, "stars": number_of_stars}
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html
index c286787..734b2fe 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html
@@ -17,24 +17,33 @@
<td class="text-left"> {%= key %} </td>
<td class="text-left"> {%= value["interview_round"] %} </td>
<td class="text-left"> {%= value["status"] %} </td>
- <td class="text-left">
- {% for (i = 0; i < value["expected_average_rating"]; i++) { %}
- <span class="fa fa-star " style="color: #F6C35E;"></span>
- {% } %}
- {% for (i = 0; i < (5-value["expected_average_rating"]); i++) { %}
- <span class="fa fa-star " style="color: #E7E9EB;"></span>
- {% } %}
- </td>
- <td class="text-left">
- {% if(value["average_rating"]){ %}
- {% for (i = 0; i < value["average_rating"]; i++) { %}
- <span class="fa fa-star " style="color: #F6C35E;"></span>
- {% } %}
- {% for (i = 0; i < (5-value["average_rating"]); i++) { %}
- <span class="fa fa-star " style="color: #E7E9EB;"></span>
- {% } %}
- {% } %}
- </td>
+ {% let right_class = ''; %}
+ {% let left_class = ''; %}
+
+ {% $.each([value["expected_average_rating"], value["average_rating"]], (_, val) => { %}
+ <td class="text-left">
+ <div class="rating">
+ {% for (let i = 1; i <= number_of_stars; i++) { %}
+ {% if (i <= val) { %}
+ {% right_class = 'star-click'; %}
+ {% } else { %}
+ {% right_class = ''; %}
+ {% } %}
+
+ {% if ((i <= val) || ((i - 0.5) == val)) { %}
+ {% left_class = 'star-click'; %}
+ {% } else { %}
+ {% left_class = ''; %}
+ {% } %}
+
+ <svg class="icon icon-md" data-rating={{i}} viewBox="0 0 24 24" fill="none">
+ <path class="right-half {{ right_class }}" d="M11.9987 3.00011C12.177 3.00011 12.3554 3.09303 12.4471 3.27888L14.8213 8.09112C14.8941 8.23872 15.0349 8.34102 15.1978 8.3647L20.5069 9.13641C20.917 9.19602 21.0807 9.69992 20.7841 9.9892L16.9421 13.7354C16.8243 13.8503 16.7706 14.0157 16.7984 14.1779L17.7053 19.4674C17.7753 19.8759 17.3466 20.1874 16.9798 19.9945L12.2314 17.4973C12.1586 17.459 12.0786 17.4398 11.9987 17.4398V3.00011Z" fill="var(--star-fill)" stroke="var(--star-fill)"/>
+ <path class="left-half {{ left_class }}" d="M11.9987 3.00011C11.8207 3.00011 11.6428 3.09261 11.5509 3.27762L9.15562 8.09836C9.08253 8.24546 8.94185 8.34728 8.77927 8.37075L3.42887 9.14298C3.01771 9.20233 2.85405 9.70811 3.1525 9.99707L7.01978 13.7414C7.13858 13.8564 7.19283 14.0228 7.16469 14.1857L6.25116 19.4762C6.18071 19.8842 6.6083 20.1961 6.97531 20.0045L11.7672 17.5022C11.8397 17.4643 11.9192 17.4454 11.9987 17.4454V3.00011Z" fill="var(--star-fill)" stroke="var(--star-fill)"/>
+ </svg>
+ {% } %}
+ </div>
+ </td>
+ {% }); %}
</tr>
{% } %}
</tbody>
diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
index 36dcf6b..bf16220 100644
--- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
@@ -9,7 +9,26 @@
class TestJobApplicant(unittest.TestCase):
- pass
+ def test_job_applicant_naming(self):
+ applicant = frappe.get_doc({
+ "doctype": "Job Applicant",
+ "status": "Open",
+ "applicant_name": "_Test Applicant",
+ "email_id": "job_applicant_naming@example.com"
+ }).insert()
+ self.assertEqual(applicant.name, 'job_applicant_naming@example.com')
+
+ applicant = frappe.get_doc({
+ "doctype": "Job Applicant",
+ "status": "Open",
+ "applicant_name": "_Test Applicant",
+ "email_id": "job_applicant_naming@example.com"
+ }).insert()
+ self.assertEqual(applicant.name, 'job_applicant_naming@example.com-1')
+
+ def tearDown(self):
+ frappe.db.rollback()
+
def create_job_applicant(**args):
args = frappe._dict(args)
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index 39f4719..072fc73 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -78,6 +78,7 @@
"doctype": "Employee",
"field_map": {
"applicant_name": "employee_name",
+ "offer_date": "scheduled_confirmation_date"
}}
}, target_doc, set_missing_values)
return doc
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 52ee463..9ecbe01 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -237,10 +237,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-10-01 15:28:26.335104",
+ "modified": "2022-01-18 19:15:53.262536",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -278,5 +279,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "timeline_field": "employee"
-}
+ "states": [],
+ "timeline_field": "employee",
+ "title_field": "employee_name"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 1dc5b31..70250f5 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -22,6 +22,7 @@
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
from erpnext.hr.utils import (
+ get_holiday_dates_for_employee,
get_leave_period,
set_employee_name,
share_doc_with_approver,
@@ -159,33 +160,57 @@
.format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
def update_attendance(self):
- if self.status == "Approved":
- for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
- date = dt.strftime("%Y-%m-%d")
- status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
- attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
- attendance_date = date, docstatus = ('!=', 2)))
+ if self.status != "Approved":
+ return
+ holiday_dates = []
+ if not frappe.db.get_value("Leave Type", self.leave_type, "include_holiday"):
+ holiday_dates = get_holiday_dates_for_employee(self.employee, self.from_date, self.to_date)
+
+ for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
+ date = dt.strftime("%Y-%m-%d")
+ attendance_name = frappe.db.exists("Attendance", dict(employee = self.employee,
+ attendance_date = date, docstatus = ('!=', 2)))
+
+ # don't mark attendance for holidays
+ # if leave type does not include holidays within leaves as leaves
+ if date in holiday_dates:
if attendance_name:
- # update existing attendance, change absent to on leave
- doc = frappe.get_doc('Attendance', attendance_name)
- if doc.status != status:
- doc.db_set('status', status)
- doc.db_set('leave_type', self.leave_type)
- doc.db_set('leave_application', self.name)
- else:
- # make new attendance and submit it
- doc = frappe.new_doc("Attendance")
- doc.employee = self.employee
- doc.employee_name = self.employee_name
- doc.attendance_date = date
- doc.company = self.company
- doc.leave_type = self.leave_type
- doc.leave_application = self.name
- doc.status = status
- doc.flags.ignore_validate = True
- doc.insert(ignore_permissions=True)
- doc.submit()
+ # cancel and delete existing attendance for holidays
+ attendance = frappe.get_doc("Attendance", attendance_name)
+ attendance.flags.ignore_permissions = True
+ if attendance.docstatus == 1:
+ attendance.cancel()
+ frappe.delete_doc("Attendance", attendance_name, force=1)
+ continue
+
+ self.create_or_update_attendance(attendance_name, date)
+
+ def create_or_update_attendance(self, attendance_name, date):
+ status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
+
+ if attendance_name:
+ # update existing attendance, change absent to on leave
+ doc = frappe.get_doc('Attendance', attendance_name)
+ if doc.status != status:
+ doc.db_set({
+ 'status': status,
+ 'leave_type': self.leave_type,
+ 'leave_application': self.name
+ })
+ else:
+ # make new attendance and submit it
+ doc = frappe.new_doc("Attendance")
+ doc.employee = self.employee
+ doc.employee_name = self.employee_name
+ doc.attendance_date = date
+ doc.company = self.company
+ doc.leave_type = self.leave_type
+ doc.leave_application = self.name
+ doc.status = status
+ doc.flags.ignore_validate = True
+ doc.insert(ignore_permissions=True)
+ doc.submit()
def cancel_attendance(self):
if self.docstatus == 2:
diff --git a/erpnext/hr/doctype/leave_application/leave_application_email_template.html b/erpnext/hr/doctype/leave_application/leave_application_email_template.html
index 14ca41b..dae9084 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_email_template.html
+++ b/erpnext/hr/doctype/leave_application/leave_application_email_template.html
@@ -23,3 +23,8 @@
<td>{{status}}</td>
</tr>
</table>
+
+ {% set doc_link = frappe.utils.get_url_to_form('Leave Application', name) %}
+
+ <br><br>
+ <a class="btn btn-primary" href="{{ doc_link }}" target="_blank">{{ _('Open Now') }}</a>
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index f73d3e5..75e99f8 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -5,7 +5,16 @@
import frappe
from frappe.permissions import clear_user_permissions_for_doctype
-from frappe.utils import add_days, add_months, getdate, nowdate
+from frappe.utils import (
+ add_days,
+ add_months,
+ get_first_day,
+ get_last_day,
+ get_year_ending,
+ get_year_start,
+ getdate,
+ nowdate,
+)
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
@@ -19,6 +28,10 @@
create_assignment_for_multiple_employees,
)
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
+ make_holiday_list,
+ make_leave_application,
+)
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
@@ -61,13 +74,15 @@
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]:
frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
+ frappe.set_user("Administrator")
+
@classmethod
def setUpClass(cls):
set_leave_approver()
frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
def tearDown(self):
- frappe.set_user("Administrator")
+ frappe.db.rollback()
def _clear_roles(self):
frappe.db.sql("""delete from `tabHas Role` where parent in
@@ -106,6 +121,72 @@
for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
self.assertTrue(getdate(d) in dates)
+ def test_attendance_for_include_holidays(self):
+ # Case 1: leave type with 'Include holidays within leaves as leaves' enabled
+ frappe.delete_doc_if_exists("Leave Type", "Test Include Holidays", force=1)
+ leave_type = frappe.get_doc(dict(
+ leave_type_name="Test Include Holidays",
+ doctype="Leave Type",
+ include_holiday=True
+ )).insert()
+
+ date = getdate()
+ make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+
+ holiday_list = make_holiday_list()
+ frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list)
+ first_sunday = get_first_sunday(holiday_list)
+
+ leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name)
+ leave_application.reload()
+ self.assertEqual(leave_application.total_leave_days, 4)
+ self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4)
+
+ leave_application.cancel()
+
+ def test_attendance_update_for_exclude_holidays(self):
+ # Case 2: leave type with 'Include holidays within leaves as leaves' disabled
+ frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
+ leave_type = frappe.get_doc(dict(
+ leave_type_name="Test Do Not Include Holidays",
+ doctype="Leave Type",
+ include_holiday=False
+ )).insert()
+
+ date = getdate()
+ make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+
+ holiday_list = make_holiday_list()
+ frappe.db.set_value("Company", "_Test Company", "default_holiday_list", holiday_list)
+ first_sunday = get_first_sunday(holiday_list)
+
+ # already marked attendance on a holiday should be deleted in this case
+ config = {
+ "doctype": "Attendance",
+ "employee": "_T-Employee-00001",
+ "status": "Present"
+ }
+ attendance_on_holiday = frappe.get_doc(config)
+ attendance_on_holiday.attendance_date = first_sunday
+ attendance_on_holiday.save()
+
+ # already marked attendance on a non-holiday should be updated
+ attendance = frappe.get_doc(config)
+ attendance.attendance_date = add_days(first_sunday, 3)
+ attendance.save()
+
+ leave_application = make_leave_application("_T-Employee-00001", first_sunday, add_days(first_sunday, 3), leave_type.name)
+ leave_application.reload()
+ # holiday should be excluded while marking attendance
+ self.assertEqual(leave_application.total_leave_days, 3)
+ self.assertEqual(frappe.db.count("Attendance", {"leave_application": leave_application.name}), 3)
+
+ # attendance on holiday deleted
+ self.assertFalse(frappe.db.exists("Attendance", attendance_on_holiday.name))
+
+ # attendance on non-holiday updated
+ self.assertEqual(frappe.db.get_value("Attendance", attendance.name, "status"), "On Leave")
+
def test_block_list(self):
self._clear_roles()
@@ -241,7 +322,13 @@
leave_period = get_leave_period()
today = nowdate()
holiday_list = 'Test Holiday List for Optional Holiday'
- optional_leave_date = add_days(today, 7)
+ employee = get_employee()
+
+ default_holiday_list = make_holiday_list()
+ frappe.db.set_value("Company", "_Test Company", "default_holiday_list", default_holiday_list)
+ first_sunday = get_first_sunday(default_holiday_list)
+
+ optional_leave_date = add_days(first_sunday, 1)
if not frappe.db.exists('Holiday List', holiday_list):
frappe.get_doc(dict(
@@ -253,7 +340,6 @@
dict(holiday_date = optional_leave_date, description = 'Test')
]
)).insert()
- employee = get_employee()
frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list)
leave_type = 'Test Optional Type'
@@ -266,7 +352,7 @@
allocate_leaves(employee, leave_period, leave_type, 10)
- date = add_days(today, 6)
+ date = add_days(first_sunday, 2)
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
@@ -443,6 +529,7 @@
leave_policy = frappe.get_doc({
"doctype": "Leave Policy",
+ "title": "Test Leave Policy",
"leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
}).insert()
@@ -636,13 +723,13 @@
carry_forward=1)
leave_allocation.submit()
-def make_allocation_record(employee=None, leave_type=None):
+def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None):
allocation = frappe.get_doc({
"doctype": "Leave Allocation",
"employee": employee or "_T-Employee-00001",
"leave_type": leave_type or "_Test Leave Type",
- "from_date": "2013-01-01",
- "to_date": "2019-12-31",
+ "from_date": from_date or "2013-01-01",
+ "to_date": to_date or "2019-12-31",
"new_leaves_allocated": 30
})
@@ -691,3 +778,16 @@
}).insert()
allocate_leave.submit()
+
+
+def get_first_sunday(holiday_list):
+ month_start_date = get_first_day(nowdate())
+ month_end_date = get_last_day(nowdate())
+ first_sunday = frappe.db.sql("""
+ select holiday_date from `tabHoliday`
+ where parent = %s
+ and holiday_date between %s and %s
+ order by holiday_date
+ """, (holiday_list, month_start_date, month_end_date))[0][0]
+
+ return first_sunday
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
index 1f6c03f..cc4e53e 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -154,10 +154,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:32:55.492327",
+ "modified": "2022-01-18 19:16:52.414356",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Encashment",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -218,7 +219,10 @@
"write": 1
}
],
+ "search_fields": "employee,employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_period/leave_period.json b/erpnext/hr/doctype/leave_period/leave_period.json
index 9e895c3..84ce114 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.json
+++ b/erpnext/hr/doctype/leave_period/leave_period.json
@@ -1,294 +1,108 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-LPR-.YYYY.-.#####",
- "beta": 0,
"creation": "2018-04-13 15:20:52.864288",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "from_date",
+ "to_date",
+ "is_active",
+ "column_break_3",
+ "company",
+ "optional_holiday_list"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "From Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "To Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"fieldname": "is_active",
"fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Is Active",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Is Active"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "company",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Company",
- "length": 0,
- "no_copy": 0,
"options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "optional_holiday_list",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Holiday List for Optional Leave",
- "length": 0,
- "no_copy": 0,
- "options": "Holiday List",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "options": "Holiday List"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-05-30 16:15:43.305502",
+ "links": [],
+ "modified": "2022-01-13 13:28:12.951025",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Period",
- "name_case": "",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
+ "search_fields": "from_date, to_date, company",
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy.json b/erpnext/hr/doctype/leave_policy/leave_policy.json
index 373095d..6ac8f20 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy.json
+++ b/erpnext/hr/doctype/leave_policy/leave_policy.json
@@ -1,131 +1,55 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
"autoname": "HR-LPOL-.YYYY.-.#####",
- "beta": 0,
"creation": "2018-04-13 16:06:19.507624",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "title",
+ "leave_allocations_section",
+ "leave_policy_details",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
"allow_in_quick_entry": 1,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "leave_allocations_section",
"fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Leave Allocations",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Leave Allocations"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "leave_policy_details",
"fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Leave Policy Details",
- "length": 0,
- "no_copy": 0,
"options": "Leave Policy Detail",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Amended From",
- "length": 0,
"no_copy": 1,
"options": "Leave Policy",
- "permlevel": 0,
"print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
"is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-29 08:42:53.363088",
+ "links": [],
+ "modified": "2022-01-19 13:07:40.556500",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Policy",
- "name_case": "",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -135,14 +59,10 @@
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
- "set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
@@ -154,14 +74,10 @@
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
- "set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
@@ -173,26 +89,19 @@
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
- "set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
+ "search_fields": "title",
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
index 3dbbef8..a4b8af7 100644
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
@@ -24,6 +24,7 @@
args = frappe._dict(args)
return frappe.get_doc({
"doctype": "Leave Policy",
+ "title": "Test Leave Policy",
"leave_policy_details": [{
"leave_type": args.leave_type or "_Test Leave Type",
"annual_allocation": args.annual_allocation or 10
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
index 3373350..27f0540 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
@@ -113,10 +113,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-01 17:54:01.014509",
+ "modified": "2022-01-13 13:37:11.218882",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Policy Assignment",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -164,5 +165,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
index 8b954c4..6b75817 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
@@ -48,7 +48,16 @@
if (cur_dialog.fields_dict.leave_period.value) {
me.set_effective_date();
}
- }
+ },
+ get_query() {
+ let filters = {"is_active": 1};
+ if (cur_dialog.fields_dict.company.value)
+ filters["company"] = cur_dialog.fields_dict.company.value;
+
+ return {
+ filters: filters
+ };
+ },
},
{
fieldtype: "Column Break"
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 8953a51..3b7f8ec 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -108,6 +108,7 @@
leave_policy = frappe.get_doc({
"doctype": "Leave Policy",
+ "title": "Test Leave Policy",
"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}]
}).insert()
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.json b/erpnext/hr/doctype/training_feedback/training_feedback.json
index cd967d5..ebf5a50 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.json
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.json
@@ -1,443 +1,144 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "HR-TRF-.YYYY.-.#####",
- "beta": 0,
- "creation": "2016-08-08 06:35:34.158568",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "autoname": "HR-TRF-.YYYY.-.#####",
+ "creation": "2016-08-08 06:35:34.158568",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "department",
+ "course",
+ "column_break_3",
+ "training_event",
+ "event_name",
+ "trainer_name",
+ "section_break_6",
+ "feedback",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_standard_filter": 1,
+ "label": "Employee",
+ "options": "Employee",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.employee_name",
- "fieldname": "employee_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Read Only",
+ "in_global_search": 1,
+ "label": "Employee Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.department",
- "fieldname": "department",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Department",
- "length": 0,
- "no_copy": 0,
- "options": "Department",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "training_event.course",
- "fieldname": "course",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Course",
- "length": 0,
- "no_copy": 0,
- "options": "Course",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "training_event.course",
+ "fieldname": "course",
+ "fieldtype": "Link",
+ "label": "Course",
+ "options": "Course",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "training_event",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Training Event",
- "length": 0,
- "no_copy": 0,
- "options": "Training Event",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "training_event",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Training Event",
+ "options": "Training Event",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "training_event.event_name",
- "fieldname": "event_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Event Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "training_event.event_name",
+ "fieldname": "event_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Event Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "training_event.trainer_name",
- "fieldname": "trainer_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Trainer Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "training_event.trainer_name",
+ "fieldname": "trainer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Trainer Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "feedback",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Feedback",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "feedback",
+ "fieldtype": "Text",
+ "label": "Feedback",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Training Feedback",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Training Feedback",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-01-30 11:28:13.849860",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Training Feedback",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-01-18 19:32:20.805277",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Training Feedback",
+ "naming_rule": "Expression (old style)",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "HR Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Employee",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "employee_name",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "employee_name, training_event, event_name",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/training_result/training_result.json b/erpnext/hr/doctype/training_result/training_result.json
index dd7abd7..f28669e 100644
--- a/erpnext/hr/doctype/training_result/training_result.json
+++ b/erpnext/hr/doctype/training_result/training_result.json
@@ -1,226 +1,83 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "HR-TRR-.YYYY.-.#####",
- "beta": 0,
- "creation": "2016-11-04 02:13:48.407576",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "HR-TRR-.YYYY.-.#####",
+ "creation": "2016-11-04 02:13:48.407576",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "training_event",
+ "section_break_3",
+ "employees",
+ "amended_from",
+ "employee_emails"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "training_event",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Training Event",
- "length": 0,
- "no_copy": 0,
- "options": "Training Event",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "training_event",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Training Event",
+ "options": "Training Event",
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_3",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employees",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employees",
- "length": 0,
- "no_copy": 0,
- "options": "Training Result Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employees",
+ "fieldtype": "Table",
+ "label": "Employees",
+ "options": "Training Result Employee"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Training Result",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Training Result",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee_emails",
- "fieldtype": "Small Text",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee Emails",
- "length": 0,
- "no_copy": 0,
- "options": "Email",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "employee_emails",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Employee Emails",
+ "options": "Email"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-21 16:15:47.614563",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Training Result",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-01-18 19:31:44.900034",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Training Result",
+ "naming_rule": "Expression (old style)",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "HR Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "training_event",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "training_event",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "training_event"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/travel_request/travel_request.json b/erpnext/hr/doctype/travel_request/travel_request.json
index 441907c..7908e1a 100644
--- a/erpnext/hr/doctype/travel_request/travel_request.json
+++ b/erpnext/hr/doctype/travel_request/travel_request.json
@@ -216,10 +216,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2019-12-12 18:42:26.451359",
+ "modified": "2022-01-18 19:19:33.678664",
"modified_by": "Administrator",
"module": "HR",
"name": "Travel Request",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -235,7 +236,10 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index 85e641c..30cec1b 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -5,7 +5,7 @@
"label": "Outgoing Salary"
}
],
- "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Human Resource\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Employee\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leave Application\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Attendance\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Applicant\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Monthly Attendance Sheet\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Lifecycle\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Exit\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Shift Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Leaves\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Expense Claims\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loans\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Recruitment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Performance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fleet Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Training\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Human Resource\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Employee\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leave Application\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Attendance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Applicant\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Monthly Attendance Sheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Lifecycle\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Exit\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Shift Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Leaves\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Expense Claims\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loans\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Recruitment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Performance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fleet Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Training\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:48:58.322521",
"docstatus": 0,
"doctype": "Workspace",
@@ -1642,7 +1642,7 @@
"type": "Link"
}
],
- "modified": "2021-12-05 22:05:13.004462",
+ "modified": "2022-01-13 17:38:45.489128",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
@@ -1651,7 +1651,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 14,
+ "sequence_id": 14.0,
"shortcuts": [
{
"color": "Green",
diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json
index 7deee0d..b08a85e 100644
--- a/erpnext/loan_management/workspace/loan_management/loan_management.json
+++ b/erpnext/loan_management/workspace/loan_management/loan_management.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Processes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Disbursement and Repayment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Security\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan Application\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Processes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Disbursement and Repayment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Security\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-12 16:35:55.299820",
"docstatus": 0,
"doctype": "Workspace",
@@ -238,7 +238,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:18:13.350905",
+ "modified": "2022-01-13 17:39:16.790152",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loans",
@@ -247,7 +247,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 16,
+ "sequence_id": 16.0,
"shortcuts": [
{
"color": "Green",
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 2ffae1a..07d928c 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-
import frappe
from frappe import _, throw
from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate
@@ -306,13 +305,18 @@
return schedule.name
@frappe.whitelist()
-def update_serial_nos(s_id):
- serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no')
+def get_serial_nos_from_schedule(item_code, schedule=None):
+ serial_nos = []
+ if schedule:
+ serial_nos = frappe.db.get_value('Maintenance Schedule Item', {
+ 'parent': schedule,
+ 'item_code': item_code
+ }, 'serial_no')
+
if serial_nos:
serial_nos = get_serial_nos(serial_nos)
- return serial_nos
- else:
- return False
+
+ return serial_nos
@frappe.whitelist()
def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
@@ -320,12 +324,9 @@
def update_status_and_detail(source, target, parent):
target.maintenance_type = "Scheduled"
- target.maintenance_schedule = source.name
target.maintenance_schedule_detail = s_id
- def update_sales_and_serial(source, target, parent):
- sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person')
- target.service_person = sales_person
+ def update_serial(source, target, parent):
serial_nos = get_serial_nos(target.serial_no)
if len(serial_nos) == 1:
target.serial_no = serial_nos[0]
@@ -346,7 +347,10 @@
"Maintenance Schedule Item": {
"doctype": "Maintenance Visit Purpose",
"condition": lambda doc: doc.item_name == item_name,
- "postprocess": update_sales_and_serial
+ "field_map": {
+ "sales_person": "service_person"
+ },
+ "postprocess": update_serial
}
}, target_doc)
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
index 5017126..6e727e5 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
@@ -4,11 +4,15 @@
import unittest
import frappe
+from frappe.utils import format_date
from frappe.utils.data import add_days, formatdate, today
from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import (
+ get_serial_nos_from_schedule,
make_maintenance_visit,
)
+from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
# test_records = frappe.get_test_records('Maintenance Schedule')
@@ -79,6 +83,49 @@
#checks if visit status is back updated in schedule
self.assertTrue(ms.schedules[1].completion_status, "Partially Completed")
+ self.assertEqual(format_date(visit.mntc_date), format_date(ms.schedules[1].actual_date))
+
+ #checks if visit status is updated on cancel
+ visit.cancel()
+ ms.reload()
+ self.assertTrue(ms.schedules[1].completion_status, "Pending")
+ self.assertEqual(ms.schedules[1].actual_date, None)
+
+ def test_serial_no_filters(self):
+ # Without serial no. set in schedule -> returns None
+ item_code = "_Test Serial Item"
+ make_serial_item_with_serial(item_code)
+ ms = make_maintenance_schedule(item_code=item_code)
+ ms.submit()
+
+ s_item = ms.schedules[0]
+ mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name)
+ mvi = mv.purposes[0]
+ serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name)
+ self.assertEqual(serial_nos, None)
+
+ # With serial no. set in schedule -> returns serial nos.
+ make_serial_item_with_serial(item_code)
+ ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002")
+ ms.submit()
+
+ s_item = ms.schedules[0]
+ mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name)
+ mvi = mv.purposes[0]
+ serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name)
+ self.assertEqual(serial_nos, ["TEST001", "TEST002"])
+
+ frappe.db.rollback()
+
+def make_serial_item_with_serial(item_code):
+ serial_item_doc = create_item(item_code, is_stock_item=1)
+ if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series:
+ serial_item_doc.has_serial_no = 1
+ serial_item_doc.serial_no_series = "TEST.###"
+ serial_item_doc.save(ignore_permissions=True)
+ active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code})
+ if len(active_serials) < 2:
+ make_serialized_item(item_code=item_code)
def get_events(ms):
return frappe.get_all("Event Participants", filters={
@@ -87,17 +134,18 @@
"parenttype": "Event"
})
-def make_maintenance_schedule():
+def make_maintenance_schedule(**args):
ms = frappe.new_doc("Maintenance Schedule")
ms.company = "_Test Company"
ms.customer = "_Test Customer"
ms.transaction_date = today()
ms.append("items", {
- "item_code": "_Test Item",
+ "item_code": args.get("item_code") or "_Test Item",
"start_date": today(),
"periodicity": "Weekly",
"no_of_visits": 4,
+ "serial_no": args.get("serial_no"),
"sales_person": "Sales Team",
})
ms.insert(ignore_permissions=True)
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index d2197a6..72686e7 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -2,52 +2,54 @@
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.maintenance");
-var serial_nos = [];
frappe.ui.form.on('Maintenance Visit', {
- refresh: function (frm) {
- //filters for serial_no based on item_code
- frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) {
- let item = locals[cdt][cdn];
- if (serial_nos) {
- return {
- filters: {
- 'item_code': item.item_code,
- 'name': ["in", serial_nos]
- }
- };
- } else {
- return {
- filters: {
- 'item_code': item.item_code
- }
- };
- }
- });
- },
setup: function (frm) {
frm.set_query('contact_person', erpnext.queries.contact_query);
frm.set_query('customer_address', erpnext.queries.address_query);
frm.set_query('customer', erpnext.queries.customer);
},
- onload: function (frm, cdt, cdn) {
- let item = locals[cdt][cdn];
+ onload: function (frm) {
+ // filters for serial no based on item code
if (frm.doc.maintenance_type === "Scheduled") {
- const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail;
+ let item_code = frm.doc.purposes[0].item_code;
frappe.call({
- method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos",
+ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule",
args: {
- s_id: schedule_id
- },
- callback: function (r) {
- serial_nos = r.message;
+ schedule: frm.doc.maintenance_schedule,
+ item_code: item_code
}
+ }).then((r) => {
+ let serial_nos = r.message;
+ frm.set_query('serial_no', 'purposes', () => {
+ if (serial_nos.length > 0) {
+ return {
+ filters: {
+ 'item_code': item_code,
+ 'name': ["in", serial_nos]
+ }
+ };
+ }
+ return {
+ filters: {
+ 'item_code': item_code
+ }
+ };
+ });
+ });
+ } else {
+ frm.set_query('serial_no', 'purposes', (frm, cdt, cdn) => {
+ let row = locals[cdt][cdn];
+ return {
+ filters: {
+ 'item_code': row.item_code
+ }
+ };
});
}
if (!frm.doc.status) {
frm.set_value({ status: 'Draft' });
}
if (frm.doc.__islocal) {
- frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes");
frm.set_value({ mntc_date: frappe.datetime.get_today() });
}
},
@@ -60,7 +62,6 @@
contact_person: function (frm) {
erpnext.utils.get_contact_details(frm);
}
-
})
// TODO commonify this code
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
index ec32239..4a6aa0a 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
@@ -179,8 +179,7 @@
"label": "Purposes",
"oldfieldname": "maintenance_visit_details",
"oldfieldtype": "Table",
- "options": "Maintenance Visit Purpose",
- "reqd": 1
+ "options": "Maintenance Visit Purpose"
},
{
"fieldname": "more_info",
@@ -294,10 +293,11 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-27 16:06:17.352572",
+ "modified": "2021-12-17 03:10:27.608112",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 5a87b16..6fe2466 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -4,7 +4,7 @@
import frappe
from frappe import _
-from frappe.utils import get_datetime
+from frappe.utils import format_date, get_datetime
from erpnext.utilities.transaction_base import TransactionBase
@@ -18,25 +18,34 @@
if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
frappe.throw(_("Serial No {0} does not exist").format(d.serial_no))
+ def validate_purpose_table(self):
+ if not self.purposes:
+ frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required")
+
def validate_maintenance_date(self):
if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference')
if item_ref:
start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date'])
if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date):
- frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date))
+ frappe.throw(_("Date must be between {0} and {1}")
+ .format(format_date(start_date), format_date(end_date)))
+
def validate(self):
self.validate_serial_no()
self.validate_maintenance_date()
+ self.validate_purpose_table()
- def update_completion_status(self):
+ def update_status_and_actual_date(self, cancel=False):
+ status = "Pending"
+ actual_date = None
+ if not cancel:
+ status = self.completion_status
+ actual_date = self.mntc_date
if self.maintenance_schedule_detail:
- frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status)
-
- def update_actual_date(self):
- if self.maintenance_schedule_detail:
- frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date)
+ frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', status)
+ frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', actual_date)
def update_customer_issue(self, flag):
if not self.maintenance_schedule:
@@ -97,12 +106,12 @@
def on_submit(self):
self.update_customer_issue(1)
frappe.db.set(self, 'status', 'Submitted')
- self.update_completion_status()
- self.update_actual_date()
+ self.update_status_and_actual_date()
def on_cancel(self):
self.check_if_last_visit()
frappe.db.set(self, 'status', 'Cancelled')
+ self.update_status_and_actual_date(cancel=True)
def on_update(self):
pass
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 6d35d65..fc3b971 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -331,7 +331,7 @@
});
});
- if (has_template_rm) {
+ if (has_template_rm && has_template_rm.length) {
dialog.fields_dict.items.grid.refresh();
}
},
@@ -467,7 +467,8 @@
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor,
- "sourced_by_supplier": d.sourced_by_supplier
+ "sourced_by_supplier": d.sourced_by_supplier,
+ "do_not_explode": d.do_not_explode
},
callback: function(r) {
d = locals[cdt][cdn];
@@ -640,6 +641,13 @@
});
});
+frappe.ui.form.on("BOM Item", {
+ do_not_explode: function(frm, cdt, cdn) {
+ get_bom_material_detail(frm.doc, cdt, cdn, false);
+ }
+})
+
+
frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
d.stock_qty = d.qty * d.conversion_factor;
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 218ac64..0b44196 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -37,7 +37,6 @@
"inspection_required",
"quality_inspection_template",
"column_break_31",
- "bom_level",
"section_break_33",
"items",
"scrap_section",
@@ -523,13 +522,6 @@
"fieldtype": "Column Break"
},
{
- "default": "0",
- "fieldname": "bom_level",
- "fieldtype": "Int",
- "label": "BOM Level",
- "read_only": 1
- },
- {
"fieldname": "section_break_33",
"fieldtype": "Section Break",
"hide_border": 1
@@ -540,7 +532,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2021-11-18 13:04:16.271975",
+ "modified": "2022-01-30 21:27:54.727298",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
@@ -577,5 +569,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 5a60fb7..d640f3f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -149,12 +149,12 @@
self.set_bom_material_details()
self.set_bom_scrap_items_detail()
self.validate_materials()
+ self.validate_transfer_against()
self.set_routing_operations()
self.validate_operations()
self.calculate_cost()
self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
- self.set_bom_level()
self.validate_scrap_items()
def get_context(self, context):
@@ -203,6 +203,10 @@
for item in self.get("items"):
self.validate_bom_currency(item)
+ item.bom_no = ''
+ if not item.do_not_explode:
+ item.bom_no = item.bom_no
+
ret = self.get_bom_material_detail({
"company": self.company,
"item_code": item.item_code,
@@ -214,8 +218,10 @@
"uom": item.uom,
"stock_uom": item.stock_uom,
"conversion_factor": item.conversion_factor,
- "sourced_by_supplier": item.sourced_by_supplier
+ "sourced_by_supplier": item.sourced_by_supplier,
+ "do_not_explode": item.do_not_explode
})
+
for r in ret:
if not item.get(r):
item.set(r, ret[r])
@@ -267,6 +273,9 @@
'sourced_by_supplier' : args.get('sourced_by_supplier', 0)
}
+ if args.get('do_not_explode'):
+ ret_item['bom_no'] = ''
+
return ret_item
def validate_bom_currency(self, item):
@@ -681,6 +690,12 @@
if act_pbom and act_pbom[0][0]:
frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
+ def validate_transfer_against(self):
+ if not self.with_operations:
+ self.transfer_material_against = "Work Order"
+ if not self.transfer_material_against and not self.is_new():
+ frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+
def set_routing_operations(self):
if self.routing and self.with_operations and not self.operations:
self.get_routing()
@@ -700,20 +715,6 @@
"""Get a complete tree representation preserving order of child items."""
return BOMTree(self.name)
- def set_bom_level(self, update=False):
- levels = []
-
- self.bom_level = 0
- for row in self.items:
- if row.bom_no:
- levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0)
-
- if levels:
- self.bom_level = max(levels) + 1
-
- if update:
- self.db_set("bom_level", self.bom_level)
-
def validate_scrap_items(self):
for item in self.scrap_items:
msg = ""
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 178d92c..53437c8 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -385,6 +385,53 @@
self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
+ def test_exclude_exploded_items_from_bom(self):
+ bom_no = get_default_bom()
+ new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no))
+ for row in new_bom.items:
+ if row.item_code == '_Test Item Home Desktop Manufactured':
+ self.assertTrue(row.bom_no)
+ row.do_not_explode = True
+
+ new_bom.docstatus = 0
+ new_bom.save()
+ new_bom.load_from_db()
+
+ for row in new_bom.items:
+ if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode:
+ self.assertFalse(row.bom_no)
+
+ new_bom.delete()
+
+ def test_valid_transfer_defaults(self):
+ bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1})
+ bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False)
+
+ # test defaults
+ bom.docstatus = 0
+ bom.transfer_material_against = None
+ bom.insert()
+ self.assertEqual(bom.transfer_material_against, "Work Order")
+
+ bom.reload()
+ bom.transfer_material_against = None
+ with self.assertRaises(frappe.ValidationError):
+ bom.save()
+ bom.reload()
+
+ # test saner default
+ bom.transfer_material_against = "Job Card"
+ bom.with_operations = 0
+ bom.save()
+ self.assertEqual(bom.transfer_material_against, "Work Order")
+
+ # test no value on existing doc
+ bom.transfer_material_against = None
+ bom.with_operations = 0
+ bom.save()
+ self.assertEqual(bom.transfer_material_against, "Work Order")
+ bom.delete()
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 4c9877f..3406215 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -10,6 +10,7 @@
"item_name",
"operation",
"column_break_3",
+ "do_not_explode",
"bom_no",
"source_warehouse",
"allow_alternative_item",
@@ -73,6 +74,7 @@
"fieldtype": "Column Break"
},
{
+ "depends_on": "eval:!doc.do_not_explode",
"fieldname": "bom_no",
"fieldtype": "Link",
"in_filter": 1,
@@ -284,18 +286,25 @@
"fieldname": "sourced_by_supplier",
"fieldtype": "Check",
"label": "Sourced by Supplier"
+ },
+ {
+ "default": "0",
+ "fieldname": "do_not_explode",
+ "fieldtype": "Check",
+ "label": "Do Not Explode"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-10-08 14:19:37.563300",
+ "modified": "2022-01-24 16:57:57.020232",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 7cec7f5..4290ca3 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -559,9 +559,11 @@
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
- def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
- bom_data = sorted(bom_data, key = lambda i: i.bom_level)
+ self.sub_assembly_items.sort(key= lambda d: d.bom_level, reverse=True)
+ for idx, row in enumerate(self.sub_assembly_items, start=1):
+ row.idx = idx
+ def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
@@ -947,11 +949,8 @@
locations = get_available_item_locations(item.get("item_code"),
warehouses, item.get("quantity"), company, ignore_validation=True)
- if not locations:
- new_mr_items.append(item)
- return
-
required_qty = item.get("quantity")
+ # get available material by transferring to production warehouse
for d in locations:
if required_qty <=0: return
@@ -962,14 +961,34 @@
new_dict.update({
"quantity": quantity,
"material_request_type": "Material Transfer",
+ "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
"from_warehouse": d.get("warehouse")
})
required_qty -= quantity
new_mr_items.append(new_dict)
+ # raise purchase request for remaining qty
if required_qty:
+ stock_uom, purchase_uom = frappe.db.get_value(
+ 'Item',
+ item['item_code'],
+ ['stock_uom', 'purchase_uom']
+ )
+
+ if purchase_uom != stock_uom and purchase_uom == item['uom']:
+ conversion_factor = get_uom_conversion_factor(item['item_code'], item['uom'])
+ if not (conversion_factor or frappe.flags.show_qty_in_stock_uom):
+ frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
+ .format(purchase_uom, stock_uom, item['item_code']))
+
+ required_qty = required_qty / conversion_factor
+
+ if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
+ required_qty = ceil(required_qty)
+
item["quantity"] = required_qty
+
new_mr_items.append(item)
@frappe.whitelist()
@@ -987,9 +1006,6 @@
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
- bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level")
- if d.value else 0)
-
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
bom_data.append(frappe._dict({
'parent_item_code': parent_item_code,
@@ -1000,7 +1016,7 @@
'uom': d.stock_uom,
'bom_no': d.value,
'is_sub_contracted_item': d.is_sub_contracted_item,
- 'bom_level': bom_level,
+ 'bom_level': indent,
'indent': indent,
'stock_qty': stock_qty
}))
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 2febc1e..21a126b 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -347,6 +347,45 @@
frappe.db.rollback()
+ def test_subassmebly_sorting(self):
+ """ Test subassembly sorting in case of multiple items with nested BOMs"""
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+ prefix = "_TestLevel_"
+ boms = {
+ "Assembly": {
+ "SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+ "SubAssembly2": {"ChildPart3": {}},
+ "SubAssembly3": {"SubSubAssy1": {"ChildPart4": {}}},
+ "ChildPart5": {},
+ "ChildPart6": {},
+ "SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}},
+ },
+ "MegaDeepAssy": {
+ "SecretSubassy": {"SecretPart": {"VerySecret" : { "SuperSecret": {"Classified": {}}}},},
+ # ^ assert that this is
+ # first item in subassy table
+ }
+ }
+ create_nested_bom(boms, prefix=prefix)
+
+ items = [prefix + item_code for item_code in boms.keys()]
+ plan = create_production_plan(item_code=items[0], do_not_save=True)
+ plan.append("po_items", {
+ 'use_multi_level_bom': 1,
+ 'item_code': items[1],
+ 'bom_no': frappe.db.get_value('Item', items[1], 'default_bom'),
+ 'planned_qty': 1,
+ 'planned_start_date': now_datetime()
+ })
+ plan.get_sub_assembly_items()
+
+ bom_level_order = [d.bom_level for d in plan.sub_assembly_items]
+ self.assertEqual(bom_level_order, sorted(bom_level_order, reverse=True))
+ # lowest most level of subassembly should be first
+ self.assertIn("SuperSecret", plan.sub_assembly_items[0].production_item)
+
+
def create_production_plan(**args):
args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
index 657ee35..45ea26c 100644
--- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -102,7 +102,6 @@
},
{
"columns": 1,
- "fetch_from": "bom_no.bom_level",
"fieldname": "bom_level",
"fieldtype": "Int",
"in_list_view": 1,
@@ -189,7 +188,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-06-28 20:10:56.296410",
+ "modified": "2022-01-30 21:31:10.527559",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",
@@ -198,5 +197,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 9926b15..a399edd 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
-from frappe.utils import add_months, cint, flt, now, today
+from frappe.utils import add_days, add_months, cint, flt, now, today
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
@@ -12,6 +12,7 @@
OverProductionError,
StockOverProductionError,
close_work_order,
+ make_job_card,
make_stock_entry,
stop_unstop,
)
@@ -804,6 +805,34 @@
if row.is_scrap_item:
self.assertEqual(row.qty, 1)
+ # Partial Job Card 1 with qty 10
+ wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1)
+ job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name')
+ update_job_card(job_card, 10)
+
+ stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
+ for row in stock_entry.items:
+ if row.is_scrap_item:
+ self.assertEqual(row.qty, 2)
+
+ # Partial Job Card 2 with qty 10
+ operations = []
+ wo_order.load_from_db()
+ for row in wo_order.operations:
+ n_dict = row.as_dict()
+ n_dict['qty'] = 10
+ n_dict['pending_qty'] = 10
+ operations.append(n_dict)
+
+ make_job_card(wo_order.name, operations)
+ job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name, 'docstatus': 0}, 'name')
+ update_job_card(job_card, 10)
+
+ stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
+ for row in stock_entry.items:
+ if row.is_scrap_item:
+ self.assertEqual(row.qty, 2)
+
def test_close_work_order(self):
items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO',
'Test RM Item 2 for Closed WO']
@@ -882,8 +911,57 @@
self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
+ def test_partial_manufacture_entries(self):
+ cancel_stock_entry = []
-def update_job_card(job_card):
+ frappe.db.set_value("Manufacturing Settings", None,
+ "backflush_raw_materials_based_on", "Material Transferred for Manufacture")
+
+ wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
+ ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+ target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
+ ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+
+ cancel_stock_entry.extend([ste1.name, ste2.name])
+
+ sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
+ for row in sm.get('items'):
+ if row.get('item_code') == '_Test Item':
+ row.qty = 110
+
+ sm.submit()
+ cancel_stock_entry.append(sm.name)
+
+ s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
+ for row in s.get('items'):
+ if row.get('item_code') == '_Test Item':
+ self.assertEqual(row.get('qty'), 100)
+ s.submit()
+ cancel_stock_entry.append(s.name)
+
+ s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
+ for row in s1.get('items'):
+ if row.get('item_code') == '_Test Item':
+ self.assertEqual(row.get('qty'), 5)
+ s1.submit()
+ cancel_stock_entry.append(s1.name)
+
+ s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
+ for row in s2.get('items'):
+ if row.get('item_code') == '_Test Item':
+ self.assertEqual(row.get('qty'), 5)
+
+ cancel_stock_entry.reverse()
+ for ste in cancel_stock_entry:
+ doc = frappe.get_doc("Stock Entry", ste)
+ doc.cancel()
+
+ frappe.db.set_value("Manufacturing Settings", None,
+ "backflush_raw_materials_based_on", "BOM")
+
+def update_job_card(job_card, jc_qty=None):
+ employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
job_card_doc = frappe.get_doc('Job Card', job_card)
job_card_doc.set('scrap_items', [
{
@@ -896,8 +974,12 @@
},
])
+ if jc_qty:
+ job_card_doc.for_quantity = jc_qty
+
job_card_doc.append('time_logs', {
'from_time': now(),
+ 'employee': employee,
'time_in_mins': 60,
'completed_qty': job_card_doc.for_quantity
})
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 5ffbb03..6433a99 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -131,16 +131,14 @@
erpnext.work_order.set_custom_buttons(frm);
frm.set_intro("");
- if (frm.doc.docstatus === 0 && !frm.doc.__islocal) {
+ if (frm.doc.docstatus === 0 && !frm.is_new()) {
frm.set_intro(__("Submit this Work Order for further processing."));
+ } else {
+ frm.trigger("show_progress_for_items");
+ frm.trigger("show_progress_for_operations");
}
if (frm.doc.status != "Closed") {
- if (frm.doc.docstatus===1) {
- frm.trigger('show_progress_for_items');
- frm.trigger('show_progress_for_operations');
- }
-
if (frm.doc.docstatus === 1
&& frm.doc.operations && frm.doc.operations.length) {
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 12cd58f..9452a63 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -333,12 +333,13 @@
"options": "fa fa-wrench"
},
{
- "default": "Work Order",
"depends_on": "operations",
+ "fetch_from": "bom_no.transfer_material_against",
+ "fetch_if_empty": 1,
"fieldname": "transfer_material_against",
"fieldtype": "Select",
"label": "Transfer Material Against",
- "options": "Work Order\nJob Card"
+ "options": "\nWork Order\nJob Card"
},
{
"fieldname": "operations",
@@ -574,7 +575,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2021-11-08 17:36:07.016300",
+ "modified": "2022-01-24 21:18:12.160114",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
@@ -607,6 +608,7 @@
],
"sort_field": "modified",
"sort_order": "ASC",
+ "states": [],
"title_field": "production_item",
"track_changes": 1,
"track_seen": 1
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 170454c..03e0910 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -31,6 +31,7 @@
from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
from erpnext.stock.doctype.serial_no.serial_no import (
auto_make_serial_nos,
+ clean_serial_no_string,
get_auto_serial_nos,
get_serial_nos,
)
@@ -65,6 +66,7 @@
self.validate_warehouse_belongs_to_company()
self.calculate_operating_cost()
self.validate_qty()
+ self.validate_transfer_against()
self.validate_operation_time()
self.status = self.get_status()
@@ -72,6 +74,7 @@
self.set_required_items(reset_only_qty = len(self.get("required_items")))
+
def validate_sales_order(self):
if self.sales_order:
self.check_sales_order_on_hold_or_close()
@@ -356,6 +359,7 @@
frappe.delete_doc("Batch", row.name)
def make_serial_nos(self, args):
+ self.serial_no = clean_serial_no_string(self.serial_no)
serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
if serial_no_series:
self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
@@ -625,6 +629,16 @@
if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0."))
+ def validate_transfer_against(self):
+ if not self.docstatus == 1:
+ # let user configure operations until they're ready to submit
+ return
+ if not self.operations:
+ self.transfer_material_against = "Work Order"
+ if not self.transfer_material_against:
+ frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+
+
def validate_operation_time(self):
for d in self.operations:
if not d.time_in_mins > 0:
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 25de2e0..19a80ab 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -26,8 +26,7 @@
'item_code': item.item_code,
'item_name': item.item_name,
'indent': indent,
- 'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level")
- if item.bom_no else ""),
+ 'bom_level': indent,
'bom': item.bom_no,
'qty': item.qty * qty,
'uom': item.uom,
@@ -73,7 +72,7 @@
},
{
"label": "BOM Level",
- "fieldtype": "Data",
+ "fieldtype": "Int",
"fieldname": "bom_level",
"width": 100
},
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
index 7468e34..0eb22a2 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -4,6 +4,39 @@
frappe.query_reports["BOM Operations Time"] = {
"filters": [
-
+ {
+ "fieldname": "item_code",
+ "label": __("Item Code"),
+ "fieldtype": "Link",
+ "width": "100",
+ "options": "Item",
+ "get_query": () =>{
+ return {
+ filters: { "disabled": 0, "is_stock_item": 1 }
+ }
+ }
+ },
+ {
+ "fieldname": "bom_id",
+ "label": __("BOM ID"),
+ "fieldtype": "MultiSelectList",
+ "width": "100",
+ "options": "BOM",
+ "get_data": function(txt) {
+ return frappe.db.get_link_options("BOM", txt);
+ },
+ "get_query": () =>{
+ return {
+ filters: { "docstatus": 1, "is_active": 1, "with_operations": 1 }
+ }
+ }
+ },
+ {
+ "fieldname": "workstation",
+ "label": __("Workstation"),
+ "fieldtype": "Link",
+ "width": "100",
+ "options": "Workstation"
+ },
]
};
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
index 665c5b9..8162017 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
@@ -1,14 +1,16 @@
{
- "add_total_row": 0,
+ "add_total_row": 1,
+ "columns": [],
"creation": "2020-03-03 01:41:20.862521",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
+ "filters": [],
"idx": 0,
"is_standard": "Yes",
"letter_head": "",
- "modified": "2020-03-03 01:41:20.862521",
+ "modified": "2022-01-20 14:21:47.771591",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operations Time",
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
index e7a818a..eda9eb9 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -12,19 +12,15 @@
return columns, data
def get_data(filters):
- data = []
+ bom_wise_data = {}
+ bom_data, report_data = [], []
- bom_data = []
- for d in frappe.db.sql("""
- SELECT
- bom.name, bom.item, bom.item_name, bom.uom,
- bomps.operation, bomps.workstation, bomps.time_in_mins
- FROM `tabBOM` bom, `tabBOM Operation` bomps
- WHERE
- bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
- """, as_dict=1):
+ bom_operation_data = get_filtered_data(filters)
+
+ for d in bom_operation_data:
row = get_args()
if d.name not in bom_data:
+ bom_wise_data[d.name] = []
bom_data.append(d.name)
row.update(d)
else:
@@ -34,14 +30,49 @@
"time_in_mins": d.time_in_mins
})
- data.append(row)
+ # maintain BOM wise data for grouping such as:
+ # {"BOM A": [{Row1}, {Row2}], "BOM B": ...}
+ bom_wise_data[d.name].append(row)
used_as_subassembly_items = get_bom_count(bom_data)
- for d in data:
- d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
+ for d in bom_wise_data:
+ for row in bom_wise_data[d]:
+ row.used_as_subassembly_items = used_as_subassembly_items.get(row.name, 0)
+ report_data.append(row)
- return data
+ return report_data
+
+def get_filtered_data(filters):
+ bom = frappe.qb.DocType("BOM")
+ bom_ops = frappe.qb.DocType("BOM Operation")
+
+ bom_ops_query = (
+ frappe.qb.from_(bom)
+ .join(bom_ops).on(bom.name == bom_ops.parent)
+ .select(
+ bom.name, bom.item, bom.item_name, bom.uom,
+ bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins
+ ).where(
+ (bom.docstatus == 1)
+ & (bom.is_active == 1)
+ )
+ )
+
+ if filters.get("item_code"):
+ bom_ops_query = bom_ops_query.where(bom.item == filters.get("item_code"))
+
+ if filters.get("bom_id"):
+ bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id")))
+
+ if filters.get("workstation"):
+ bom_ops_query = bom_ops_query.where(
+ bom_ops.workstation == filters.get("workstation")
+ )
+
+ bom_operation_data = bom_ops_query.run(as_dict=True)
+
+ return bom_operation_data
def get_bom_count(bom_data):
data = frappe.get_all("BOM Item",
@@ -68,13 +99,13 @@
"options": "BOM",
"fieldname": "name",
"fieldtype": "Link",
- "width": 140
+ "width": 220
}, {
- "label": _("BOM Item Code"),
+ "label": _("Item Code"),
"options": "Item",
"fieldname": "item",
"fieldtype": "Link",
- "width": 140
+ "width": 150
}, {
"label": _("Item Name"),
"fieldname": "item_name",
@@ -85,13 +116,13 @@
"options": "UOM",
"fieldname": "uom",
"fieldtype": "Link",
- "width": 140
+ "width": 100
}, {
"label": _("Operation"),
"options": "Operation",
"fieldname": "operation",
"fieldtype": "Link",
- "width": 120
+ "width": 140
}, {
"label": _("Workstation"),
"options": "Workstation",
@@ -101,11 +132,11 @@
}, {
"label": _("Time (In Mins)"),
"fieldname": "time_in_mins",
- "fieldtype": "Int",
- "width": 140
+ "fieldtype": "Float",
+ "width": 120
}, {
"label": _("Sub-assembly BOM Count"),
"fieldname": "used_as_subassembly_items",
"fieldtype": "Int",
- "width": 180
+ "width": 200
}]
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
index 97e7e0a..72eed5e 100644
--- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
@@ -17,14 +17,12 @@
fieldname:"from_date",
fieldtype: "Datetime",
default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
- reqd: 1
},
{
label: __("To Date"),
fieldname:"to_date",
fieldtype: "Datetime",
default: frappe.datetime.now_datetime(),
- reqd: 1,
},
{
label: __("Job Card"),
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
index 7741823..88b2117 100644
--- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
@@ -3,46 +3,65 @@
import frappe
from frappe import _
-from frappe.utils import flt
def execute(filters=None):
- columns, data = [], []
+ return get_columns(filters), get_data(filters)
- columns = get_columns(filters)
- data = get_data(filters)
-
- return columns, data
def get_data(report_filters):
data = []
operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1})
if operations:
- operations = [d.name for d in operations]
- fields = ["production_item as item_code", "item_name", "work_order", "operation",
- "workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"]
+ if report_filters.get('operation'):
+ operations = [report_filters.get('operation')]
+ else:
+ operations = [d.name for d in operations]
- filters = get_filters(report_filters, operations)
+ job_card = frappe.qb.DocType("Job Card")
- job_cards = frappe.get_all("Job Card", fields = fields,
- filters = filters)
+ operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_('operating_cost')
+ item_code = (job_card.production_item).as_('item_code')
- for row in job_cards:
- row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0)
- data.append(row)
+ query = (frappe.qb
+ .from_(job_card)
+ .select(job_card.name, job_card.work_order, item_code, job_card.item_name,
+ job_card.operation, job_card.serial_no, job_card.batch_no,
+ job_card.workstation, job_card.total_time_in_mins, job_card.hour_rate,
+ operating_cost)
+ .where(
+ (job_card.docstatus == 1)
+ & (job_card.is_corrective_job_card == 1))
+ .groupby(job_card.name)
+ )
+ query = append_filters(query, report_filters, operations, job_card)
+ data = query.run(as_dict=True)
return data
-def get_filters(report_filters, operations):
- filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1}
- for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]:
- if report_filters.get(field):
- if field != 'serial_no':
- filters[field] = report_filters.get(field)
- else:
- filters[field] = ('like', '% {} %'.format(report_filters.get(field)))
+def append_filters(query, report_filters, operations, job_card):
+ """Append optional filters to query builder. """
- return filters
+ for field in ("name", "work_order", "operation", "workstation",
+ "company", "serial_no", "batch_no", "production_item"):
+ if report_filters.get(field):
+ if field == 'serial_no':
+ query = query.where(job_card[field].like('%{}%'.format(report_filters.get(field))))
+ elif field == 'operation':
+ query = query.where(job_card[field].isin(operations))
+ else:
+ query = query.where(job_card[field] == report_filters.get(field))
+
+ if report_filters.get('from_date') or report_filters.get('to_date'):
+ job_card_time_log = frappe.qb.DocType("Job Card Time Log")
+
+ query = query.join(job_card_time_log).on(job_card.name == job_card_time_log.parent)
+ if report_filters.get('from_date'):
+ query = query.where(job_card_time_log.from_time >= report_filters.get('from_date'))
+ if report_filters.get('to_date'):
+ query = query.where(job_card_time_log.to_time <= report_filters.get('to_date'))
+
+ return query
def get_columns(filters):
return [
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
index 55b1a3f..aaa2314 100644
--- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
@@ -48,7 +48,7 @@
"qty": row.planned_qty,
"document_type": "Work Order",
"document_name": work_order or "",
- "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"),
+ "bom_level": 0,
"produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
"pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0))
})
diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py
index 1de4726..9f51ded 100644
--- a/erpnext/manufacturing/report/test_reports.py
+++ b/erpnext/manufacturing/report/test_reports.py
@@ -18,7 +18,7 @@
("BOM Operations Time", {}),
("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}),
("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}),
- ("Cost of Poor Quality Report", {}),
+ ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}),
("Downtime Analysis", {}),
(
"Exponential Smoothing Forecasting",
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index 65b4d02..05ca2a8 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 17:11:37.032604",
"docstatus": 0,
"doctype": "Workspace",
@@ -402,7 +402,7 @@
"type": "Link"
}
],
- "modified": "2021-11-22 17:55:03.524496",
+ "modified": "2022-01-13 17:40:09.474747",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
@@ -411,7 +411,7 @@
"public": 1,
"restrict_to_domain": "Manufacturing",
"roles": [],
- "sequence_id": 17,
+ "sequence_id": 17.0,
"shortcuts": [
{
"color": "Green",
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index d3b7a17..c5705c1 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -14,10 +14,8 @@
Maintenance
Education
Regional
-Restaurant
ERPNext Integrations
Non Profit
-Hotels
Quality Management
Communication
Loan Management
diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json
index ba2f919..fc90475 100644
--- a/erpnext/non_profit/workspace/non_profit/non_profit.json
+++ b/erpnext/non_profit/workspace/non_profit/non_profit.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Member\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Profit Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Membership\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter Member\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Grant Application\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Membership\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Volunteer\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Donation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tax Exemption Certification (India)\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Non Profit Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Membership\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chapter\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chapter Member\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Grant Application\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Membership\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Volunteer\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Chapter\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Donation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tax Exemption Certification (India)\",\"col\":4}}]",
"creation": "2020-03-02 17:23:47.811421",
"docstatus": 0,
"doctype": "Workspace",
@@ -231,7 +231,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:01.146207",
+ "modified": "2022-01-13 17:40:50.220877",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Non Profit",
@@ -240,7 +240,7 @@
"public": 1,
"restrict_to_domain": "Non Profit",
"roles": [],
- "sequence_id": 18,
+ "sequence_id": 18.0,
"shortcuts": [
{
"label": "Member",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d2d1927..9317afa 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -1,4 +1,6 @@
+[pre_model_sync]
erpnext.patches.v12_0.update_is_cancelled_field
+erpnext.patches.v13_0.add_bin_unique_constraint
erpnext.patches.v11_0.rename_production_order_to_work_order
erpnext.patches.v11_0.refactor_naming_series
erpnext.patches.v11_0.refactor_autoname_naming
@@ -202,6 +204,7 @@
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
erpnext.patches.v12_0.create_itc_reversal_custom_fields
+execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings")
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
erpnext.patches.v12_0.add_taxjar_integration_field
@@ -222,11 +225,11 @@
erpnext.patches.v13_0.set_app_name
erpnext.patches.v13_0.print_uom_after_quantity_patch
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
+erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
erpnext.patches.v13_0.update_member_email_address
-erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
erpnext.patches.v13_0.add_po_to_global_search
@@ -242,6 +245,8 @@
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
+erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17
+erpnext.patches.v12_0.add_einvoice_summary_report_permissions
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
@@ -252,9 +257,10 @@
erpnext.patches.v12_0.purchase_receipt_status
erpnext.patches.v13_0.fix_non_unique_represents_company
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
-erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
+erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022
erpnext.patches.v13_0.update_shipment_status
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
+erpnext.patches.v12_0.add_ewaybill_validity_field
erpnext.patches.v13_0.germany_make_custom_fields
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
erpnext.patches.v13_0.set_pos_closing_as_failed
@@ -266,12 +272,11 @@
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
erpnext.patches.v13_0.update_response_by_variance
-erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.update_job_card_details
-erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
erpnext.patches.v13_0.update_subscription_status_in_memberships
erpnext.patches.v13_0.update_amt_in_work_order_required_items
+erpnext.patches.v12_0.show_einvoice_irn_cancelled_field
erpnext.patches.v13_0.delete_orphaned_tables
erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16
erpnext.patches.v13_0.update_tds_check_field #3
@@ -279,17 +284,14 @@
erpnext.patches.v13_0.update_recipient_email_digest
erpnext.patches.v13_0.shopify_deprecation_warning
erpnext.patches.v13_0.remove_bad_selling_defaults
-erpnext.patches.v13_0.trim_whitespace_from_serial_nos
+erpnext.patches.v13_0.trim_whitespace_from_serial_nos # 16-01-2022
erpnext.patches.v13_0.migrate_stripe_api
erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
-erpnext.patches.v13_0.einvoicing_deprecation_warning
execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings")
execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category")
-erpnext.patches.v14_0.delete_einvoicing_doctypes
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
-erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v13_0.fix_invoice_statuses
erpnext.patches.v13_0.create_website_items #30-09-2021
erpnext.patches.v13_0.populate_e_commerce_settings
@@ -305,9 +307,6 @@
erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry
erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
erpnext.patches.v13_0.add_default_interview_notification_templates
-erpnext.patches.v13_0.create_website_items
-erpnext.patches.v13_0.populate_e_commerce_settings
-erpnext.patches.v13_0.make_homepage_products_website_items
erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
erpnext.patches.v13_0.requeue_failed_reposts
erpnext.patches.v13_0.update_job_card_status
@@ -318,17 +317,31 @@
erpnext.patches.v14_0.delete_healthcare_doctypes
erpnext.patches.v13_0.update_category_in_ltds_certificate
erpnext.patches.v13_0.create_pan_field_for_india #2
-erpnext.patches.v14_0.delete_hub_doctypes
-erpnext.patches.v13_0.create_ksa_vat_custom_fields
erpnext.patches.v13_0.fetch_thumbnail_in_website_items
-erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
+erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
+erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022
erpnext.patches.v14_0.migrate_crm_settings
erpnext.patches.v13_0.rename_ksa_qr_field
+erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty
erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
-erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
erpnext.patches.v13_0.update_tax_category_for_rcm
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
erpnext.patches.v14_0.set_payroll_cost_centers
erpnext.patches.v13_0.agriculture_deprecation_warning
-erpnext.patches.v14_0.delete_agriculture_doctypes
+erpnext.patches.v13_0.hospitality_deprecation_warning
erpnext.patches.v13_0.update_exchange_rate_settings
+erpnext.patches.v13_0.update_asset_quantity_field
+erpnext.patches.v13_0.delete_bank_reconciliation_detail
+
+[post_model_sync]
+erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
+erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
+erpnext.patches.v14_0.delete_shopify_doctypes
+erpnext.patches.v14_0.delete_hub_doctypes
+erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
+erpnext.patches.v14_0.delete_agriculture_doctypes
+erpnext.patches.v14_0.rearrange_company_fields
+erpnext.patches.v14_0.update_leave_notification_template
+erpnext.patches.v14_0.restore_einvoice_fields
+erpnext.patches.v13_0.update_sane_transfer_against
+erpnext.patches.v12_0.add_company_link_to_einvoice_settings
diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
new file mode 100644
index 0000000..e498b67
--- /dev/null
+++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company or not frappe.db.count('E Invoice User'):
+ return
+
+ frappe.reload_doc("regional", "doctype", "e_invoice_user")
+ for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
+ company_name = frappe.db.sql("""
+ select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
+ where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
+ """, (creds.get('gstin')))
+ if company_name and len(company_name) > 0:
+ frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py
new file mode 100644
index 0000000..aeff9ca
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_status_field.py
@@ -0,0 +1,72 @@
+from __future__ import unicode_literals
+
+import json
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ # move hidden einvoice fields to a different section
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+ print_hide=1, hidden=1),
+
+ dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+ dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+ options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+ hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
+
+ if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'):
+ frappe.db.sql('''
+ UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
+ WHERE
+ posting_date >= '2021-04-01'
+ AND ifnull(irn, '') = ''
+ AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
+ AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
+ ''')
+
+ # set appropriate statuses
+ frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
+ WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''')
+
+ frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
+ WHERE ifnull(irn_cancelled, 0) = 1''')
+
+ # set correct acknowledgement in e-invoices
+ einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice'])
+
+ if einvoices:
+ for inv in einvoices:
+ signed_einvoice = inv.get('signed_einvoice')
+ if signed_einvoice:
+ signed_einvoice = json.loads(signed_einvoice)
+ frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False)
+ frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False)
diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
new file mode 100644
index 0000000..e837786
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
@@ -0,0 +1,20 @@
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ if frappe.db.exists('Report', 'E-Invoice Summary') and \
+ not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')):
+ frappe.get_doc(dict(
+ doctype='Custom Role',
+ report='E-Invoice Summary',
+ roles= [
+ dict(role='Accounts User'),
+ dict(role='Accounts Manager')
+ ]
+ )).insert()
diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
new file mode 100644
index 0000000..247140d
--- /dev/null
+++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py
new file mode 100644
index 0000000..c17666a
--- /dev/null
+++ b/erpnext/patches/v12_0/setup_einvoice_fields.py
@@ -0,0 +1,59 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+from erpnext.regional.india.setup import add_permissions, add_print_formats
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ frappe.reload_doc("custom", "doctype", "custom_field")
+ frappe.reload_doc("regional", "doctype", "e_invoice_settings")
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+ depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+
+ dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
+
+ dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+ dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+ depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+ dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+ depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+ dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
+ add_permissions()
+ add_print_formats()
+
+ einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
+ t = {
+ 'mode_of_transport': [{'default': None}],
+ 'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
+ 'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+ 'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+ 'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+ 'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+ 'ewaybill': [
+ {'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
+ {'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
+ ]
+ }
+
+ for field, conditions in t.items():
+ for c in conditions:
+ [(prop, value)] = c.items()
+ frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)
diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
new file mode 100644
index 0000000..3f90a03
--- /dev/null
+++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
@@ -0,0 +1,14 @@
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'})
+ if irn_cancelled_field:
+ frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn')
+ frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0)
diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py
index df78750..0401034 100644
--- a/erpnext/patches/v12_0/update_is_cancelled_field.py
+++ b/erpnext/patches/v12_0/update_is_cancelled_field.py
@@ -2,14 +2,28 @@
def execute():
- try:
- frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
- frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
+ #handle type casting for is_cancelled field
+ module_doctypes = (
+ ('stock', 'Stock Ledger Entry'),
+ ('stock', 'Serial No'),
+ ('accounts', 'GL Entry')
+ )
- frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 1 where is_cancelled = 'Yes'")
- frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'")
+ for module, doctype in module_doctypes:
+ if (not frappe.db.has_column(doctype, "is_cancelled")
+ or frappe.db.get_column_type(doctype, "is_cancelled").lower() == "int(1)"
+ ):
+ continue
- frappe.reload_doc("stock", "doctype", "stock_ledger_entry")
- frappe.reload_doc("stock", "doctype", "serial_no")
- except Exception:
- pass
+ frappe.db.sql("""
+ UPDATE `tab{doctype}`
+ SET is_cancelled = 0
+ where is_cancelled in ('', NULL, 'No')"""
+ .format(doctype=doctype))
+ frappe.db.sql("""
+ UPDATE `tab{doctype}`
+ SET is_cancelled = 1
+ where is_cancelled = 'Yes'"""
+ .format(doctype=doctype))
+
+ frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
diff --git a/erpnext/patches/v13_0/add_bin_unique_constraint.py b/erpnext/patches/v13_0/add_bin_unique_constraint.py
new file mode 100644
index 0000000..57fbaae
--- /dev/null
+++ b/erpnext/patches/v13_0/add_bin_unique_constraint.py
@@ -0,0 +1,63 @@
+import frappe
+
+from erpnext.stock.stock_balance import (
+ get_balance_qty_from_sle,
+ get_indented_qty,
+ get_ordered_qty,
+ get_planned_qty,
+ get_reserved_qty,
+)
+from erpnext.stock.utils import get_bin
+
+
+def execute():
+ delete_broken_bins()
+ delete_and_patch_duplicate_bins()
+
+def delete_broken_bins():
+ # delete useless bins
+ frappe.db.sql("delete from `tabBin` where item_code is null or warehouse is null")
+
+def delete_and_patch_duplicate_bins():
+
+ duplicate_bins = frappe.db.sql("""
+ SELECT
+ item_code, warehouse, count(*) as bin_count
+ FROM
+ tabBin
+ GROUP BY
+ item_code, warehouse
+ HAVING
+ bin_count > 1
+ """, as_dict=1)
+
+ for duplicate_bin in duplicate_bins:
+ item_code = duplicate_bin.item_code
+ warehouse = duplicate_bin.warehouse
+ existing_bins = frappe.get_list("Bin",
+ filters={
+ "item_code": item_code,
+ "warehouse": warehouse
+ },
+ fields=["name"],
+ order_by="creation",)
+
+ # keep last one
+ existing_bins.pop()
+
+ for broken_bin in existing_bins:
+ frappe.delete_doc("Bin", broken_bin.name)
+
+ qty_dict = {
+ "reserved_qty": get_reserved_qty(item_code, warehouse),
+ "indented_qty": get_indented_qty(item_code, warehouse),
+ "ordered_qty": get_ordered_qty(item_code, warehouse),
+ "planned_qty": get_planned_qty(item_code, warehouse),
+ "actual_qty": get_balance_qty_from_sle(item_code, warehouse)
+ }
+
+ bin = get_bin(item_code, warehouse)
+ bin.update(qty_dict)
+ bin.update_reserved_qty_for_production()
+ bin.update_reserved_qty_for_sub_contracting()
+ bin.db_update()
diff --git a/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
new file mode 100644
index 0000000..75953b0
--- /dev/null
+++ b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+import frappe
+
+
+def execute():
+
+ if frappe.db.exists('DocType', 'Bank Reconciliation Detail') and \
+ frappe.db.exists('DocType', 'Bank Clearance Detail'):
+
+ frappe.delete_doc("DocType", 'Bank Reconciliation Detail', force=1)
diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py
index c597fe8..e6eba0a 100644
--- a/erpnext/patches/v13_0/delete_old_sales_reports.py
+++ b/erpnext/patches/v13_0/delete_old_sales_reports.py
@@ -12,6 +12,7 @@
for report in reports_to_delete:
if frappe.db.exists("Report", report):
+ delete_links_from_desktop_icons(report)
delete_auto_email_reports(report)
check_and_delete_linked_reports(report)
@@ -22,3 +23,9 @@
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
+
+def delete_links_from_desktop_icons(report):
+ """ Check for one or multiple Desktop Icons and delete """
+ desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
+ for desktop_icon in desktop_icons:
+ frappe.delete_doc("Desktop Icon", desktop_icon[0])
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py b/erpnext/patches/v13_0/einvoicing_deprecation_warning.py
deleted file mode 100644
index e123a55..0000000
--- a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import click
-
-
-def execute():
- click.secho(
- "Indian E-Invoicing integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
- "Please install the app to continue using the integration: https://github.com/frappe/erpnext_gst_compliance",
- fg="yellow",
- )
diff --git a/erpnext/patches/v13_0/hospitality_deprecation_warning.py b/erpnext/patches/v13_0/hospitality_deprecation_warning.py
new file mode 100644
index 0000000..2708b2c
--- /dev/null
+++ b/erpnext/patches/v13_0/hospitality_deprecation_warning.py
@@ -0,0 +1,10 @@
+import click
+
+
+def execute():
+
+ click.secho(
+ "Hospitality domain is moved to a separate app and will be removed from ERPNext in version-14.\n"
+ "When upgrading to ERPNext version-14, please install the app to continue using the Hospitality domain: https://github.com/frappe/hospitality",
+ fg="yellow",
+ )
diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py
index a7bdf93..ff241a3 100644
--- a/erpnext/patches/v13_0/make_non_standard_user_type.py
+++ b/erpnext/patches/v13_0/make_non_standard_user_type.py
@@ -10,8 +10,15 @@
def execute():
doctype_dict = {
'projects': ['Timesheet'],
- 'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
- 'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
+ 'payroll': [
+ 'Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
+ 'Employee Benefit Application', 'Employee Benefit Claim'
+ ],
+ 'hr': [
+ 'Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request',
+ 'Holiday List', 'Employee Advance', 'Training Program', 'Training Feedback',
+ 'Shift Request', 'Employee Grievance', 'Employee Referral', 'Travel Request'
+ ]
}
for module, doctypes in doctype_dict.items():
diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
index 7a2a253..2d35ea3 100644
--- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
+++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
@@ -5,6 +5,9 @@
def execute():
if frappe.get_all('Company', filters = {'country': 'India'}):
+ frappe.reload_doc('accounts', 'doctype', 'POS Invoice')
+ frappe.reload_doc('accounts', 'doctype', 'POS Invoice Item')
+
make_custom_fields()
if not frappe.db.exists('Party Type', 'Donor'):
diff --git a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
index 8a9633d..4ec22e9 100644
--- a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
+++ b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
@@ -9,13 +9,15 @@
from `tabStock Ledger Entry`
where
is_cancelled = 0
- and (serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s)
+ and ( serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s
+ or serial_no = %s )
""",
(
" %", # leading whitespace
"% ", # trailing whitespace
"%\n %", # leading whitespace on newline
"% \n%", # trailing whitespace on newline
+ "\n", # just new line
),
as_dict=True,
)
diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
index 55fd465..60466eb 100644
--- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -37,4 +37,4 @@
jc.production_item = wo.production_item, jc.item_name = wo.item_name
WHERE
jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
- """)
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_asset_quantity_field.py b/erpnext/patches/v13_0/update_asset_quantity_field.py
new file mode 100644
index 0000000..47884d1
--- /dev/null
+++ b/erpnext/patches/v13_0/update_asset_quantity_field.py
@@ -0,0 +1,8 @@
+import frappe
+
+
+def execute():
+ if frappe.db.count('Asset'):
+ frappe.reload_doc("assets", "doctype", "Asset")
+ asset = frappe.qb.DocType('Asset')
+ frappe.qb.update(asset).set(asset.asset_quantity, 1).run()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py
deleted file mode 100644
index 499412e..0000000
--- a/erpnext/patches/v13_0/update_level_in_bom.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (c) 2020, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-
-def execute():
- for document in ["bom", "bom_item", "bom_explosion_item"]:
- frappe.reload_doc('manufacturing', 'doctype', document)
-
- frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1")
-
- bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
- where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item`
- where parent=bom.name and ifnull(bom_no, '')!='')""")
-
- count = 0
- while(count < len(bom_list)):
- for parent_bom in get_parent_boms(bom_list[count]):
- bom_doc = frappe.get_cached_doc("BOM", parent_bom)
- bom_doc.set_bom_level(update=True)
- bom_list.append(parent_bom)
- count += 1
-
-def get_parent_boms(bom_no):
- return frappe.db.sql_list("""
- select distinct bom_item.parent from `tabBOM Item` bom_item
- where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
- and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
- """, bom_no)
diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
new file mode 100644
index 0000000..450c00e
--- /dev/null
+++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
@@ -0,0 +1,22 @@
+
+import frappe
+
+
+def execute():
+ # Updates the Maintenance Schedule link to fetch serial nos
+ from frappe.query_builder.functions import Coalesce
+ mvp = frappe.qb.DocType('Maintenance Visit Purpose')
+ mv = frappe.qb.DocType('Maintenance Visit')
+
+ frappe.qb.update(
+ mv
+ ).join(
+ mvp
+ ).on(mvp.parent == mv.name).set(
+ mv.maintenance_schedule,
+ Coalesce(mvp.prevdoc_docname, '')
+ ).where(
+ (mv.maintenance_type == "Scheduled")
+ & (mvp.prevdoc_docname.notnull())
+ & (mv.docstatus < 2)
+ ).run(as_dict=1)
diff --git a/erpnext/patches/v13_0/update_sane_transfer_against.py b/erpnext/patches/v13_0/update_sane_transfer_against.py
new file mode 100644
index 0000000..a163d38
--- /dev/null
+++ b/erpnext/patches/v13_0/update_sane_transfer_against.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+ bom = frappe.qb.DocType("BOM")
+
+ (frappe.qb
+ .update(bom)
+ .set(bom.transfer_material_against, "Work Order")
+ .where(bom.with_operations == 0)
+ ).run()
diff --git a/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
new file mode 100644
index 0000000..e43a8ba
--- /dev/null
+++ b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
@@ -0,0 +1,18 @@
+import frappe
+
+
+def execute():
+
+ doctype = "Stock Reconciliation Item"
+
+ if not frappe.db.has_column(doctype, "current_serial_no"):
+ # nothing to fix if column doesn't exist
+ return
+
+ sr_item = frappe.qb.DocType(doctype)
+
+ (frappe.qb
+ .update(sr_item)
+ .set(sr_item.current_serial_no, None)
+ .where(sr_item.current_qty == 0)
+ ).run()
diff --git a/erpnext/patches/v14_0/__init__.py b/erpnext/patches/v14_0/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/patches/v14_0/__init__.py
+++ /dev/null
diff --git a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
index 120182a..2a8b6ef 100644
--- a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
+++ b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
@@ -5,9 +5,6 @@
def execute():
- frappe.reload_doc("email", "doctype", "email_template")
- frappe.reload_doc("hr", "doctype", "hr_settings")
-
template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
if not template:
base_path = frappe.get_app_path("erpnext", "hr", "doctype")
diff --git a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py b/erpnext/patches/v14_0/delete_einvoicing_doctypes.py
deleted file mode 100644
index a3a8149..0000000
--- a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import frappe
-
-
-def execute():
- frappe.delete_doc('DocType', 'E Invoice Settings', ignore_missing=True)
- frappe.delete_doc('DocType', 'E Invoice User', ignore_missing=True)
- frappe.delete_doc('Report', 'E-Invoice Summary', ignore_missing=True)
- frappe.delete_doc('Print Format', 'GST E-Invoice', ignore_missing=True)
- frappe.delete_doc('Custom Field', 'Sales Invoice-eway_bill_cancelled', ignore_missing=True)
- frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
index 28fc01b..3a4f8f5 100644
--- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py
+++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
@@ -47,3 +47,18 @@
frappe.delete_doc("DocType", doctype, ignore_missing=True)
frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True)
+
+ custom_fields = {
+ 'Sales Invoice': ['patient', 'patient_name', 'ref_practitioner'],
+ 'Sales Invoice Item': ['reference_dt', 'reference_dn'],
+ 'Stock Entry': ['inpatient_medication_entry'],
+ 'Stock Entry Detail': ['patient', 'inpatient_medication_entry_child'],
+ }
+ for doc, fields in custom_fields.items():
+ filters = {
+ 'dt': doc,
+ 'fieldname': ['in', fields]
+ }
+ records = frappe.get_all('Custom Field', filters=filters, pluck='name')
+ for record in records:
+ frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True)
diff --git a/erpnext/patches/v14_0/delete_hospitality_doctypes.py b/erpnext/patches/v14_0/delete_hospitality_doctypes.py
new file mode 100644
index 0000000..d0216f8
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_hospitality_doctypes.py
@@ -0,0 +1,32 @@
+import frappe
+
+
+def execute():
+ modules = ['Hotels', 'Restaurant']
+
+ for module in modules:
+ frappe.delete_doc("Module Def", module, ignore_missing=True, force=True)
+
+ frappe.delete_doc("Workspace", module, ignore_missing=True, force=True)
+
+ reports = frappe.get_all("Report", {"module": module, "is_standard": "Yes"}, pluck='name')
+ for report in reports:
+ frappe.delete_doc("Report", report, ignore_missing=True, force=True)
+
+ dashboards = frappe.get_all("Dashboard", {"module": module, "is_standard": 1}, pluck='name')
+ for dashboard in dashboards:
+ frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
+
+ doctypes = frappe.get_all("DocType", {"module": module, "custom": 0}, pluck='name')
+ for doctype in doctypes:
+ frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+ custom_fields = [
+ {"dt": "Sales Invoice", "fieldname": "restaurant"},
+ {"dt": "Sales Invoice", "fieldname": "restaurant_table"},
+ {"dt": "Price List", "fieldname": "restaurant_menu"},
+ ]
+
+ for field in custom_fields:
+ custom_field = frappe.db.get_value("Custom Field", field)
+ frappe.delete_doc("Custom Field", custom_field, ignore_missing=True)
diff --git a/erpnext/patches/v14_0/rearrange_company_fields.py b/erpnext/patches/v14_0/rearrange_company_fields.py
new file mode 100644
index 0000000..fd7eb7f
--- /dev/null
+++ b/erpnext/patches/v14_0/rearrange_company_fields.py
@@ -0,0 +1,28 @@
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+
+def execute():
+ custom_fields = {
+ 'Company': [
+ dict(fieldname='hra_section', label='HRA Settings',
+ fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
+ dict(fieldname='basic_component', label='Basic Component',
+ fieldtype='Link', options='Salary Component', insert_after='hra_section'),
+ dict(fieldname='hra_component', label='HRA Component',
+ fieldtype='Link', options='Salary Component', insert_after='basic_component'),
+ dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
+ dict(fieldname='arrear_component', label='Arrear Component',
+ fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
+ dict(fieldname='non_profit_section', label='Non Profit Settings',
+ fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
+ dict(fieldname='company_80g_number', label='80G Number',
+ fieldtype='Data', insert_after='non_profit_section'),
+ dict(fieldname='with_effect_from', label='80G With Effect From',
+ fieldtype='Date', insert_after='company_80g_number'),
+ dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
+ dict(fieldname='pan_details', label='PAN Number',
+ fieldtype='Data', insert_after='non_profit_column_break')
+ ]
+ }
+
+ create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v14_0/restore_einvoice_fields.py b/erpnext/patches/v14_0/restore_einvoice_fields.py
new file mode 100644
index 0000000..c4431fb
--- /dev/null
+++ b/erpnext/patches/v14_0/restore_einvoice_fields.py
@@ -0,0 +1,24 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+from erpnext.regional.india.setup import add_permissions, add_print_formats
+
+
+def execute():
+ # restores back the 2 custom fields that was deleted while removing e-invoicing from v14
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+ depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+ dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+ depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
+ add_permissions()
+ add_print_formats()
diff --git a/erpnext/patches/v14_0/update_leave_notification_template.py b/erpnext/patches/v14_0/update_leave_notification_template.py
new file mode 100644
index 0000000..e744054
--- /dev/null
+++ b/erpnext/patches/v14_0/update_leave_notification_template.py
@@ -0,0 +1,17 @@
+import os
+
+import frappe
+from frappe import _
+
+
+def execute():
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html"))
+
+ template = frappe.db.exists("Email Template", _("Leave Approval Notification"))
+ if template:
+ frappe.db.set_value("Email Template", template, "response", response)
+
+ template = frappe.db.exists("Email Template", _("Leave Status Notification"))
+ if template:
+ frappe.db.set_value("Email Template", template, "response", response)
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index d9efe45..9c897a7 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -204,10 +204,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-05-26 11:10:00.812698",
+ "modified": "2022-01-19 12:56:51.765353",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Additional Salary",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -239,8 +240,10 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
- "title_field": "employee",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index 8332697..2e4b64e 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -147,10 +147,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:35:08.940087",
+ "modified": "2022-01-19 12:58:31.664468",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Application",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -212,8 +213,10 @@
}
],
"quick_entry": 1,
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
index b3bac01..5deb0a5 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -144,10 +144,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:37:21.024625",
+ "modified": "2022-01-19 12:59:15.699118",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Claim",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -208,8 +209,10 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
index 0d10b2c..64fb8c5 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -94,10 +94,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:38:20.332316",
+ "modified": "2022-01-19 12:52:19.850710",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Incentive",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -136,8 +137,10 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_other_income/employee_other_income.json b/erpnext/payroll/doctype/employee_other_income/employee_other_income.json
index 14f63e4..04ce9f7 100644
--- a/erpnext/payroll/doctype/employee_other_income/employee_other_income.json
+++ b/erpnext/payroll/doctype/employee_other_income/employee_other_income.json
@@ -76,10 +76,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 22:55:17.604688",
+ "modified": "2022-01-19 12:58:43.255900",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Other Income",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -129,7 +130,10 @@
}
],
"quick_entry": 1,
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index b247d26..5ef373e 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -119,10 +119,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:39:59.237361",
+ "modified": "2022-01-19 12:58:54.707871",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Declaration",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -186,7 +187,10 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index 77b107e..bb90051 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -142,10 +142,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:41:13.723339",
+ "modified": "2022-01-19 12:58:24.244546",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Proof Submission",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -209,7 +210,10 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json
index 48a9ce4..1970895 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.json
+++ b/erpnext/payroll/doctype/gratuity/gratuity.json
@@ -167,10 +167,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-07-02 15:05:57.396398",
+ "modified": "2022-01-19 12:54:37.306145",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Gratuity",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -198,6 +199,9 @@
"write": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name"
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index ed3fa5b..db88c06 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -61,6 +61,8 @@
def on_cancel(self):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
where payroll_entry=%s """, (self.name)))
+ self.db_set("salary_slips_created", 0)
+ self.db_set("salary_slips_submitted", 0)
def get_emp_list(self):
"""
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
index 7ea6210..f8d8bb4 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -105,10 +105,11 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:43:28.363644",
+ "modified": "2022-01-19 12:57:37.898953",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Retention Bonus",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -163,7 +164,10 @@
"share": 1
}
],
+ "search_fields": "employee_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 4e40e13..fe8e22c 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -637,7 +637,7 @@
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2021-12-23 11:47:47.098248",
+ "modified": "2022-01-19 12:45:54.999345",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",
@@ -673,9 +673,11 @@
"role": "Employee"
}
],
+ "search_fields": "employee_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "employee",
"title_field": "employee_name"
-}
+}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index c0e005a..bcf981b 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -994,6 +994,8 @@
))
leave_application.submit()
+ return leave_application
+
def setup_test():
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
index 197ab5f..613246e 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -162,7 +162,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-12-23 17:28:09.794444",
+ "modified": "2022-01-19 12:43:54.439073",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure Assignment",
@@ -209,6 +209,7 @@
"write": 1
}
],
+ "search_fields": "employee_name, salary_structure",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json
index 7246dae..762bea0 100644
--- a/erpnext/payroll/workspace/payroll/payroll.json
+++ b/erpnext/payroll/workspace/payroll/payroll.json
@@ -5,7 +5,7 @@
"label": "Outgoing Salary"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Payroll\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Structure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payroll Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Slip\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Income Tax Slab\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payroll\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Compensations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Payroll\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Salary Structure\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payroll Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Salary Slip\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Income Tax Slab\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Salary Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payroll\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Compensations\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-05-27 19:54:23.405607",
"docstatus": 0,
"doctype": "Workspace",
@@ -312,7 +312,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:01.335325",
+ "modified": "2022-01-13 17:41:19.098813",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll",
@@ -321,7 +321,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 19,
+ "sequence_id": 19.0,
"shortcuts": [
{
"label": "Salary Structure",
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 2570df7..1cda0a0 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -235,13 +235,13 @@
{
"fieldname": "actual_start_date",
"fieldtype": "Data",
- "label": "Actual Start Date",
+ "label": "Actual Start Date (via Time Sheet)",
"read_only": 1
},
{
"fieldname": "actual_time",
"fieldtype": "Float",
- "label": "Actual Time (in Hours)",
+ "label": "Actual Time (in Hours via Time Sheet)",
"read_only": 1
},
{
@@ -251,7 +251,7 @@
{
"fieldname": "actual_end_date",
"fieldtype": "Date",
- "label": "Actual End Date",
+ "label": "Actual End Date (via Time Sheet)",
"oldfieldname": "act_completion_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -458,10 +458,11 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
- "modified": "2021-04-28 16:36:11.654632",
+ "modified": "2022-01-29 13:58:27.712714",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -499,6 +500,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "customer",
"title_field": "project_name",
"track_seen": 1
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index ef4740d..8182208 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -249,7 +249,7 @@
{
"fieldname": "actual_time",
"fieldtype": "Float",
- "label": "Actual Time (in hours)",
+ "label": "Actual Time (in Hours via Time Sheet)",
"read_only": 1
},
{
@@ -397,10 +397,11 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
- "modified": "2021-04-16 12:46:51.556741",
+ "modified": "2022-01-29 13:58:47.005241",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
+ "naming_rule": "Expression (old style)",
"nsm_parent_field": "parent_task",
"owner": "Administrator",
"permissions": [
@@ -421,6 +422,7 @@
"show_preview_popup": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "project",
"title_field": "subject",
"track_seen": 1
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 148d8ba..989bcd1 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -5,7 +5,7 @@
import unittest
import frappe
-from frappe.utils import add_months, now_datetime, nowdate
+from frappe.utils import add_months, add_to_date, now_datetime, nowdate
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -151,6 +151,27 @@
settings.ignore_employee_time_overlap = initial_setting
settings.save()
+ def test_to_time(self):
+ emp = make_employee("test_employee_6@salary.com")
+ from_time = now_datetime()
+
+ timesheet = frappe.new_doc("Timesheet")
+ timesheet.employee = emp
+ timesheet.append(
+ 'time_logs',
+ {
+ "billable": 1,
+ "activity_type": "_Test Activity Type",
+ "from_time": from_time,
+ "hours": 2,
+ "company": "_Test Company"
+ }
+ )
+ timesheet.save()
+
+ to_time = timesheet.time_logs[0].to_time
+ self.assertEqual(to_time, add_to_date(from_time, hours=2, as_datetime=True))
+
def make_salary_structure_for_timesheet(employee, company=None):
salary_structure_name = "Timesheet Salary Structure Test"
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index e92785e..dd0b5f9 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -7,7 +7,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import flt, getdate, time_diff_in_hours
+from frappe.utils import add_to_date, flt, getdate, time_diff_in_hours
from erpnext.controllers.queries import get_match_cond
from erpnext.hr.utils import validate_active_employee
@@ -136,10 +136,19 @@
def validate_time_logs(self):
for data in self.get('time_logs'):
+ self.set_to_time(data)
self.validate_overlap(data)
self.set_project(data)
self.validate_project(data)
+ def set_to_time(self, data):
+ if not (data.from_time and data.hours):
+ return
+
+ _to_time = add_to_date(data.from_time, hours=data.hours, as_datetime=True)
+ if data.to_time != _to_time:
+ data.to_time = _to_time
+
def validate_overlap(self, data):
settings = frappe.get_single('Projects Settings')
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
index 1df2b08..c5a047d 100644
--- a/erpnext/projects/workspace/projects/projects.json
+++ b/erpnext/projects/workspace/projects/projects.json
@@ -5,7 +5,7 @@
"label": "Open Projects"
}
],
- "content": "[{\"type\": \"chart\", \"data\": {\"chart_name\": \"Open Projects\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Task\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Timesheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project Billing Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Projects\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Time Tracking\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:46:04.874669",
"docstatus": 0,
"doctype": "Workspace",
@@ -194,7 +194,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:01.540147",
+ "modified": "2022-01-13 17:41:55.163878",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
@@ -203,7 +203,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 20,
+ "sequence_id": 20.0,
"shortcuts": [
{
"color": "Blue",
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 597d77c..08270bd 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -592,6 +592,6 @@
&& doc.fg_completed_qty
&& erpnext.stock.bom
&& erpnext.stock.bom.name === doc.bom_no;
- const itemChecks = !!item;
+ const itemChecks = !!item && !item.allow_alternative_item;
return docChecks && itemChecks;
}
diff --git a/erpnext/quality_management/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json
index ae28470..3effd59 100644
--- a/erpnext/quality_management/workspace/quality/quality.json
+++ b/erpnext/quality_management/workspace/quality/quality.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Goal\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Procedure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Inspection\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Review\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Action\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Conformance\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goal and Procedure\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Feedback\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Meeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Review and Action\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Goal\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Procedure\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Inspection\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Review\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Action\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Non Conformance\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goal and Procedure\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Feedback\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Meeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Review and Action\",\"col\":4}}]",
"creation": "2020-03-02 15:49:28.632014",
"docstatus": 0,
"doctype": "Workspace",
@@ -142,7 +142,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:01.699913",
+ "modified": "2022-01-13 17:42:20.105187",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality",
@@ -151,7 +151,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 21,
+ "sequence_id": 21.0,
"shortcuts": [
{
"color": "Grey",
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/hotel_room_pricing/__init__.py
rename to erpnext/regional/doctype/e_invoice_request_log/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
new file mode 100644
index 0000000..7b7ba96
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Request Log', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
new file mode 100644
index 0000000..3034370
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
@@ -0,0 +1,102 @@
+{
+ "actions": [],
+ "autoname": "EINV-REQ-.#####",
+ "creation": "2020-12-08 12:54:08.175992",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "user",
+ "url",
+ "headers",
+ "response",
+ "column_break_7",
+ "timestamp",
+ "reference_invoice",
+ "data"
+ ],
+ "fields": [
+ {
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "label": "User",
+ "options": "User"
+ },
+ {
+ "fieldname": "reference_invoice",
+ "fieldtype": "Data",
+ "label": "Reference Invoice"
+ },
+ {
+ "fieldname": "headers",
+ "fieldtype": "Code",
+ "label": "Headers",
+ "options": "JSON"
+ },
+ {
+ "fieldname": "data",
+ "fieldtype": "Code",
+ "label": "Data",
+ "options": "JSON"
+ },
+ {
+ "default": "Now",
+ "fieldname": "timestamp",
+ "fieldtype": "Datetime",
+ "label": "Timestamp"
+ },
+ {
+ "fieldname": "response",
+ "fieldtype": "Code",
+ "label": "Response",
+ "options": "JSON"
+ },
+ {
+ "fieldname": "url",
+ "fieldtype": "Data",
+ "label": "URL"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-01-13 12:06:57.253111",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Request Log",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
new file mode 100644
index 0000000..c89552d
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class EInvoiceRequestLog(Document):
+ pass
diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
new file mode 100644
index 0000000..091cc88e
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+
+class TestEInvoiceRequestLog(unittest.TestCase):
+ pass
diff --git a/erpnext/hotels/doctype/hotel_settings/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/hotel_settings/__init__.py
rename to erpnext/regional/doctype/e_invoice_settings/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
new file mode 100644
index 0000000..54e4886
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Settings', {
+ refresh(frm) {
+ const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
+ frm.dashboard.set_headline(
+ __("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
+ );
+ }
+});
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
new file mode 100644
index 0000000..16b2963
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -0,0 +1,97 @@
+{
+ "actions": [],
+ "creation": "2020-09-24 16:23:16.235722",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "enable",
+ "section_break_2",
+ "sandbox_mode",
+ "applicable_from",
+ "credentials",
+ "advanced_settings_section",
+ "client_id",
+ "column_break_8",
+ "client_secret",
+ "auth_token",
+ "token_expiry"
+ ],
+ "fields": [
+ {
+ "default": "0",
+ "fieldname": "enable",
+ "fieldtype": "Check",
+ "label": "Enable"
+ },
+ {
+ "depends_on": "enable",
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "auth_token",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "token_expiry",
+ "fieldtype": "Datetime",
+ "hidden": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "credentials",
+ "fieldtype": "Table",
+ "label": "Credentials",
+ "mandatory_depends_on": "enable",
+ "options": "E Invoice User"
+ },
+ {
+ "default": "0",
+ "fieldname": "sandbox_mode",
+ "fieldtype": "Check",
+ "label": "Sandbox Mode"
+ },
+ {
+ "fieldname": "applicable_from",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Applicable From",
+ "reqd": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "advanced_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Advanced Settings"
+ },
+ {
+ "fieldname": "client_id",
+ "fieldtype": "Data",
+ "label": "Client ID"
+ },
+ {
+ "fieldname": "client_secret",
+ "fieldtype": "Password",
+ "label": "Client Secret"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2021-11-16 19:50:28.029517",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Settings",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
new file mode 100644
index 0000000..342d583
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+
+class EInvoiceSettings(Document):
+ def validate(self):
+ if self.enable and not self.credentials:
+ frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.'))
diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
new file mode 100644
index 0000000..10770de
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+
+class TestEInvoiceSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/demo/user/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py
similarity index 100%
rename from erpnext/demo/user/__init__.py
rename to erpnext/regional/doctype/e_invoice_user/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
new file mode 100644
index 0000000..a65b1ca
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
@@ -0,0 +1,57 @@
+{
+ "actions": [],
+ "creation": "2020-12-22 15:02:46.229474",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "gstin",
+ "username",
+ "password"
+ ],
+ "fields": [
+ {
+ "fieldname": "gstin",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "GSTIN",
+ "reqd": 1
+ },
+ {
+ "fieldname": "username",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Username",
+ "reqd": 1
+ },
+ {
+ "fieldname": "password",
+ "fieldtype": "Password",
+ "in_list_view": 1,
+ "label": "Password",
+ "reqd": 1
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-22 12:16:56.365616",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice User",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
new file mode 100644
index 0000000..4e0e89c
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class EInvoiceUser(Document):
+ pass
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index 2d1e02e..ec271a1 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -1,11 +1,11 @@
import datetime
import zipfile
from csv import QUOTE_NONNUMERIC
+from io import BytesIO
import frappe
import pandas as pd
from frappe import _
-from six import BytesIO
from .datev_constants import DataCategory
diff --git a/erpnext/restaurant/doctype/__init__.py b/erpnext/regional/india/e_invoice/__init__.py
similarity index 100%
rename from erpnext/restaurant/doctype/__init__.py
rename to erpnext/regional/india/e_invoice/__init__.py
diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json
new file mode 100644
index 0000000..78e5651
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_item_template.json
@@ -0,0 +1,31 @@
+{{
+ "SlNo": "{item.sr_no}",
+ "PrdDesc": "{item.description}",
+ "IsServc": "{item.is_service_item}",
+ "HsnCd": "{item.gst_hsn_code}",
+ "Barcde": "{item.barcode}",
+ "Unit": "{item.uom}",
+ "Qty": "{item.qty}",
+ "FreeQty": "{item.free_qty}",
+ "UnitPrice": "{item.unit_rate}",
+ "TotAmt": "{item.gross_amount}",
+ "Discount": "{item.discount_amount}",
+ "AssAmt": "{item.taxable_value}",
+ "PrdSlNo": "{item.serial_no}",
+ "GstRt": "{item.tax_rate}",
+ "IgstAmt": "{item.igst_amount}",
+ "CgstAmt": "{item.cgst_amount}",
+ "SgstAmt": "{item.sgst_amount}",
+ "CesRt": "{item.cess_rate}",
+ "CesAmt": "{item.cess_amount}",
+ "CesNonAdvlAmt": "{item.cess_nadv_amount}",
+ "StateCesRt": "{item.state_cess_rate}",
+ "StateCesAmt": "{item.state_cess_amount}",
+ "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
+ "OthChrg": "{item.other_charges}",
+ "TotItemVal": "{item.total_value}",
+ "BchDtls": {{
+ "Nm": "{item.batch_no}",
+ "ExpDt": "{item.batch_expiry_date}"
+ }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json
new file mode 100644
index 0000000..c2a28f2
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_template.json
@@ -0,0 +1,110 @@
+{{
+ "Version": "1.1",
+ "TranDtls": {{
+ "TaxSch": "{transaction_details.tax_scheme}",
+ "SupTyp": "{transaction_details.supply_type}",
+ "RegRev": "{transaction_details.reverse_charge}",
+ "EcmGstin": "{transaction_details.ecom_gstin}",
+ "IgstOnIntra": "{transaction_details.igst_on_intra}"
+ }},
+ "DocDtls": {{
+ "Typ": "{doc_details.invoice_type}",
+ "No": "{doc_details.invoice_name}",
+ "Dt": "{doc_details.invoice_date}"
+ }},
+ "SellerDtls": {{
+ "Gstin": "{seller_details.gstin}",
+ "LglNm": "{seller_details.legal_name}",
+ "TrdNm": "{seller_details.trade_name}",
+ "Loc": "{seller_details.location}",
+ "Pin": "{seller_details.pincode}",
+ "Stcd": "{seller_details.state_code}",
+ "Addr1": "{seller_details.address_line1}",
+ "Addr2": "{seller_details.address_line2}",
+ "Ph": "{seller_details.phone}",
+ "Em": "{seller_details.email}"
+ }},
+ "BuyerDtls": {{
+ "Gstin": "{buyer_details.gstin}",
+ "LglNm": "{buyer_details.legal_name}",
+ "TrdNm": "{buyer_details.trade_name}",
+ "Addr1": "{buyer_details.address_line1}",
+ "Addr2": "{buyer_details.address_line2}",
+ "Loc": "{buyer_details.location}",
+ "Pin": "{buyer_details.pincode}",
+ "Stcd": "{buyer_details.state_code}",
+ "Ph": "{buyer_details.phone}",
+ "Em": "{buyer_details.email}",
+ "Pos": "{buyer_details.place_of_supply}"
+ }},
+ "DispDtls": {{
+ "Nm": "{dispatch_details.legal_name}",
+ "Addr1": "{dispatch_details.address_line1}",
+ "Addr2": "{dispatch_details.address_line2}",
+ "Loc": "{dispatch_details.location}",
+ "Pin": "{dispatch_details.pincode}",
+ "Stcd": "{dispatch_details.state_code}"
+ }},
+ "ShipDtls": {{
+ "Gstin": "{shipping_details.gstin}",
+ "LglNm": "{shipping_details.legal_name}",
+ "TrdNm": "{shipping_details.trader_name}",
+ "Addr1": "{shipping_details.address_line1}",
+ "Addr2": "{shipping_details.address_line2}",
+ "Loc": "{shipping_details.location}",
+ "Pin": "{shipping_details.pincode}",
+ "Stcd": "{shipping_details.state_code}"
+ }},
+ "ItemList": [
+ {item_list}
+ ],
+ "ValDtls": {{
+ "AssVal": "{invoice_value_details.base_total}",
+ "CgstVal": "{invoice_value_details.total_cgst_amt}",
+ "SgstVal": "{invoice_value_details.total_sgst_amt}",
+ "IgstVal": "{invoice_value_details.total_igst_amt}",
+ "CesVal": "{invoice_value_details.total_cess_amt}",
+ "Discount": "{invoice_value_details.invoice_discount_amt}",
+ "RndOffAmt": "{invoice_value_details.round_off}",
+ "OthChrg": "{invoice_value_details.total_other_charges}",
+ "TotInvVal": "{invoice_value_details.base_grand_total}",
+ "TotInvValFc": "{invoice_value_details.grand_total}"
+ }},
+ "PayDtls": {{
+ "Nm": "{payment_details.payee_name}",
+ "AccDet": "{payment_details.account_no}",
+ "Mode": "{payment_details.mode_of_payment}",
+ "FinInsBr": "{payment_details.ifsc_code}",
+ "PayTerm": "{payment_details.terms}",
+ "PaidAmt": "{payment_details.paid_amount}",
+ "PaymtDue": "{payment_details.outstanding_amount}"
+ }},
+ "RefDtls": {{
+ "DocPerdDtls": {{
+ "InvStDt": "{period_details.start_date}",
+ "InvEndDt": "{period_details.end_date}"
+ }},
+ "PrecDocDtls": [{{
+ "InvNo": "{prev_doc_details.invoice_name}",
+ "InvDt": "{prev_doc_details.invoice_date}"
+ }}]
+ }},
+ "ExpDtls": {{
+ "ShipBNo": "{export_details.bill_no}",
+ "ShipBDt": "{export_details.bill_date}",
+ "Port": "{export_details.port}",
+ "ForCur": "{export_details.foreign_curr_code}",
+ "CntCode": "{export_details.country_code}",
+ "ExpDuty": "{export_details.export_duty}"
+ }},
+ "EwbDtls": {{
+ "TransId": "{eway_bill_details.gstin}",
+ "TransName": "{eway_bill_details.name}",
+ "TransMode": "{eway_bill_details.mode_of_transport}",
+ "Distance": "{eway_bill_details.distance}",
+ "TransDocNo": "{eway_bill_details.document_name}",
+ "TransDocDt": "{eway_bill_details.document_date}",
+ "VehNo": "{eway_bill_details.vehicle_no}",
+ "VehType": "{eway_bill_details.vehicle_type}"
+ }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
new file mode 100644
index 0000000..f4a3542
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -0,0 +1,957 @@
+{
+ "Version": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 6,
+ "description": "Version of the schema"
+ },
+ "Irn": {
+ "type": "string",
+ "minLength": 64,
+ "maxLength": 64,
+ "description": "Invoice Reference Number"
+ },
+ "TranDtls": {
+ "type": "object",
+ "properties": {
+ "TaxSch": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 10,
+ "enum": ["GST"],
+ "description": "GST- Goods and Services Tax Scheme"
+ },
+ "SupTyp": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 10,
+ "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"],
+ "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export"
+ },
+ "RegRev": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 1,
+ "enum": ["Y", "N"],
+ "description": "Y- whether the tax liability is payable under reverse charge"
+ },
+ "EcmGstin": {
+ "type": "string",
+ "minLength": 15,
+ "maxLength": 15,
+ "pattern": "([0-9]{2}[0-9A-Z]{13})",
+ "description": "E-Commerce GSTIN",
+ "validationMsg": "E-Commerce GSTIN is invalid"
+ },
+ "IgstOnIntra": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 1,
+ "enum": ["Y", "N"],
+ "description": "Y- indicates the supply is intra state but chargeable to IGST"
+ }
+ },
+ "required": ["TaxSch", "SupTyp"]
+ },
+ "DocDtls": {
+ "type": "object",
+ "properties": {
+ "Typ": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 3,
+ "enum": ["INV", "CRN", "DBN"],
+ "description": "Document Type"
+ },
+ "No": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 16,
+ "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$",
+ "description": "Document Number",
+ "validationMsg": "Document Number should not be starting with 0, / and -"
+ },
+ "Dt": {
+ "type": "string",
+ "minLength": 10,
+ "maxLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Document Date"
+ }
+ },
+ "required": ["Typ", "No", "Dt"]
+ },
+ "SellerDtls": {
+ "type": "object",
+ "properties": {
+ "Gstin": {
+ "type": "string",
+ "minLength": 15,
+ "maxLength": 15,
+ "pattern": "([0-9]{2}[0-9A-Z]{13})",
+ "description": "Supplier GSTIN",
+ "validationMsg": "Company GSTIN is invalid"
+ },
+ "LglNm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Legal Name"
+ },
+ "TrdNm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Tradename"
+ },
+ "Addr1": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Address Line 1"
+ },
+ "Addr2": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Address Line 2"
+ },
+ "Loc": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 50,
+ "description": "Location"
+ },
+ "Pin": {
+ "type": "number",
+ "minimum": 100000,
+ "maximum": 999999,
+ "description": "Pincode"
+ },
+ "Stcd": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2,
+ "description": "Supplier State Code"
+ },
+ "Ph": {
+ "type": "string",
+ "minLength": 6,
+ "maxLength": 12,
+ "description": "Phone"
+ },
+ "Em": {
+ "type": "string",
+ "minLength": 6,
+ "maxLength": 100,
+ "description": "Email-Id"
+ }
+ },
+ "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+ },
+ "BuyerDtls": {
+ "type": "object",
+ "properties": {
+ "Gstin": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 15,
+ "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+ "description": "Buyer GSTIN",
+ "validationMsg": "Customer GSTIN is invalid"
+ },
+ "LglNm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Legal Name"
+ },
+ "TrdNm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Trade Name"
+ },
+ "Pos": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2,
+ "description": "Place of Supply State code"
+ },
+ "Addr1": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Address Line 1"
+ },
+ "Addr2": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Address Line 2"
+ },
+ "Loc": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Location"
+ },
+ "Pin": {
+ "type": "number",
+ "minimum": 100000,
+ "maximum": 999999,
+ "description": "Pincode"
+ },
+ "Stcd": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2,
+ "description": "Buyer State Code"
+ },
+ "Ph": {
+ "type": "string",
+ "minLength": 6,
+ "maxLength": 12,
+ "description": "Phone"
+ },
+ "Em": {
+ "type": "string",
+ "minLength": 6,
+ "maxLength": 100,
+ "description": "Email-Id"
+ }
+ },
+ "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
+ },
+ "DispDtls": {
+ "type": "object",
+ "properties": {
+ "Nm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Dispatch Address Name"
+ },
+ "Addr1": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Address Line 1"
+ },
+ "Addr2": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Address Line 2"
+ },
+ "Loc": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Location"
+ },
+ "Pin": {
+ "type": "number",
+ "minimum": 100000,
+ "maximum": 999999,
+ "description": "Pincode"
+ },
+ "Stcd": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2,
+ "description": "State Code"
+ }
+ },
+ "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
+ },
+ "ShipDtls": {
+ "type": "object",
+ "properties": {
+ "Gstin": {
+ "type": "string",
+ "maxLength": 15,
+ "minLength": 3,
+ "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+ "description": "Shipping Address GSTIN",
+ "validationMsg": "Shipping Address GSTIN is invalid"
+ },
+ "LglNm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Legal Name"
+ },
+ "TrdNm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Trade Name"
+ },
+ "Addr1": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Address Line 1"
+ },
+ "Addr2": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Address Line 2"
+ },
+ "Loc": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Location"
+ },
+ "Pin": {
+ "type": "number",
+ "minimum": 100000,
+ "maximum": 999999,
+ "description": "Pincode"
+ },
+ "Stcd": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2,
+ "description": "State Code"
+ }
+ },
+ "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+ },
+ "ItemList": {
+ "type": "Array",
+ "properties": {
+ "SlNo": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 6,
+ "description": "Serial No. of Item"
+ },
+ "PrdDesc": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 300,
+ "description": "Item Name"
+ },
+ "IsServc": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 1,
+ "enum": ["Y", "N"],
+ "description": "Is Service Item"
+ },
+ "HsnCd": {
+ "type": "string",
+ "minLength": 4,
+ "maxLength": 8,
+ "description": "HSN Code"
+ },
+ "Barcde": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 30,
+ "description": "Barcode"
+ },
+ "Qty": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 9999999999.999,
+ "description": "Quantity"
+ },
+ "FreeQty": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 9999999999.999,
+ "description": "Free Quantity"
+ },
+ "Unit": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 8,
+ "description": "UOM"
+ },
+ "UnitPrice": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.999,
+ "description": "Rate"
+ },
+ "TotAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Gross Amount"
+ },
+ "Discount": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Discount"
+ },
+ "PreTaxVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Pre tax value"
+ },
+ "AssAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Taxable Value"
+ },
+ "GstRt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999.999,
+ "description": "GST Rate"
+ },
+ "IgstAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "IGST Amount"
+ },
+ "CgstAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "CGST Amount"
+ },
+ "SgstAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "SGST Amount"
+ },
+ "CesRt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999.999,
+ "description": "Cess Rate"
+ },
+ "CesAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Cess Amount (Advalorem)"
+ },
+ "CesNonAdvlAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Cess Amount (Non-Advalorem)"
+ },
+ "StateCesRt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999.999,
+ "description": "State CESS Rate"
+ },
+ "StateCesAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "State CESS Amount"
+ },
+ "StateCesNonAdvlAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "State CESS Amount (Non Advalorem)"
+ },
+ "OthChrg": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Other Charges"
+ },
+ "TotItemVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Total Item Value"
+ },
+ "OrdLineRef": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 50,
+ "description": "Order line reference"
+ },
+ "OrgCntry": {
+ "type": "string",
+ "minLength": 2,
+ "maxLength": 2,
+ "description": "Origin Country"
+ },
+ "PrdSlNo": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "description": "Serial number"
+ },
+ "BchDtls": {
+ "type": "object",
+ "properties": {
+ "Nm": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 20,
+ "description": "Batch number"
+ },
+ "ExpDt": {
+ "type": "string",
+ "maxLength": 10,
+ "minLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Batch Expiry Date"
+ },
+ "WrDt": {
+ "type": "string",
+ "maxLength": 10,
+ "minLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Warranty Date"
+ }
+ },
+ "required": ["Nm"]
+ },
+ "AttribDtls": {
+ "type": "Array",
+ "Attribute": {
+ "type": "object",
+ "properties": {
+ "Nm": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Attribute name of the item"
+ },
+ "Val": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Attribute value of the item"
+ }
+ }
+ }
+ }
+ },
+ "required": [
+ "SlNo",
+ "IsServc",
+ "HsnCd",
+ "UnitPrice",
+ "TotAmt",
+ "AssAmt",
+ "GstRt",
+ "TotItemVal"
+ ]
+ },
+ "ValDtls": {
+ "type": "object",
+ "properties": {
+ "AssVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Total Assessable value of all items"
+ },
+ "CgstVal": {
+ "type": "number",
+ "maximum": 99999999999999.99,
+ "minimum": 0,
+ "description": "Total CGST value of all items"
+ },
+ "SgstVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Total SGST value of all items"
+ },
+ "IgstVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Total IGST value of all items"
+ },
+ "CesVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Total CESS value of all items"
+ },
+ "StCesVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Total State CESS value of all items"
+ },
+ "Discount": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Invoice Discount"
+ },
+ "OthChrg": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Other Charges"
+ },
+ "RndOffAmt": {
+ "type": "number",
+ "minimum": -99.99,
+ "maximum": 99.99,
+ "description": "Rounded off Amount"
+ },
+ "TotInvVal": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Final Invoice Value "
+ },
+ "TotInvValFc": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Final Invoice value in Foreign Currency"
+ }
+ },
+ "required": ["AssVal", "TotInvVal"]
+ },
+ "PayDtls": {
+ "type": "object",
+ "properties": {
+ "Nm": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Payee Name"
+ },
+ "AccDet": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 18,
+ "description": "Bank Account Number of Payee"
+ },
+ "Mode": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 18,
+ "description": "Mode of Payment"
+ },
+ "FinInsBr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 11,
+ "description": "Branch or IFSC code"
+ },
+ "PayTerm": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Terms of Payment"
+ },
+ "PayInstr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Payment Instruction"
+ },
+ "CrTrn": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Credit Transfer"
+ },
+ "DirDr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100,
+ "description": "Direct Debit"
+ },
+ "CrDay": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 9999,
+ "description": "Credit Days"
+ },
+ "PaidAmt": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Advance Amount"
+ },
+ "PaymtDue": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 99999999999999.99,
+ "description": "Outstanding Amount"
+ }
+ }
+ },
+ "RefDtls": {
+ "type": "object",
+ "properties": {
+ "InvRm": {
+ "type": "string",
+ "maxLength": 100,
+ "minLength": 3,
+ "pattern": "^[0-9A-Za-z/-]{3,100}$",
+ "description": "Remarks/Note"
+ },
+ "DocPerdDtls": {
+ "type": "object",
+ "properties": {
+ "InvStDt": {
+ "type": "string",
+ "maxLength": 10,
+ "minLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Invoice Period Start Date"
+ },
+ "InvEndDt": {
+ "type": "string",
+ "maxLength": 10,
+ "minLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Invoice Period End Date"
+ }
+ },
+ "required": ["InvStDt ", "InvEndDt "]
+ },
+ "PrecDocDtls": {
+ "type": "object",
+ "properties": {
+ "InvNo": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 16,
+ "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$",
+ "description": "Reference of Original Invoice"
+ },
+ "InvDt": {
+ "type": "string",
+ "maxLength": 10,
+ "minLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Date of Orginal Invoice"
+ },
+ "OthRefNo": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "description": "Other Reference"
+ }
+ }
+ },
+ "required": ["InvNo", "InvDt"],
+ "ContrDtls": {
+ "type": "object",
+ "properties": {
+ "RecAdvRefr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "pattern": "^([0-9A-Za-z/-]){1,20}$",
+ "description": "Receipt Advice No."
+ },
+ "RecAdvDt": {
+ "type": "string",
+ "minLength": 10,
+ "maxLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Date of receipt advice"
+ },
+ "TendRefr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "pattern": "^([0-9A-Za-z/-]){1,20}$",
+ "description": "Lot/Batch Reference No."
+ },
+ "ContrRefr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "pattern": "^([0-9A-Za-z/-]){1,20}$",
+ "description": "Contract Reference Number"
+ },
+ "ExtRefr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "pattern": "^([0-9A-Za-z/-]){1,20}$",
+ "description": "Any other reference"
+ },
+ "ProjRefr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "pattern": "^([0-9A-Za-z/-]){1,20}$",
+ "description": "Project Reference Number"
+ },
+ "PORefr": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 16,
+ "pattern": "^([0-9A-Za-z/-]){1,16}$",
+ "description": "PO Reference Number"
+ },
+ "PORefDt": {
+ "type": "string",
+ "minLength": 10,
+ "maxLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "PO Reference date"
+ }
+ }
+ }
+ }
+ },
+ "AddlDocDtls": {
+ "type": "Array",
+ "properties": {
+ "Url": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Supporting document URL"
+ },
+ "Docs": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 1000,
+ "description": "Supporting document in Base64 Format"
+ },
+ "Info": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 1000,
+ "description": "Any additional information"
+ }
+ }
+ },
+
+ "ExpDtls": {
+ "type": "object",
+ "properties": {
+ "ShipBNo": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "description": "Shipping Bill No."
+ },
+ "ShipBDt": {
+ "type": "string",
+ "minLength": 10,
+ "maxLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Shipping Bill Date"
+ },
+ "Port": {
+ "type": "string",
+ "minLength": 2,
+ "maxLength": 10,
+ "pattern": "^[0-9A-Za-z]{2,10}$",
+ "description": "Port Code. Refer the master"
+ },
+ "RefClm": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 1,
+ "description": "Claiming Refund. Y/N"
+ },
+ "ForCur": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 16,
+ "description": "Additional Currency Code. Refer the master"
+ },
+ "CntCode": {
+ "type": "string",
+ "minLength": 2,
+ "maxLength": 2,
+ "description": "Country Code. Refer the master"
+ },
+ "ExpDuty": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 999999999999.99,
+ "description": "Export Duty"
+ }
+ }
+ },
+ "EwbDtls": {
+ "type": "object",
+ "properties": {
+ "TransId": {
+ "type": "string",
+ "minLength": 15,
+ "maxLength": 15,
+ "description": "Transporter GSTIN"
+ },
+ "TransName": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 100,
+ "description": "Transporter Name"
+ },
+ "TransMode": {
+ "type": "string",
+ "maxLength": 1,
+ "minLength": 1,
+ "enum": ["1", "2", "3", "4"],
+ "description": "Mode of Transport"
+ },
+ "Distance": {
+ "type": "number",
+ "minimum": 1,
+ "maximum": 9999,
+ "description": "Distance"
+ },
+ "TransDocNo": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 15,
+ "pattern": "^([0-9A-Z/-]){1,15}$",
+ "description": "Tranport Document Number",
+ "validationMsg": "Transport Receipt No is invalid"
+ },
+ "TransDocDt": {
+ "type": "string",
+ "minLength": 10,
+ "maxLength": 10,
+ "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+ "description": "Transport Document Date"
+ },
+ "VehNo": {
+ "type": "string",
+ "minLength": 4,
+ "maxLength": 20,
+ "description": "Vehicle Number"
+ },
+ "VehType": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 1,
+ "enum": ["O", "R"],
+ "description": "Vehicle Type"
+ }
+ },
+ "required": ["Distance"]
+ },
+ "required": [
+ "Version",
+ "TranDtls",
+ "DocDtls",
+ "SellerDtls",
+ "BuyerDtls",
+ "ItemList",
+ "ValDtls"
+ ]
+}
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
new file mode 100644
index 0000000..348f0c6
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -0,0 +1,292 @@
+erpnext.setup_einvoice_actions = (doctype) => {
+ frappe.ui.form.on(doctype, {
+ async refresh(frm) {
+ if (frm.doc.docstatus == 2) return;
+
+ const res = await frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
+ args: { doc: frm.doc }
+ });
+ const invoice_eligible = res.message;
+
+ if (!invoice_eligible) return;
+
+ const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
+
+ const add_custom_button = (label, action) => {
+ if (!frm.custom_buttons[label]) {
+ frm.add_custom_button(label, action, __('E Invoicing'));
+ }
+ };
+
+ if (!irn && !__unsaved) {
+ const action = () => {
+ if (frm.doc.__unsaved) {
+ frappe.throw(__('Please save the document to generate IRN.'));
+ }
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
+ args: { doctype, docname: name },
+ freeze: true,
+ callback: (res) => {
+ const einvoice = res.message;
+ show_einvoice_preview(frm, einvoice);
+ }
+ });
+ };
+
+ add_custom_button(__("Generate IRN"), action);
+ }
+
+ if (irn && !irn_cancelled && !ewaybill) {
+ const fields = [
+ {
+ "label": "Reason",
+ "fieldname": "reason",
+ "fieldtype": "Select",
+ "reqd": 1,
+ "default": "1-Duplicate",
+ "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+ },
+ {
+ "label": "Remark",
+ "fieldname": "remark",
+ "fieldtype": "Data",
+ "reqd": 1
+ }
+ ];
+ const action = () => {
+ const d = new frappe.ui.Dialog({
+ title: __("Cancel IRN"),
+ fields: fields,
+ primary_action: function() {
+ const data = d.get_values();
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
+ args: {
+ doctype,
+ docname: name,
+ irn: irn,
+ reason: data.reason.split('-')[0],
+ remark: data.remark
+ },
+ freeze: true,
+ callback: () => frm.reload_doc() || d.hide(),
+ error: () => d.hide()
+ });
+ },
+ primary_action_label: __('Submit')
+ });
+ d.show();
+ };
+ add_custom_button(__("Cancel IRN"), action);
+ }
+
+ if (irn && !irn_cancelled && !ewaybill) {
+ const action = () => {
+ const d = new frappe.ui.Dialog({
+ title: __('Generate E-Way Bill'),
+ size: "large",
+ fields: get_ewaybill_fields(frm),
+ primary_action: function() {
+ const data = d.get_values();
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill',
+ args: {
+ doctype,
+ docname: name,
+ irn,
+ ...data
+ },
+ freeze: true,
+ callback: () => frm.reload_doc() || d.hide(),
+ error: () => d.hide()
+ });
+ },
+ primary_action_label: __('Submit')
+ });
+ d.show();
+ };
+
+ add_custom_button(__("Generate E-Way Bill"), action);
+ }
+
+ if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
+ const action = () => {
+ let message = __('Cancellation of e-way bill is currently not supported.') + ' ';
+ message += '<br><br>';
+ message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
+
+ const dialog = frappe.msgprint({
+ title: __('Update E-Way Bill Cancelled Status?'),
+ message: message,
+ indicator: 'orange',
+ primary_action: {
+ action: function() {
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+ args: { doctype, docname: name },
+ freeze: true,
+ callback: () => frm.reload_doc() || dialog.hide()
+ });
+ }
+ },
+ primary_action_label: __('Yes')
+ });
+ };
+ add_custom_button(__("Cancel E-Way Bill"), action);
+ }
+ }
+ });
+};
+
+const get_ewaybill_fields = (frm) => {
+ return [
+ {
+ 'fieldname': 'transporter',
+ 'label': 'Transporter',
+ 'fieldtype': 'Link',
+ 'options': 'Supplier',
+ 'default': frm.doc.transporter
+ },
+ {
+ 'fieldname': 'gst_transporter_id',
+ 'label': 'GST Transporter ID',
+ 'fieldtype': 'Data',
+ 'fetch_from': 'transporter.gst_transporter_id',
+ 'default': frm.doc.gst_transporter_id
+ },
+ {
+ 'fieldname': 'driver',
+ 'label': 'Driver',
+ 'fieldtype': 'Link',
+ 'options': 'Driver',
+ 'default': frm.doc.driver
+ },
+ {
+ 'fieldname': 'lr_no',
+ 'label': 'Transport Receipt No',
+ 'fieldtype': 'Data',
+ 'default': frm.doc.lr_no
+ },
+ {
+ 'fieldname': 'vehicle_no',
+ 'label': 'Vehicle No',
+ 'fieldtype': 'Data',
+ 'default': frm.doc.vehicle_no
+ },
+ {
+ 'fieldname': 'distance',
+ 'label': 'Distance (in km)',
+ 'fieldtype': 'Float',
+ 'default': frm.doc.distance
+ },
+ {
+ 'fieldname': 'transporter_col_break',
+ 'fieldtype': 'Column Break',
+ },
+ {
+ 'fieldname': 'transporter_name',
+ 'label': 'Transporter Name',
+ 'fieldtype': 'Data',
+ 'fetch_from': 'transporter.name',
+ 'read_only': 1,
+ 'default': frm.doc.transporter_name
+ },
+ {
+ 'fieldname': 'mode_of_transport',
+ 'label': 'Mode of Transport',
+ 'fieldtype': 'Select',
+ 'options': `\nRoad\nAir\nRail\nShip`,
+ 'default': frm.doc.mode_of_transport
+ },
+ {
+ 'fieldname': 'driver_name',
+ 'label': 'Driver Name',
+ 'fieldtype': 'Data',
+ 'fetch_from': 'driver.full_name',
+ 'read_only': 1,
+ 'default': frm.doc.driver_name
+ },
+ {
+ 'fieldname': 'lr_date',
+ 'label': 'Transport Receipt Date',
+ 'fieldtype': 'Date',
+ 'default': frm.doc.lr_date
+ },
+ {
+ 'fieldname': 'gst_vehicle_type',
+ 'label': 'GST Vehicle Type',
+ 'fieldtype': 'Select',
+ 'options': `Regular\nOver Dimensional Cargo (ODC)`,
+ 'depends_on': 'eval:(doc.mode_of_transport === "Road")',
+ 'default': frm.doc.gst_vehicle_type
+ }
+ ];
+};
+
+const request_irn_generation = (frm) => {
+ frappe.call({
+ method: 'erpnext.regional.india.e_invoice.utils.generate_irn',
+ args: { doctype: frm.doc.doctype, docname: frm.doc.name },
+ freeze: true,
+ callback: () => frm.reload_doc()
+ });
+};
+
+const get_preview_dialog = (frm, action) => {
+ const dialog = new frappe.ui.Dialog({
+ title: __("Preview"),
+ size: "large",
+ fields: [
+ {
+ "label": "Preview",
+ "fieldname": "preview_html",
+ "fieldtype": "HTML"
+ }
+ ],
+ primary_action: () => action(frm) || dialog.hide(),
+ primary_action_label: __('Generate IRN')
+ });
+ return dialog;
+};
+
+const show_einvoice_preview = (frm, einvoice) => {
+ const preview_dialog = get_preview_dialog(frm, request_irn_generation);
+
+ // initialize e-invoice fields
+ einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate();
+ frm.doc.signed_einvoice = JSON.stringify(einvoice);
+
+ // initialize preview wrapper
+ const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper;
+ $preview_wrapper.html(
+ `<div>
+ <div class="print-preview">
+ <div class="print-format"></div>
+ </div>
+ <div class="page-break-message text-muted text-center text-medium margin-top"></div>
+ </div>`
+ );
+
+ frappe.call({
+ method: "frappe.www.printview.get_html_and_style",
+ args: {
+ doc: frm.doc,
+ print_format: "GST E-Invoice",
+ no_letterhead: 1
+ },
+ callback: function (r) {
+ if (!r.exc) {
+ $preview_wrapper.find(".print-format").html(r.message.html);
+ const style = `
+ .print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; }
+ .print-preview { min-height: 0px; }
+ .modal-dialog { width: 720px; }`;
+
+ frappe.dom.set_style(style, "custom-print-style");
+ preview_dialog.show();
+ }
+ }
+ });
+};
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
new file mode 100644
index 0000000..e3f7e90
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -0,0 +1,1171 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import base64
+import io
+import json
+import os
+import re
+import sys
+import traceback
+
+import frappe
+import jwt
+from frappe import _, bold
+from frappe.core.page.background_jobs.background_jobs import get_info
+from frappe.integrations.utils import make_get_request, make_post_request
+from frappe.utils.background_jobs import enqueue
+from frappe.utils.data import (
+ add_to_date,
+ cint,
+ cstr,
+ flt,
+ format_date,
+ get_link_to_form,
+ getdate,
+ now_datetime,
+ time_diff_in_hours,
+ time_diff_in_seconds,
+)
+from frappe.utils.scheduler import is_scheduler_inactive
+from pyqrcode import create as qrcreate
+
+from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
+
+
+@frappe.whitelist()
+def validate_eligibility(doc):
+ if isinstance(doc, str):
+ doc = json.loads(doc)
+
+ invalid_doctype = doc.get('doctype') != 'Sales Invoice'
+ if invalid_doctype:
+ return False
+
+ einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
+ if not einvoicing_enabled:
+ return False
+
+ einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01'
+ if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from):
+ return False
+
+ invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
+ invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
+ company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
+
+ # if export invoice, then taxes can be empty
+ # invoice can only be ineligible if no taxes applied and is not an export invoice
+ no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas'
+ has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
+
+ if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
+ return False
+
+ return True
+
+def validate_einvoice_fields(doc):
+ invoice_eligible = validate_eligibility(doc)
+
+ if not invoice_eligible:
+ return
+
+ if doc.docstatus == 0 and doc._action == 'save':
+ if doc.irn:
+ frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
+ if len(doc.name) > 16:
+ raise_document_name_too_long_error()
+
+ doc.einvoice_status = 'Pending'
+
+ elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
+ frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
+
+ elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
+ frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
+
+def raise_document_name_too_long_error():
+ title = _('Document ID Too Long')
+ msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice')
+ msg += ', '
+ msg += _('document id {} exceed 16 letters.').format(bold(_('should not')))
+ msg += '<br><br>'
+ msg += _('You must {} your {} in order to have document id of {} length 16.').format(
+ bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
+ )
+ msg += _('Please account for ammended documents too.')
+ frappe.throw(msg, title=title)
+
+def read_json(name):
+ file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
+ with open(file_path, 'r') as f:
+ return cstr(f.read())
+
+def get_transaction_details(invoice):
+ supply_type = ''
+ if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
+ elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
+ elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
+ elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
+
+ if not supply_type:
+ rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
+ frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
+ title=_('Invalid Supply Type'))
+
+ return frappe._dict(dict(
+ tax_scheme='GST',
+ supply_type=supply_type,
+ reverse_charge=invoice.reverse_charge
+ ))
+
+def get_doc_details(invoice):
+ if getdate(invoice.posting_date) < getdate('2021-01-01'):
+ frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed'))
+
+ invoice_type = 'CRN' if invoice.is_return else 'INV'
+
+ invoice_name = invoice.name
+ invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
+
+ return frappe._dict(dict(
+ invoice_type=invoice_type,
+ invoice_name=invoice_name,
+ invoice_date=invoice_date
+ ))
+
+def validate_address_fields(address, skip_gstin_validation):
+ if ((not address.gstin and not skip_gstin_validation)
+ or not address.city
+ or not address.pincode
+ or not address.address_title
+ or not address.address_line1
+ or not address.gst_state_number):
+
+ frappe.throw(
+ msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
+ title=_('Missing Address Fields')
+ )
+
+ if address.address_line2 and len(address.address_line2) < 2:
+ # to prevent "The field Address 2 must be a string with a minimum length of 3 and a maximum length of 100"
+ address.address_line2 = ""
+
+def get_party_details(address_name, skip_gstin_validation=False):
+ addr = frappe.get_doc('Address', address_name)
+
+ validate_address_fields(addr, skip_gstin_validation)
+
+ if addr.gst_state_number == 97:
+ # according to einvoice standard
+ addr.pincode = 999999
+
+ party_address_details = frappe._dict(dict(
+ legal_name=sanitize_for_json(addr.address_title),
+ location=sanitize_for_json(addr.city),
+ pincode=addr.pincode, gstin=addr.gstin,
+ state_code=addr.gst_state_number,
+ address_line1=sanitize_for_json(addr.address_line1),
+ address_line2=sanitize_for_json(addr.address_line2)
+ ))
+
+ return party_address_details
+
+def get_overseas_address_details(address_name):
+ address_title, address_line1, address_line2, city = frappe.db.get_value(
+ 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
+ )
+
+ if not address_title or not address_line1 or not city:
+ frappe.throw(
+ msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
+ get_link_to_form('Address', address_name)
+ ),
+ title=_('Missing Address Fields')
+ )
+
+ return frappe._dict(dict(
+ gstin='URP',
+ legal_name=sanitize_for_json(address_title),
+ location=city,
+ address_line1=sanitize_for_json(address_line1),
+ address_line2=sanitize_for_json(address_line2),
+ pincode=999999, state_code=96, place_of_supply=96
+ ))
+
+def get_item_list(invoice):
+ item_list = []
+
+ for d in invoice.items:
+ einvoice_item_schema = read_json('einv_item_template')
+ item = frappe._dict({})
+ item.update(d.as_dict())
+
+ item.sr_no = d.idx
+ item.description = sanitize_for_json(d.item_name)
+
+ item.qty = abs(item.qty)
+ if flt(item.qty) != 0.0:
+ item.unit_rate = abs(item.taxable_value / item.qty)
+ else:
+ item.unit_rate = abs(item.taxable_value)
+ item.gross_amount = abs(item.taxable_value)
+ item.taxable_value = abs(item.taxable_value)
+ item.discount_amount = 0
+
+ item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
+ item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
+ item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N'
+ item.serial_no = ""
+
+ item = update_item_taxes(invoice, item)
+
+ item.total_value = abs(
+ item.taxable_value + item.igst_amount + item.sgst_amount +
+ item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
+ )
+ einv_item = einvoice_item_schema.format(item=item)
+ item_list.append(einv_item)
+
+ return ', '.join(item_list)
+
+def update_item_taxes(invoice, item):
+ gst_accounts = get_gst_accounts(invoice.company)
+ gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
+
+ for attr in [
+ 'tax_rate', 'cess_rate', 'cess_nadv_amount',
+ 'cgst_amount', 'sgst_amount', 'igst_amount',
+ 'cess_amount', 'cess_nadv_amount', 'other_charges'
+ ]:
+ item[attr] = 0
+
+ for t in invoice.taxes:
+ is_applicable = t.tax_amount and t.account_head in gst_accounts_list
+ if is_applicable:
+ # this contains item wise tax rate & tax amount (incl. discount)
+ item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
+
+ item_tax_rate = item_tax_detail[0]
+ # item tax amount excluding discount amount
+ item_tax_amount = (item_tax_rate / 100) * item.taxable_value
+
+ if t.account_head in gst_accounts.cess_account:
+ item_tax_amount_after_discount = item_tax_detail[1]
+ if t.charge_type == 'On Item Quantity':
+ item.cess_nadv_amount += abs(item_tax_amount_after_discount)
+ else:
+ item.cess_rate += item_tax_rate
+ item.cess_amount += abs(item_tax_amount_after_discount)
+
+ for tax_type in ['igst', 'cgst', 'sgst']:
+ if t.account_head in gst_accounts[f'{tax_type}_account']:
+ item.tax_rate += item_tax_rate
+ item[f'{tax_type}_amount'] += abs(item_tax_amount)
+ else:
+ # TODO: other charges per item
+ pass
+
+ return item
+
+def get_invoice_value_details(invoice):
+ invoice_value_details = frappe._dict(dict())
+ invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
+ invoice_value_details.invoice_discount_amt = 0
+
+ invoice_value_details.round_off = invoice.base_rounding_adjustment
+ invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
+ invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
+
+ invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
+
+ return invoice_value_details
+
+def update_invoice_taxes(invoice, invoice_value_details):
+ gst_accounts = get_gst_accounts(invoice.company)
+ gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
+
+ invoice_value_details.total_cgst_amt = 0
+ invoice_value_details.total_sgst_amt = 0
+ invoice_value_details.total_igst_amt = 0
+ invoice_value_details.total_cess_amt = 0
+ invoice_value_details.total_other_charges = 0
+ considered_rows = []
+
+ for t in invoice.taxes:
+ tax_amount = t.base_tax_amount_after_discount_amount
+ if t.account_head in gst_accounts_list:
+ if t.account_head in gst_accounts.cess_account:
+ # using after discount amt since item also uses after discount amt for cess calc
+ invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
+
+ for tax_type in ['igst', 'cgst', 'sgst']:
+ if t.account_head in gst_accounts[f'{tax_type}_account']:
+
+ invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
+ update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
+ else:
+ invoice_value_details.total_other_charges += abs(tax_amount)
+
+ return invoice_value_details
+
+def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
+ prev_row_id = cint(tax_row.row_id) - 1
+ if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
+ if tax_row.charge_type == 'On Previous Row Amount':
+ amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
+ invoice_value_details.total_other_charges -= abs(amount)
+ considered_rows.append(prev_row_id)
+ if tax_row.charge_type == 'On Previous Row Total':
+ amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
+ invoice_value_details.total_other_charges -= abs(amount)
+ considered_rows.append(prev_row_id)
+
+def get_payment_details(invoice):
+ payee_name = invoice.company
+ mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
+ paid_amount = invoice.base_paid_amount
+ outstanding_amount = invoice.outstanding_amount
+
+ return frappe._dict(dict(
+ payee_name=payee_name, mode_of_payment=mode_of_payment,
+ paid_amount=paid_amount, outstanding_amount=outstanding_amount
+ ))
+
+def get_return_doc_reference(invoice):
+ invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
+ return frappe._dict(dict(
+ invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
+ ))
+
+def get_eway_bill_details(invoice):
+ if invoice.is_return:
+ frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
+ title=_('Invalid Fields'))
+
+
+ mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
+ vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
+
+ return frappe._dict(dict(
+ gstin=invoice.gst_transporter_id,
+ name=invoice.transporter_name,
+ mode_of_transport=mode_of_transport[invoice.mode_of_transport],
+ distance=invoice.distance or 0,
+ document_name=invoice.lr_no,
+ document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
+ vehicle_no=invoice.vehicle_no,
+ vehicle_type=vehicle_type[invoice.gst_vehicle_type]
+ ))
+
+def validate_mandatory_fields(invoice):
+ if not invoice.company_address:
+ frappe.throw(
+ _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
+ title=_('Missing Fields')
+ )
+ if not invoice.customer_address:
+ frappe.throw(
+ _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
+ title=_('Missing Fields')
+ )
+ if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
+ frappe.throw(
+ _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
+ title=_('Missing Fields')
+ )
+ if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
+ frappe.throw(
+ _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
+ title=_('Missing Fields')
+ )
+
+def validate_totals(einvoice):
+ item_list = einvoice['ItemList']
+ value_details = einvoice['ValDtls']
+
+ total_item_ass_value = 0
+ total_item_cgst_value = 0
+ total_item_sgst_value = 0
+ total_item_igst_value = 0
+ total_item_value = 0
+ for item in item_list:
+ total_item_ass_value += flt(item['AssAmt'])
+ total_item_cgst_value += flt(item['CgstAmt'])
+ total_item_sgst_value += flt(item['SgstAmt'])
+ total_item_igst_value += flt(item['IgstAmt'])
+ total_item_value += flt(item['TotItemVal'])
+
+ if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
+ frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
+
+ if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
+ frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
+
+ if abs(flt(value_details['CgstVal']) + flt(value_details['SgstVal']) - total_item_cgst_value - total_item_sgst_value) > 1:
+ frappe.throw(_('CGST + SGST value of the items is not equal to total CGST + SGST value. Please review taxes for any correction.'))
+
+ if abs(flt(value_details['IgstVal']) - total_item_igst_value) > 1:
+ frappe.throw(_('IGST value of all items is not equal to total IGST value. Please review taxes for any correction.'))
+
+ if abs(
+ flt(value_details['TotInvVal']) + flt(value_details['Discount']) -
+ flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) -
+ total_item_value
+ ) > 1:
+ frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
+
+ calculated_invoice_value = \
+ flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
+ + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
+ + flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount'])
+
+ if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
+ frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
+
+def make_einvoice(invoice):
+ validate_mandatory_fields(invoice)
+
+ schema = read_json('einv_template')
+
+ transaction_details = get_transaction_details(invoice)
+ item_list = get_item_list(invoice)
+ doc_details = get_doc_details(invoice)
+ invoice_value_details = get_invoice_value_details(invoice)
+ seller_details = get_party_details(invoice.company_address)
+
+ if invoice.gst_category == 'Overseas':
+ buyer_details = get_overseas_address_details(invoice.customer_address)
+ else:
+ buyer_details = get_party_details(invoice.customer_address)
+ place_of_supply = get_place_of_supply(invoice, invoice.doctype)
+ if place_of_supply:
+ place_of_supply = place_of_supply.split('-')[0]
+ else:
+ place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
+ buyer_details.update(dict(place_of_supply=place_of_supply))
+
+ seller_details.update(dict(legal_name=invoice.company))
+ buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
+
+ shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
+ if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
+ if invoice.gst_category == 'Overseas':
+ shipping_details = get_overseas_address_details(invoice.shipping_address_name)
+ else:
+ shipping_details = get_party_details(invoice.shipping_address_name, skip_gstin_validation=True)
+
+ dispatch_details = frappe._dict({})
+ if invoice.dispatch_address_name:
+ dispatch_details = get_party_details(invoice.dispatch_address_name, skip_gstin_validation=True)
+
+ if invoice.is_pos and invoice.base_paid_amount:
+ payment_details = get_payment_details(invoice)
+
+ if invoice.is_return and invoice.return_against:
+ prev_doc_details = get_return_doc_reference(invoice)
+
+ if invoice.transporter and not invoice.is_return:
+ eway_bill_details = get_eway_bill_details(invoice)
+
+ # not yet implemented
+ period_details = export_details = frappe._dict({})
+
+ einvoice = schema.format(
+ transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details,
+ seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
+ item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details,
+ period_details=period_details, prev_doc_details=prev_doc_details,
+ export_details=export_details, eway_bill_details=eway_bill_details
+ )
+
+ try:
+ einvoice = safe_json_load(einvoice)
+ einvoice = santize_einvoice_fields(einvoice)
+ except Exception:
+ show_link_to_error_log(invoice, einvoice)
+
+ try:
+ validate_totals(einvoice)
+ except Exception:
+ log_error(einvoice)
+ raise
+
+ return einvoice
+
+def show_link_to_error_log(invoice, einvoice):
+ err_log = log_error(einvoice)
+ link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
+ frappe.throw(
+ _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
+ invoice.name, link_to_error_log),
+ title=_('E Invoice Creation Failed')
+ )
+
+def log_error(data=None):
+ if isinstance(data, str):
+ data = json.loads(data)
+
+ seperator = "--" * 50
+ err_tb = traceback.format_exc()
+ err_msg = str(sys.exc_info()[1])
+ data = json.dumps(data, indent=4)
+
+ message = "\n".join([
+ "Error", err_msg, seperator,
+ "Data:", data, seperator,
+ "Exception:", err_tb
+ ])
+ return frappe.log_error(title=_('E Invoice Request Failed'), message=message)
+
+def santize_einvoice_fields(einvoice):
+ int_fields = ["Pin","Distance","CrDay"]
+ float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
+ copy = einvoice.copy()
+ for key, value in copy.items():
+ if isinstance(value, list):
+ for idx, d in enumerate(value):
+ santized_dict = santize_einvoice_fields(d)
+ if santized_dict:
+ einvoice[key][idx] = santized_dict
+ else:
+ einvoice[key].pop(idx)
+
+ if not einvoice[key]:
+ einvoice.pop(key, None)
+
+ elif isinstance(value, dict):
+ santized_dict = santize_einvoice_fields(value)
+ if santized_dict:
+ einvoice[key] = santized_dict
+ else:
+ einvoice.pop(key, None)
+
+ elif not value or value == "None":
+ einvoice.pop(key, None)
+
+ elif key in float_fields:
+ einvoice[key] = flt(value, 2)
+
+ elif key in int_fields:
+ einvoice[key] = cint(value)
+
+ return einvoice
+
+def safe_json_load(json_string):
+ try:
+ return json.loads(json_string)
+ except json.JSONDecodeError as e:
+ # print a snippet of 40 characters around the location where error occured
+ pos = e.pos
+ start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
+ snippet = json_string[start:end]
+ frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
+
+class RequestFailed(Exception):
+ pass
+class CancellationNotAllowed(Exception):
+ pass
+
+class GSPConnector():
+ def __init__(self, doctype=None, docname=None):
+ self.doctype = doctype
+ self.docname = docname
+
+ self.set_invoice()
+ self.set_credentials()
+
+ # authenticate url is same for sandbox & live
+ self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
+ self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test'
+
+ self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
+ self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+ self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
+ self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
+ self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
+ self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
+
+ def set_invoice(self):
+ self.invoice = None
+ if self.doctype and self.docname:
+ self.invoice = frappe.get_cached_doc(self.doctype, self.docname)
+
+ def set_credentials(self):
+ self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+
+ if not self.e_invoice_settings.enable:
+ frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
+
+ if self.invoice:
+ gstin = self.get_seller_gstin()
+ credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
+ if credentials_for_gstin:
+ self.credentials = credentials_for_gstin[0]
+ else:
+ frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
+ else:
+ self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
+
+ def get_seller_gstin(self):
+ gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
+ if not gstin:
+ frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
+ return gstin
+
+ def get_auth_token(self):
+ if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
+ self.fetch_auth_token()
+
+ return self.e_invoice_settings.auth_token
+
+ def make_request(self, request_type, url, headers=None, data=None):
+ if request_type == 'post':
+ res = make_post_request(url, headers=headers, data=data)
+ else:
+ res = make_get_request(url, headers=headers, data=data)
+
+ self.log_request(url, headers, data, res)
+ return res
+
+ def log_request(self, url, headers, data, res):
+ headers.update({ 'password': self.credentials.password })
+ request_log = frappe.get_doc({
+ "doctype": "E Invoice Request Log",
+ "user": frappe.session.user,
+ "reference_invoice": self.invoice.name if self.invoice else None,
+ "url": url,
+ "headers": json.dumps(headers, indent=4) if headers else None,
+ "data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
+ "response": json.dumps(res, indent=4) if res else None
+ })
+ request_log.save(ignore_permissions=True)
+ frappe.db.commit()
+
+ def get_client_credentials(self):
+ if self.e_invoice_settings.client_id and self.e_invoice_settings.client_secret:
+ return self.e_invoice_settings.client_id, self.e_invoice_settings.get_password('client_secret')
+
+ return frappe.conf.einvoice_client_id, frappe.conf.einvoice_client_secret
+
+ def fetch_auth_token(self):
+ client_id, client_secret = self.get_client_credentials()
+ headers = {
+ 'gspappid': client_id,
+ 'gspappsecret': client_secret
+ }
+ res = {}
+ try:
+ res = self.make_request('post', self.authenticate_url, headers)
+ self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
+ self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
+ self.e_invoice_settings.save(ignore_permissions=True)
+ self.e_invoice_settings.reload()
+
+ except Exception:
+ log_error(res)
+ self.raise_error(True)
+
+ def get_headers(self):
+ return {
+ 'content-type': 'application/json',
+ 'user_name': self.credentials.username,
+ 'password': self.credentials.get_password(),
+ 'gstin': self.credentials.gstin,
+ 'authorization': self.get_auth_token(),
+ 'requestid': str(base64.b64encode(os.urandom(18))),
+ }
+
+ def fetch_gstin_details(self, gstin):
+ headers = self.get_headers()
+
+ try:
+ params = '?gstin={gstin}'.format(gstin=gstin)
+ res = self.make_request('get', self.gstin_details_url + params, headers)
+ if res.get('success'):
+ return res.get('result')
+ else:
+ log_error(res)
+ raise RequestFailed
+
+ except RequestFailed:
+ self.raise_error()
+
+ except Exception:
+ log_error()
+ self.raise_error(True)
+ @staticmethod
+ def get_gstin_details(gstin):
+ '''fetch and cache GSTIN details'''
+ if not hasattr(frappe.local, 'gstin_cache'):
+ frappe.local.gstin_cache = {}
+
+ key = gstin
+ gsp_connector = GSPConnector()
+ details = gsp_connector.fetch_gstin_details(gstin)
+
+ frappe.local.gstin_cache[key] = details
+ frappe.cache().hset('gstin_cache', key, details)
+ return details
+
+ def generate_irn(self):
+ data = {}
+ try:
+ headers = self.get_headers()
+ einvoice = make_einvoice(self.invoice)
+ data = json.dumps(einvoice, indent=4)
+ res = self.make_request('post', self.generate_irn_url, headers, data)
+
+ if res.get('success'):
+ self.set_einvoice_data(res.get('result'))
+
+ elif '2150' in res.get('message'):
+ # IRN already generated but not updated in invoice
+ # Extract the IRN from the response description and fetch irn details
+ irn = res.get('result')[0].get('Desc').get('Irn')
+ irn_details = self.get_irn_details(irn)
+ if irn_details:
+ self.set_einvoice_data(irn_details)
+ else:
+ raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
+ Contact ERPNext support to resolve the issue.')
+
+ else:
+ raise RequestFailed
+
+ except RequestFailed:
+ errors = self.sanitize_error_message(res.get('message'))
+ self.set_failed_status(errors=errors)
+ self.raise_error(errors=errors)
+
+ except Exception as e:
+ self.set_failed_status(errors=str(e))
+ log_error(data)
+ self.raise_error(True)
+
+ @staticmethod
+ def bulk_generate_irn(invoices):
+ gsp_connector = GSPConnector()
+ gsp_connector.doctype = 'Sales Invoice'
+
+ failed = []
+
+ for invoice in invoices:
+ try:
+ gsp_connector.docname = invoice
+ gsp_connector.set_invoice()
+ gsp_connector.set_credentials()
+ gsp_connector.generate_irn()
+
+ except Exception as e:
+ failed.append({
+ 'docname': invoice,
+ 'message': str(e)
+ })
+
+ return failed
+
+ def get_irn_details(self, irn):
+ headers = self.get_headers()
+
+ try:
+ params = '?irn={irn}'.format(irn=irn)
+ res = self.make_request('get', self.irn_details_url + params, headers)
+ if res.get('success'):
+ return res.get('result')
+ else:
+ raise RequestFailed
+
+ except RequestFailed:
+ errors = self.sanitize_error_message(res.get('message'))
+ self.raise_error(errors=errors)
+
+ except Exception:
+ log_error()
+ self.raise_error(True)
+
+ def cancel_irn(self, irn, reason, remark):
+ data, res = {}, {}
+ try:
+ # validate cancellation
+ if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24:
+ frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
+ if not irn:
+ frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
+
+ headers = self.get_headers()
+ data = json.dumps({
+ 'Irn': irn,
+ 'Cnlrsn': reason,
+ 'Cnlrem': remark
+ }, indent=4)
+
+ res = self.make_request('post', self.cancel_irn_url, headers, data)
+ if res.get('success') or '9999' in res.get('message'):
+ self.invoice.irn_cancelled = 1
+ self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else ""
+ self.invoice.einvoice_status = 'Cancelled'
+ self.invoice.flags.updater_reference = {
+ 'doctype': self.invoice.doctype,
+ 'docname': self.invoice.name,
+ 'label': _('IRN Cancelled - {}').format(remark)
+ }
+ self.update_invoice()
+
+ else:
+ raise RequestFailed
+
+ except RequestFailed:
+ errors = self.sanitize_error_message(res.get('message'))
+ self.set_failed_status(errors=errors)
+ self.raise_error(errors=errors)
+
+ except CancellationNotAllowed as e:
+ self.set_failed_status(errors=str(e))
+ self.raise_error(errors=str(e))
+
+ except Exception as e:
+ self.set_failed_status(errors=str(e))
+ log_error(data)
+ self.raise_error(True)
+
+ @staticmethod
+ def bulk_cancel_irn(invoices, reason, remark):
+ gsp_connector = GSPConnector()
+ gsp_connector.doctype = 'Sales Invoice'
+
+ failed = []
+
+ for invoice in invoices:
+ try:
+ gsp_connector.docname = invoice
+ gsp_connector.set_invoice()
+ gsp_connector.set_credentials()
+ irn = gsp_connector.invoice.irn
+ gsp_connector.cancel_irn(irn, reason, remark)
+
+ except Exception as e:
+ failed.append({
+ 'docname': invoice,
+ 'message': str(e)
+ })
+
+ return failed
+
+ def generate_eway_bill(self, **kwargs):
+ args = frappe._dict(kwargs)
+
+ headers = self.get_headers()
+ eway_bill_details = get_eway_bill_details(args)
+ data = json.dumps({
+ 'Irn': args.irn,
+ 'Distance': cint(eway_bill_details.distance),
+ 'TransMode': eway_bill_details.mode_of_transport,
+ 'TransId': eway_bill_details.gstin,
+ 'TransName': eway_bill_details.transporter,
+ 'TrnDocDt': eway_bill_details.document_date,
+ 'TrnDocNo': eway_bill_details.document_name,
+ 'VehNo': eway_bill_details.vehicle_no,
+ 'VehType': eway_bill_details.vehicle_type
+ }, indent=4)
+
+ try:
+ res = self.make_request('post', self.generate_ewaybill_url, headers, data)
+ if res.get('success'):
+ self.invoice.ewaybill = res.get('result').get('EwbNo')
+ self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill')
+ self.invoice.eway_bill_cancelled = 0
+ self.invoice.update(args)
+ self.invoice.flags.updater_reference = {
+ 'doctype': self.invoice.doctype,
+ 'docname': self.invoice.name,
+ 'label': _('E-Way Bill Generated')
+ }
+ self.update_invoice()
+
+ else:
+ raise RequestFailed
+
+ except RequestFailed:
+ errors = self.sanitize_error_message(res.get('message'))
+ self.raise_error(errors=errors)
+
+ except Exception:
+ log_error(data)
+ self.raise_error(True)
+
+ def cancel_eway_bill(self, eway_bill, reason, remark):
+ headers = self.get_headers()
+ data = json.dumps({
+ 'ewbNo': eway_bill,
+ 'cancelRsnCode': reason,
+ 'cancelRmrk': remark
+ }, indent=4)
+ headers["username"] = headers["user_name"]
+ del headers["user_name"]
+ try:
+ res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
+ if res.get('success'):
+ self.invoice.ewaybill = ''
+ self.invoice.eway_bill_cancelled = 1
+ self.invoice.flags.updater_reference = {
+ 'doctype': self.invoice.doctype,
+ 'docname': self.invoice.name,
+ 'label': _('E-Way Bill Cancelled - {}').format(remark)
+ }
+ self.update_invoice()
+
+ else:
+ raise RequestFailed
+
+ except RequestFailed:
+ errors = self.sanitize_error_message(res.get('message'))
+ self.raise_error(errors=errors)
+
+ except Exception:
+ log_error(data)
+ self.raise_error(True)
+
+ def sanitize_error_message(self, message):
+ '''
+ On validation errors, response message looks something like this:
+ message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
+ 3095 : Supplier GSTIN is inactive'
+ we search for string between ':' to extract the error messages
+ errors = [
+ ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
+ ': Test'
+ ]
+ then we trim down the message by looping over errors
+ '''
+ if not message:
+ return []
+
+ errors = re.findall(': [^:]+', message)
+ for idx, e in enumerate(errors):
+ # remove colons
+ errors[idx] = errors[idx].replace(':', '').strip()
+ # if not last
+ if idx != len(errors) - 1:
+ # remove last 7 chars eg: ', 3095 '
+ errors[idx] = errors[idx][:-6]
+
+ return errors
+
+ def raise_error(self, raise_exception=False, errors=None):
+ if errors is None:
+ errors = []
+ title = _('E Invoice Request Failed')
+ if errors:
+ frappe.throw(errors, title=title, as_list=1)
+ else:
+ link_to_error_list = '<a href="/app/error-log" target="_blank">Error Log</a>'
+ frappe.msgprint(
+ _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
+ title=title,
+ raise_exception=raise_exception,
+ indicator='red'
+ )
+
+ def set_einvoice_data(self, res):
+ enc_signed_invoice = res.get('SignedInvoice')
+ dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data']
+
+ self.invoice.irn = res.get('Irn')
+ self.invoice.ewaybill = res.get('EwbNo')
+ self.invoice.eway_bill_validity = res.get('EwbValidTill')
+ self.invoice.ack_no = res.get('AckNo')
+ self.invoice.ack_date = res.get('AckDt')
+ self.invoice.signed_einvoice = dec_signed_invoice
+ self.invoice.ack_no = res.get('AckNo')
+ self.invoice.ack_date = res.get('AckDt')
+ self.invoice.signed_qr_code = res.get('SignedQRCode')
+ self.invoice.einvoice_status = 'Generated'
+
+ self.attach_qrcode_image()
+
+ self.invoice.flags.updater_reference = {
+ 'doctype': self.invoice.doctype,
+ 'docname': self.invoice.name,
+ 'label': _('IRN Generated')
+ }
+ self.update_invoice()
+
+ def attach_qrcode_image(self):
+ qrcode = self.invoice.signed_qr_code
+ doctype = self.invoice.doctype
+ docname = self.invoice.name
+ filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__")
+
+ qr_image = io.BytesIO()
+ url = qrcreate(qrcode, error='L')
+ url.png(qr_image, scale=2, quiet_zone=1)
+ _file = frappe.get_doc({
+ "doctype": "File",
+ "file_name": filename,
+ "attached_to_doctype": doctype,
+ "attached_to_name": docname,
+ "attached_to_field": "qrcode_image",
+ "is_private": 0,
+ "content": qr_image.getvalue()})
+ _file.save()
+ frappe.db.commit()
+ self.invoice.qrcode_image = _file.file_url
+
+ def update_invoice(self):
+ self.invoice.flags.ignore_validate_update_after_submit = True
+ self.invoice.flags.ignore_validate = True
+ self.invoice.save()
+
+ def set_failed_status(self, errors=None):
+ frappe.db.rollback()
+ self.invoice.einvoice_status = 'Failed'
+ self.invoice.failure_description = self.get_failure_message(errors) if errors else ""
+ self.update_invoice()
+ frappe.db.commit()
+
+ def get_failure_message(self, errors):
+ if isinstance(errors, list):
+ errors = ', '.join(errors)
+ return errors
+
+def sanitize_for_json(string):
+ """Escape JSON specific characters from a string."""
+
+ # json.dumps adds double-quotes to the string. Indexing to remove them.
+ return json.dumps(string)[1:-1]
+
+@frappe.whitelist()
+def get_einvoice(doctype, docname):
+ invoice = frappe.get_doc(doctype, docname)
+ return make_einvoice(invoice)
+
+@frappe.whitelist()
+def generate_irn(doctype, docname):
+ gsp_connector = GSPConnector(doctype, docname)
+ gsp_connector.generate_irn()
+
+@frappe.whitelist()
+def cancel_irn(doctype, docname, irn, reason, remark):
+ gsp_connector = GSPConnector(doctype, docname)
+ gsp_connector.cancel_irn(irn, reason, remark)
+
+@frappe.whitelist()
+def generate_eway_bill(doctype, docname, **kwargs):
+ gsp_connector = GSPConnector(doctype, docname)
+ gsp_connector.generate_eway_bill(**kwargs)
+
+@frappe.whitelist()
+def cancel_eway_bill(doctype, docname):
+ # TODO: uncomment when eway_bill api from Adequare is enabled
+ # gsp_connector = GSPConnector(doctype, docname)
+ # gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
+
+ frappe.db.set_value(doctype, docname, 'ewaybill', '')
+ frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
+
+@frappe.whitelist()
+def generate_einvoices(docnames):
+ docnames = json.loads(docnames) or []
+
+ if len(docnames) < 10:
+ failures = GSPConnector.bulk_generate_irn(docnames)
+ frappe.local.message_log = []
+
+ if failures:
+ show_bulk_action_failure_message(failures)
+
+ success = len(docnames) - len(failures)
+ frappe.msgprint(
+ _('{} e-invoices generated successfully').format(success),
+ title=_('Bulk E-Invoice Generation Complete')
+ )
+
+ else:
+ enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames)
+
+def schedule_bulk_generate_irn(docnames):
+ failures = GSPConnector.bulk_generate_irn(docnames)
+ frappe.local.message_log = []
+
+ frappe.publish_realtime("bulk_einvoice_generation_complete", {
+ "user": frappe.session.user,
+ "failures": failures,
+ "invoices": docnames
+ })
+
+def show_bulk_action_failure_message(failures):
+ for doc in failures:
+ docname = '<a href="sales-invoice/{0}">{0}</a>'.format(doc.get('docname'))
+ message = doc.get('message').replace("'", '"')
+ if message[0] == '[':
+ errors = json.loads(message)
+ error_list = ''.join(['<li>{}</li>'.format(err) for err in errors])
+ message = '''{} has following errors:<br>
+ <ul style="padding-left: 20px; padding-top: 5px">{}</ul>'''.format(docname, error_list)
+ else:
+ message = '{} - {}'.format(docname, message)
+
+ frappe.msgprint(
+ message,
+ title=_('Bulk E-Invoice Generation Complete'),
+ indicator='red'
+ )
+
+@frappe.whitelist()
+def cancel_irns(docnames, reason, remark):
+ docnames = json.loads(docnames) or []
+
+ if len(docnames) < 10:
+ failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
+ frappe.local.message_log = []
+
+ if failures:
+ show_bulk_action_failure_message(failures)
+
+ success = len(docnames) - len(failures)
+ frappe.msgprint(
+ _('{} e-invoices cancelled successfully').format(success),
+ title=_('Bulk E-Invoice Cancellation Complete')
+ )
+ else:
+ enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark)
+
+def schedule_bulk_cancel_irn(docnames, reason, remark):
+ failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
+ frappe.local.message_log = []
+
+ frappe.publish_realtime("bulk_einvoice_cancellation_complete", {
+ "user": frappe.session.user,
+ "failures": failures,
+ "invoices": docnames
+ })
+
+def enqueue_bulk_action(job, **kwargs):
+ check_scheduler_status()
+
+ enqueue(
+ job,
+ **kwargs,
+ queue="long",
+ timeout=10000,
+ event="processing_bulk_einvoice_action",
+ now=frappe.conf.developer_mode or frappe.flags.in_test,
+ )
+
+ if job == schedule_bulk_generate_irn:
+ msg = _('E-Invoices will be generated in a background process.')
+ else:
+ msg = _('E-Invoices will be cancelled in a background process.')
+
+ frappe.msgprint(msg, alert=1)
+
+def check_scheduler_status():
+ if is_scheduler_inactive() and not frappe.flags.in_test:
+ frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
+
+def job_already_enqueued(job_name):
+ enqueued_jobs = [d.get("job_name") for d in get_info()]
+ if job_name in enqueued_jobs:
+ return True
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index c0dcb70..074bd52 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -60,7 +60,7 @@
def add_custom_roles_for_reports():
for report_name in ('GST Sales Register', 'GST Purchase Register',
- 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'):
+ 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'):
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
frappe.get_doc(dict(
@@ -99,7 +99,7 @@
)).insert()
def add_permissions():
- for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
+ for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'):
add_permission(doctype, 'All', 0)
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
add_permission(doctype, role, 0)
@@ -115,9 +115,11 @@
def add_print_formats():
frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
+ frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0)
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
+ frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
def make_property_setters(patch=False):
# GST rules do not allow for an invoice no. bigger than 16 characters
@@ -453,7 +455,7 @@
'fieldname': 'ewaybill',
'label': 'E-Way Bill No.',
'fieldtype': 'Data',
- 'depends_on': 'eval:(doc.docstatus === 1)',
+ 'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)',
'allow_on_submit': 1,
'insert_after': 'tax_id',
'translatable': 0,
@@ -481,6 +483,46 @@
fetch_from='customer_address.gstin', print_hide=1, read_only=1)
]
+ si_einvoice_fields = [
+ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+ depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+
+ dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+ depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'),
+
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
+
+ dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+ depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+ dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+ print_hide=1, hidden=1),
+
+ dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+ dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
+ no_copy=1, print_hide=1),
+
+ dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+ no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+ options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+ dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+ hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+ ]
+
custom_fields = {
'Address': [
dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
@@ -493,7 +535,7 @@
'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields,
'Purchase Order': purchase_invoice_gst_fields,
'Purchase Receipt': purchase_invoice_gst_fields,
- 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
+ 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
'POS Invoice': sales_invoice_gst_fields,
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
'Payment Entry': payment_entry_fields,
@@ -567,16 +609,16 @@
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
dict(fieldname='arrear_component', label='Arrear Component',
- fieldtype='Link', options='Salary Component', insert_after='hra_component'),
+ fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
dict(fieldname='non_profit_section', label='Non Profit Settings',
- fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
+ fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
dict(fieldname='company_80g_number', label='80G Number',
fieldtype='Data', insert_after='non_profit_section'),
dict(fieldname='with_effect_from', label='80G With Effect From',
fieldtype='Date', insert_after='company_80g_number'),
dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
dict(fieldname='pan_details', label='PAN Number',
- fieldtype='Data', insert_after='with_effect_from')
+ fieldtype='Data', insert_after='non_profit_column_break')
],
'Employee Tax Exemption Declaration':[
dict(fieldname='hra_section', label='HRA Exemption',
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index d443f9c..8715ef5 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -219,6 +219,7 @@
if not party_details.place_of_supply: return party_details
if not party_details.company_gstin: return party_details
+ if not party_details.supplier_gstin: return party_details
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
index 14d5495..052fb2a 100644
--- a/erpnext/regional/report/datev/test_datev.py
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -1,9 +1,9 @@
import zipfile
+from io import BytesIO
from unittest import TestCase
import frappe
from frappe.utils import cstr, now_datetime, today
-from six import BytesIO
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.regional.germany.utils.datev.datev_constants import (
diff --git a/erpnext/restaurant/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py
similarity index 100%
rename from erpnext/restaurant/__init__.py
rename to erpnext/regional/report/e_invoice_summary/__init__.py
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
new file mode 100644
index 0000000..4713217
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
@@ -0,0 +1,55 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["E-Invoice Summary"] = {
+ "filters": [
+ {
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "fieldname": "company",
+ "label": __("Company"),
+ "default": frappe.defaults.get_user_default("Company"),
+ },
+ {
+ "fieldtype": "Link",
+ "options": "Customer",
+ "fieldname": "customer",
+ "label": __("Customer")
+ },
+ {
+ "fieldtype": "Date",
+ "reqd": 1,
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldtype": "Date",
+ "reqd": 1,
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "default": frappe.datetime.get_today(),
+ },
+ {
+ "fieldtype": "Select",
+ "fieldname": "status",
+ "label": __("Status"),
+ "options": "\nPending\nGenerated\nCancelled\nFailed"
+ }
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "einvoice_status" && value) {
+ if (value == 'Pending') value = `<span class="bold" style="color: var(--text-on-orange)">${value}</span>`;
+ else if (value == 'Generated') value = `<span class="bold" style="color: var(--text-on-green)">${value}</span>`;
+ else if (value == 'Cancelled') value = `<span class="bold" style="color: var(--text-on-red)">${value}</span>`;
+ else if (value == 'Failed') value = `<span class="bold" style="color: var(--text-on-red)">${value}</span>`;
+ }
+
+ return value;
+ }
+};
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
new file mode 100644
index 0000000..d0000ad
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-12 11:23:37.312294",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Logo",
+ "modified": "2021-03-13 12:36:48.689413",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E-Invoice Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Invoice",
+ "report_name": "E-Invoice Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Administrator"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
new file mode 100644
index 0000000..2110c44
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+
+
+def execute(filters=None):
+ validate_filters(filters)
+
+ columns = get_columns()
+ data = get_data(filters)
+
+ return columns, data
+
+def validate_filters(filters=None):
+ if filters is None:
+ filters = {}
+ filters = frappe._dict(filters)
+
+ if not filters.company:
+ frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter'))
+ if filters.company:
+ # validate if company has e-invoicing enabled
+ pass
+ if not filters.from_date or not filters.to_date:
+ frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter'))
+ if filters.from_date > filters.to_date:
+ frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter'))
+
+def get_data(filters=None):
+ if filters is None:
+ filters = {}
+ query_filters = {
+ 'posting_date': ['between', [filters.from_date, filters.to_date]],
+ 'einvoice_status': ['is', 'set'],
+ 'company': filters.company
+ }
+ if filters.customer:
+ query_filters['customer'] = filters.customer
+ if filters.status:
+ query_filters['einvoice_status'] = filters.status
+
+ data = frappe.get_all(
+ 'Sales Invoice',
+ filters=query_filters,
+ fields=[d.get('fieldname') for d in get_columns()]
+ )
+
+ return data
+
+def get_columns():
+ return [
+ {
+ "fieldtype": "Date",
+ "fieldname": "posting_date",
+ "label": _("Posting Date"),
+ "width": 0
+ },
+ {
+ "fieldtype": "Link",
+ "fieldname": "name",
+ "label": _("Sales Invoice"),
+ "options": "Sales Invoice",
+ "width": 140
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "einvoice_status",
+ "label": _("Status"),
+ "width": 100
+ },
+ {
+ "fieldtype": "Link",
+ "fieldname": "customer",
+ "options": "Customer",
+ "label": _("Customer")
+ },
+ {
+ "fieldtype": "Check",
+ "fieldname": "is_return",
+ "label": _("Is Return"),
+ "width": 85
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "ack_no",
+ "label": "Ack. No.",
+ "width": 145
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "ack_date",
+ "label": "Ack. Date",
+ "width": 165
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "irn",
+ "label": _("IRN No."),
+ "width": 250
+ },
+ {
+ "fieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "fieldname": "base_grand_total",
+ "label": _("Grand Total"),
+ "width": 120
+ }
+ ]
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index ef2bdb6..4b98978 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -53,7 +53,8 @@
{ "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") },
{ "value": "CDNR-UNREG", "label": __("Credit/Debit Notes (Unregistered) - 9B") },
{ "value": "EXPORT", "label": __("Export Invoice - 6A") },
- { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") }
+ { "value": "Advances", "label": __("Tax Liability (Advances Received) - 11A(1), 11A(2)") },
+ { "value": "NIL Rated", "label": __("NIL RATED/EXEMPTED Invoices") }
],
"default": "B2B"
}
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 11b684d..e50ff18 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -40,7 +40,8 @@
port_code,
shipping_bill_number,
shipping_bill_date,
- reason_for_issuing_document
+ reason_for_issuing_document,
+ company_gstin
"""
def run(self):
@@ -62,6 +63,8 @@
self.get_b2c_data()
elif self.filters.get("type_of_business") == "Advances":
self.get_advance_data()
+ elif self.filters.get("type_of_business") == "NIL Rated":
+ self.get_nil_rated_invoices()
elif self.invoices:
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv)
@@ -91,6 +94,57 @@
row= [key[0], key[1], value[0], value[1]]
self.data.append(row)
+ def get_nil_rated_invoices(self):
+ nil_exempt_output = [
+ {
+ "description": "Inter-State supplies to registered persons",
+ "nil_rated": 0.0,
+ "exempted": 0.0,
+ "non_gst": 0.0
+ },
+ {
+ "description": "Intra-State supplies to registered persons",
+ "nil_rated": 0.0,
+ "exempted": 0.0,
+ "non_gst": 0.0
+ },
+ {
+ "description": "Inter-State supplies to unregistered persons",
+ "nil_rated": 0.0,
+ "exempted": 0.0,
+ "non_gst": 0.0
+ },
+ {
+ "description": "Intra-State supplies to unregistered persons",
+ "nil_rated": 0.0,
+ "exempted": 0.0,
+ "non_gst": 0.0
+ }
+ ]
+
+ for invoice, details in self.nil_exempt_non_gst.items():
+ invoice_detail = self.invoices.get(invoice)
+ if invoice_detail.get('gst_category') in ("Registered Regular", "Deemed Export", "SEZ"):
+ if is_inter_state(invoice_detail):
+ nil_exempt_output[0]["nil_rated"] += details[0]
+ nil_exempt_output[0]["exempted"] += details[1]
+ nil_exempt_output[0]["non_gst"] += details[2]
+ else:
+ nil_exempt_output[1]["nil_rated"] += details[0]
+ nil_exempt_output[1]["exempted"] += details[1]
+ nil_exempt_output[1]["non_gst"] += details[2]
+ else:
+ if is_inter_state(invoice_detail):
+ nil_exempt_output[2]["nil_rated"] += details[0]
+ nil_exempt_output[2]["exempted"] += details[1]
+ nil_exempt_output[2]["non_gst"] += details[2]
+ else:
+ nil_exempt_output[3]["nil_rated"] += details[0]
+ nil_exempt_output[3]["exempted"] += details[1]
+ nil_exempt_output[3]["non_gst"] += details[2]
+
+ self.data = nil_exempt_output
+
def get_b2c_data(self):
b2cs_output = {}
@@ -240,10 +294,11 @@
def get_invoice_items(self):
self.invoice_items = frappe._dict()
self.item_tax_rate = frappe._dict()
+ self.nil_exempt_non_gst = {}
items = frappe.db.sql("""
- select item_code, parent, taxable_value, base_net_amount, item_tax_rate
- from `tab%s Item`
+ select item_code, parent, taxable_value, base_net_amount, item_tax_rate, is_nil_exempt,
+ is_non_gst from `tab%s Item`
where parent in (%s)
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
@@ -260,6 +315,16 @@
tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, [])
tax_rate_dict.append(rate)
+ if d.is_nil_exempt:
+ self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0])
+ if item_tax_rate:
+ self.nil_exempt_non_gst[d.parent][0] += d.get('taxable_value', 0)
+ else:
+ self.nil_exempt_non_gst[d.parent][1] += d.get('taxable_value', 0)
+ elif d.is_non_gst:
+ self.nil_exempt_non_gst.setdefault(d.parent, [0.0, 0.0, 0.0])
+ self.nil_exempt_non_gst[d.parent][2] += d.get('taxable_value', 0)
+
def get_items_based_on_tax_rate(self):
self.tax_details = frappe.db.sql("""
select
@@ -322,21 +387,24 @@
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
def get_columns(self):
- self.tax_columns = [
- {
- "fieldname": "rate",
- "label": "Rate",
- "fieldtype": "Int",
- "width": 60
- },
- {
- "fieldname": "taxable_value",
- "label": "Taxable Value",
- "fieldtype": "Currency",
- "width": 100
- }
- ]
self.other_columns = []
+ self.tax_columns = []
+
+ if self.filters.get("type_of_business") != "NIL Rated":
+ self.tax_columns = [
+ {
+ "fieldname": "rate",
+ "label": "Rate",
+ "fieldtype": "Int",
+ "width": 60
+ },
+ {
+ "fieldname": "taxable_value",
+ "label": "Taxable Value",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
if self.filters.get("type_of_business") == "B2B":
self.invoice_columns = [
@@ -705,6 +773,33 @@
"width": 100
}
]
+ elif self.filters.get("type_of_business") == "NIL Rated":
+ self.invoice_columns = [
+ {
+ "fieldname": "description",
+ "label": "Description",
+ "fieldtype": "Data",
+ "width": 420
+ },
+ {
+ "fieldname": "nil_rated",
+ "label": "Nil Rated",
+ "fieldtype": "Currency",
+ "width": 200
+ },
+ {
+ "fieldname": "exempted",
+ "label": "Exempted",
+ "fieldtype": "Currency",
+ "width": 200
+ },
+ {
+ "fieldname": "non_gst",
+ "label": "Non GST",
+ "fieldtype": "Currency",
+ "width": 200
+ }
+ ]
self.columns = self.invoice_columns + self.tax_columns + self.other_columns
@@ -768,6 +863,11 @@
out = get_advances_json(res, gstin)
gst_json["at"] = out
+ elif filters["type_of_business"] == "NIL Rated":
+ res = report_data[:-1]
+ out = get_exempted_json(res)
+ gst_json["nil"] = out
+
return {
'report_name': report_name,
'report_type': filters['type_of_business'],
@@ -980,6 +1080,36 @@
return out
+def get_exempted_json(data):
+ out = {
+ "inv": [
+ {
+ "sply_ty": "INTRB2B"
+ },
+ {
+ "sply_ty": "INTRAB2B"
+ },
+ {
+ "sply_ty": "INTRB2C"
+ },
+ {
+ "sply_ty": "INTRAB2C"
+ }
+ ]
+ }
+
+ for i, v in enumerate(data):
+ if data[i].get('nil_rated'):
+ out['inv'][i]['nil_amt'] = data[i]['nil_rated']
+
+ if data[i].get('exempted'):
+ out['inv'][i]['expt_amt'] = data[i]['exempted']
+
+ if data[i].get('non_gst'):
+ out['inv'][i]['ngsup_amt'] = data[i]['non_gst']
+
+ return out
+
def get_invoice_type_for_cdnr(row):
if row.get('gst_category') == 'SEZ':
if row.get('export_type') == 'WPAY':
@@ -1064,3 +1194,9 @@
frappe.response['filecontent'] = data['data']
frappe.response['content_type'] = 'application/json'
frappe.response['type'] = 'download'
+
+def is_inter_state(invoice_detail):
+ if invoice_detail.place_of_supply.split("-")[0] != invoice_detail.company_gstin[:2]:
+ return True
+ else:
+ return False
\ No newline at end of file
diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py
index 2e31c03..15d524d 100644
--- a/erpnext/regional/saudi_arabia/setup.py
+++ b/erpnext/regional/saudi_arabia/setup.py
@@ -3,12 +3,10 @@
import frappe
from frappe.permissions import add_permission, update_permission_property
-from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields
from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def setup(company=None, patch=True):
- uae_custom_fields()
add_print_formats()
add_permissions()
make_custom_fields()
@@ -40,38 +38,67 @@
- Company Name in Arabic
- Address in Arabic
"""
+ is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
+ fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
+ print_hide=1)
+
+ is_exempt = dict(fieldname='is_exempt', label='Is Exempt',
+ fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated',
+ print_hide=1)
+
+ purchase_invoice_fields = [
+ dict(fieldname='company_trn', label='Company TRN',
+ fieldtype='Read Only', insert_after='shipping_address',
+ fetch_from='company.tax_id', print_hide=1),
+ dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
+ fieldtype='Read Only', insert_after='supplier_name',
+ fetch_from='supplier.supplier_name_in_arabic', print_hide=1)
+ ]
+
+ sales_invoice_fields = [
+ dict(fieldname='company_trn', label='Company TRN',
+ fieldtype='Read Only', insert_after='company_address',
+ fetch_from='company.tax_id', print_hide=1),
+ dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
+ fieldtype='Read Only', insert_after='customer_name',
+ fetch_from='customer.customer_name_in_arabic', print_hide=1),
+ dict(fieldname='ksa_einv_qr', label='KSA E-Invoicing QR',
+ fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1)
+ ]
+
custom_fields = {
- 'Sales Invoice': [
- dict(
- fieldname='ksa_einv_qr',
- label='KSA E-Invoicing QR',
- fieldtype='Attach Image',
- read_only=1, no_copy=1, hidden=1
- )
+ 'Item': [is_zero_rated, is_exempt],
+ 'Customer': [
+ dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
+ fieldtype='Data', insert_after='customer_name'),
],
- 'POS Invoice': [
- dict(
- fieldname='ksa_einv_qr',
- label='KSA E-Invoicing QR',
- fieldtype='Attach Image',
- read_only=1, no_copy=1, hidden=1
- )
+ 'Supplier': [
+ dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
+ fieldtype='Data', insert_after='supplier_name'),
],
+ 'Purchase Invoice': purchase_invoice_fields,
+ 'Purchase Order': purchase_invoice_fields,
+ 'Purchase Receipt': purchase_invoice_fields,
+ 'Sales Invoice': sales_invoice_fields,
+ 'POS Invoice': sales_invoice_fields,
+ 'Sales Order': sales_invoice_fields,
+ 'Delivery Note': sales_invoice_fields,
+ 'Sales Invoice Item': [is_zero_rated, is_exempt],
+ 'POS Invoice Item': [is_zero_rated, is_exempt],
+ 'Purchase Invoice Item': [is_zero_rated, is_exempt],
+ 'Sales Order Item': [is_zero_rated, is_exempt],
+ 'Delivery Note Item': [is_zero_rated, is_exempt],
+ 'Quotation Item': [is_zero_rated, is_exempt],
+ 'Purchase Order Item': [is_zero_rated, is_exempt],
+ 'Purchase Receipt Item': [is_zero_rated, is_exempt],
+ 'Supplier Quotation Item': [is_zero_rated, is_exempt],
'Address': [
- dict(
- fieldname='address_in_arabic',
- label='Address in Arabic',
- fieldtype='Data',
- insert_after='address_line2'
- )
+ dict(fieldname='address_in_arabic', label='Address in Arabic',
+ fieldtype='Data',insert_after='address_line2')
],
'Company': [
- dict(
- fieldname='company_name_in_arabic',
- label='Company Name In Arabic',
- fieldtype='Data',
- insert_after='company_name'
- )
+ dict(fieldname='company_name_in_arabic', label='Company Name In Arabic',
+ fieldtype='Data', insert_after='company_name')
]
}
diff --git a/erpnext/restaurant/doctype/restaurant/__init__.py b/erpnext/restaurant/doctype/restaurant/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.js b/erpnext/restaurant/doctype/restaurant/restaurant.js
deleted file mode 100644
index 13fda73..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant', {
- refresh: function(frm) {
- frm.add_custom_button(__('Order Entry'), () => {
- frappe.set_route('Form', 'Restaurant Order Entry');
- });
- }
-});
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.json b/erpnext/restaurant/doctype/restaurant/restaurant.json
deleted file mode 100644
index 8572687..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant.json
+++ /dev/null
@@ -1,309 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "prompt",
- "beta": 1,
- "creation": "2017-09-15 12:40:41.546933",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Image",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_customer",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Customer",
- "length": 0,
- "no_copy": 0,
- "options": "Customer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "invoice_series_prefix",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Invoice Series Prefix",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "active_menu",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Active Menu",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant Menu",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_tax_template",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Tax Template",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Taxes and Charges Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address",
- "length": 0,
- "no_copy": 0,
- "options": "Address",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_field": "image",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:13:10.185496",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.py b/erpnext/restaurant/doctype/restaurant/restaurant.py
deleted file mode 100644
index 67838d2..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class Restaurant(Document):
- pass
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py
deleted file mode 100644
index bfdd052..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from frappe import _
-
-
-def get_data():
- return {
- 'fieldname': 'restaurant',
- 'transactions': [
- {
- 'label': _('Setup'),
- 'items': ['Restaurant Menu', 'Restaurant Table']
- },
- {
- 'label': _('Operations'),
- 'items': ['Restaurant Reservation', 'Sales Invoice']
- }
- ]
- }
diff --git a/erpnext/restaurant/doctype/restaurant/test_restaurant.py b/erpnext/restaurant/doctype/restaurant/test_restaurant.py
deleted file mode 100644
index f88f980..0000000
--- a/erpnext/restaurant/doctype/restaurant/test_restaurant.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_records = [
- dict(doctype='Restaurant', name='Test Restaurant 1', company='_Test Company 1',
- invoice_series_prefix='Test-Rest-1-Inv-', default_customer='_Test Customer 1'),
- dict(doctype='Restaurant', name='Test Restaurant 2', company='_Test Company 1',
- invoice_series_prefix='Test-Rest-2-Inv-', default_customer='_Test Customer 1'),
-]
-
-class TestRestaurant(unittest.TestCase):
- pass
diff --git a/erpnext/restaurant/doctype/restaurant_menu/__init__.py b/erpnext/restaurant/doctype/restaurant_menu/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js
deleted file mode 100644
index da7d43f..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Menu', {
- setup: function(frm) {
- frm.add_fetch('item', 'standard_rate', 'rate');
- },
-});
diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
deleted file mode 100644
index 1b1610d..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
+++ /dev/null
@@ -1,247 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "prompt",
- "beta": 1,
- "creation": "2017-09-15 12:48:29.818715",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "restaurant",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Restaurant",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Enabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "price_list",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Price List (Auto created)",
- "length": 0,
- "no_copy": 0,
- "options": "Price List",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant Menu Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:13:13.684500",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant Menu",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Restaurant Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py
deleted file mode 100644
index 64eb40f..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.model.document import Document
-
-
-class RestaurantMenu(Document):
- def validate(self):
- for d in self.items:
- if not d.rate:
- d.rate = frappe.db.get_value('Item', d.item, 'standard_rate')
-
- def on_update(self):
- '''Sync Price List'''
- self.make_price_list()
-
- def on_trash(self):
- '''clear prices'''
- self.clear_item_price()
-
- def clear_item_price(self, price_list=None):
- '''clear all item prices for this menu'''
- if not price_list:
- price_list = self.get_price_list().name
- frappe.db.sql('delete from `tabItem Price` where price_list = %s', price_list)
-
- def make_price_list(self):
- # create price list for menu
- price_list = self.get_price_list()
- self.db_set('price_list', price_list.name)
-
- # delete old items
- self.clear_item_price(price_list.name)
-
- for d in self.items:
- frappe.get_doc(dict(
- doctype = 'Item Price',
- price_list = price_list.name,
- item_code = d.item,
- price_list_rate = d.rate
- )).insert()
-
- def get_price_list(self):
- '''Create price list for menu if missing'''
- price_list_name = frappe.db.get_value('Price List', dict(restaurant_menu=self.name))
- if price_list_name:
- price_list = frappe.get_doc('Price List', price_list_name)
- else:
- price_list = frappe.new_doc('Price List')
- price_list.restaurant_menu = self.name
- price_list.price_list_name = self.name
-
- price_list.enabled = 1
- price_list.selling = 1
- price_list.save()
-
- return price_list
diff --git a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py
deleted file mode 100644
index 27020eb..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-
-test_records = [
- dict(doctype='Item', item_code='Food Item 1',
- item_group='Products', is_stock_item=0),
- dict(doctype='Item', item_code='Food Item 2',
- item_group='Products', is_stock_item=0),
- dict(doctype='Item', item_code='Food Item 3',
- item_group='Products', is_stock_item=0),
- dict(doctype='Item', item_code='Food Item 4',
- item_group='Products', is_stock_item=0),
- dict(doctype='Restaurant Menu', restaurant='Test Restaurant 1', name='Test Restaurant 1 Menu 1',
- items = [
- dict(item='Food Item 1', rate=400),
- dict(item='Food Item 2', rate=300),
- dict(item='Food Item 3', rate=200),
- dict(item='Food Item 4', rate=100),
- ]),
- dict(doctype='Restaurant Menu', restaurant='Test Restaurant 1', name='Test Restaurant 1 Menu 2',
- items = [
- dict(item='Food Item 1', rate=450),
- dict(item='Food Item 2', rate=350),
- ])
-]
-
-class TestRestaurantMenu(unittest.TestCase):
- def test_price_list_creation_and_editing(self):
- menu1 = frappe.get_doc('Restaurant Menu', 'Test Restaurant 1 Menu 1')
- menu1.save()
-
- menu2 = frappe.get_doc('Restaurant Menu', 'Test Restaurant 1 Menu 2')
- menu2.save()
-
- self.assertTrue(frappe.db.get_value('Price List', 'Test Restaurant 1 Menu 1'))
- self.assertEqual(frappe.db.get_value('Item Price',
- dict(price_list = 'Test Restaurant 1 Menu 1', item_code='Food Item 1'), 'price_list_rate'), 400)
- self.assertEqual(frappe.db.get_value('Item Price',
- dict(price_list = 'Test Restaurant 1 Menu 2', item_code='Food Item 1'), 'price_list_rate'), 450)
-
- menu1.items[0].rate = 401
- menu1.save()
-
- self.assertEqual(frappe.db.get_value('Item Price',
- dict(price_list = 'Test Restaurant 1 Menu 1', item_code='Food Item 1'), 'price_list_rate'), 401)
-
- menu1.items[0].rate = 400
- menu1.save()
diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py b/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json
deleted file mode 100644
index 87568bf..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2017-09-15 12:49:36.072636",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-09-15 14:18:55.145088",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant Menu Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py
deleted file mode 100644
index 98b245e..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class RestaurantMenuItem(Document):
- pass
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py b/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
deleted file mode 100644
index 8df851c..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Order Entry', {
- setup: function(frm) {
- let get_item_query = () => {
- return {
- query: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.item_query_restaurant',
- filters: {
- 'table': frm.doc.restaurant_table
- }
- };
- };
- frm.set_query('item', 'items', get_item_query);
- frm.set_query('add_item', get_item_query);
- },
- onload_post_render: function(frm) {
- if(!frm.item_selector) {
- frm.item_selector = new erpnext.ItemSelector({
- frm: frm,
- item_field: 'item',
- item_query: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.item_query_restaurant',
- get_filters: () => {
- return {table: frm.doc.restaurant_table};
- }
- });
- }
-
- let $input = frm.get_field('add_item').$input;
-
- $input.on('keyup', function(e) {
- if (e.which===13) {
- if (frm.clear_item_timeout) {
- clearTimeout (frm.clear_item_timeout);
- }
-
- // clear the item input so user can enter a new item
- frm.clear_item_timeout = setTimeout (() => {
- frm.set_value('add_item', '');
- }, 1000);
-
- let item = $input.val();
-
- if (!item) return;
-
- var added = false;
- (frm.doc.items || []).forEach((d) => {
- if (d.item===item) {
- d.qty += 1;
- added = true;
- }
- });
-
- return frappe.run_serially([
- () => {
- if (!added) {
- return frm.add_child('items', {item: item, qty: 1});
- }
- },
- () => frm.get_field("items").refresh()
- ]);
- }
- });
- },
- refresh: function(frm) {
- frm.disable_save();
- frm.add_custom_button(__('Update'), () => {
- return frm.trigger('sync');
- });
- frm.add_custom_button(__('Clear'), () => {
- return frm.trigger('clear');
- });
- frm.add_custom_button(__('Bill'), () => {
- return frm.trigger('make_invoice');
- });
- },
- clear: function(frm) {
- frm.doc.add_item = '';
- frm.doc.grand_total = 0;
- frm.doc.items = [];
- frm.refresh();
- frm.get_field('add_item').$input.focus();
- },
- restaurant_table: function(frm) {
- // select the open sales order items for this table
- if (!frm.doc.restaurant_table) {
- return;
- }
- return frappe.call({
- method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.get_invoice',
- args: {
- table: frm.doc.restaurant_table
- },
- callback: (r) => {
- frm.events.set_invoice_items(frm, r);
- }
- });
- },
- sync: function(frm) {
- return frappe.call({
- method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.sync',
- args: {
- table: frm.doc.restaurant_table,
- items: frm.doc.items
- },
- callback: (r) => {
- frm.events.set_invoice_items(frm, r);
- frappe.show_alert({message: __('Saved'), indicator: 'green'});
- }
- });
-
- },
- make_invoice: function(frm) {
- frm.trigger('sync').then(() => {
- frappe.prompt([
- {
- fieldname: 'customer',
- label: __('Customer'),
- fieldtype: 'Link',
- reqd: 1,
- options: 'Customer',
- 'default': frm.invoice.customer
- },
- {
- fieldname: 'mode_of_payment',
- label: __('Mode of Payment'),
- fieldtype: 'Link',
- reqd: 1,
- options: 'Mode of Payment',
- 'default': frm.mode_of_payment || ''
- }
- ], (data) => {
- // cache this for next entry
- frm.mode_of_payment = data.mode_of_payment;
- return frappe.call({
- method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.make_invoice',
- args: {
- table: frm.doc.restaurant_table,
- customer: data.customer,
- mode_of_payment: data.mode_of_payment
- },
- callback: (r) => {
- frm.set_value('last_sales_invoice', r.message);
- frm.trigger('clear');
- }
- });
- },
- __("Select Customer"));
- });
- },
- set_invoice_items: function(frm, r) {
- let invoice = r.message;
- frm.doc.items = [];
- (invoice.items || []).forEach((d) => {
- frm.add_child('items', {item: d.item_code, qty: d.qty, rate: d.rate});
- });
- frm.set_value('grand_total', invoice.grand_total);
- frm.set_value('last_sales_invoice', invoice.name);
- frm.invoice = invoice;
- frm.refresh();
- }
-});
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json
deleted file mode 100644
index 3e4d593..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json
+++ /dev/null
@@ -1,280 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2017-09-15 15:10:24.530365",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "restaurant_table",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Restaurant Table",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant Table",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "restaurant_table",
- "description": "Click Enter To Add",
- "fieldname": "add_item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Add Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "grand_total",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Grand Total",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "last_sales_invoice",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Last Sales Invoice",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Invoice",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "restaurant_table",
- "fieldname": "current_order",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Current Order",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "restaurant_table",
- "fieldname": "items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Items",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant Order Entry Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-10-04 17:06:20.926999",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant Order Entry",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Restaurant Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
deleted file mode 100644
index f9e75b4..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import json
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-from erpnext.controllers.queries import item_query
-
-
-class RestaurantOrderEntry(Document):
- pass
-
-@frappe.whitelist()
-def get_invoice(table):
- '''returns the active invoice linked to the given table'''
- invoice_name = frappe.get_value('Sales Invoice', dict(restaurant_table = table, docstatus=0))
- restaurant, menu_name = get_restaurant_and_menu_name(table)
- if invoice_name:
- invoice = frappe.get_doc('Sales Invoice', invoice_name)
- else:
- invoice = frappe.new_doc('Sales Invoice')
- invoice.naming_series = frappe.db.get_value('Restaurant', restaurant, 'invoice_series_prefix')
- invoice.is_pos = 1
- default_customer = frappe.db.get_value('Restaurant', restaurant, 'default_customer')
- if not default_customer:
- frappe.throw(_('Please set default customer in Restaurant Settings'))
- invoice.customer = default_customer
-
- invoice.taxes_and_charges = frappe.db.get_value('Restaurant', restaurant, 'default_tax_template')
- invoice.selling_price_list = frappe.db.get_value('Price List', dict(restaurant_menu=menu_name, enabled=1))
-
- return invoice
-
-@frappe.whitelist()
-def sync(table, items):
- '''Sync the sales order related to the table'''
- invoice = get_invoice(table)
- items = json.loads(items)
-
- invoice.items = []
- invoice.restaurant_table = table
- for d in items:
- invoice.append('items', dict(
- item_code = d.get('item'),
- qty = d.get('qty')
- ))
-
- invoice.save()
- return invoice.as_dict()
-
-@frappe.whitelist()
-def make_invoice(table, customer, mode_of_payment):
- '''Make table based on Sales Order'''
- restaurant, menu = get_restaurant_and_menu_name(table)
- invoice = get_invoice(table)
- invoice.customer = customer
- invoice.restaurant = restaurant
- invoice.calculate_taxes_and_totals()
- invoice.append('payments', dict(mode_of_payment=mode_of_payment, amount=invoice.grand_total))
- invoice.save()
- invoice.submit()
-
- frappe.msgprint(_('Invoice Created'), indicator='green', alert=True)
-
- return invoice.name
-
-@frappe.whitelist()
-def item_query_restaurant(doctype='Item', txt='', searchfield='name', start=0, page_len=20, filters=None, as_dict=False):
- '''Return items that are selected in active menu of the restaurant'''
- restaurant, menu = get_restaurant_and_menu_name(filters['table'])
- items = frappe.db.get_all('Restaurant Menu Item', ['item'], dict(parent = menu))
- del filters['table']
- filters['name'] = ('in', [d.item for d in items])
-
- return item_query('Item', txt, searchfield, start, page_len, filters, as_dict)
-
-def get_restaurant_and_menu_name(table):
- if not table:
- frappe.throw(_('Please select a table'))
-
- restaurant = frappe.db.get_value('Restaurant Table', table, 'restaurant')
- menu = frappe.db.get_value('Restaurant', restaurant, 'active_menu')
-
- if not menu:
- frappe.throw(_('Please set an active menu for Restaurant {0}').format(restaurant))
-
- return restaurant, menu
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json
deleted file mode 100644
index 0240013..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json
+++ /dev/null
@@ -1,163 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-09-15 15:11:50.313241",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "served",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Served",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-09-21 08:39:27.232175",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant Order Entry Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py
deleted file mode 100644
index 0d9c236..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class RestaurantOrderEntryItem(Document):
- pass
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/__init__.py b/erpnext/restaurant/doctype/restaurant_reservation/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js
deleted file mode 100644
index cebd105..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Reservation', {
- setup: function(frm) {
- frm.add_fetch('customer', 'customer_name', 'customer_name');
- },
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
deleted file mode 100644
index 17df2b9..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
+++ /dev/null
@@ -1,355 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "RES-RES-.YYYY.-.#####",
- "beta": 1,
- "creation": "2017-09-15 13:05:51.063661",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "Open\nWaitlisted\nCancelled\nNo Show\nSuccess",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "restaurant",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Restaurant",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_people",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "No of People",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reservation_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Reservation Time",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reservation_end_time",
- "fieldtype": "Datetime",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Reservation End Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Customer",
- "length": 0,
- "no_copy": 0,
- "options": "Customer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Customer Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_number",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Contact Number",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-21 16:15:38.435656",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant Reservation",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Restaurant Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py
deleted file mode 100644
index 02ffaf6..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from datetime import timedelta
-
-from frappe.model.document import Document
-from frappe.utils import get_datetime
-
-
-class RestaurantReservation(Document):
- def validate(self):
- if not self.reservation_end_time:
- self.reservation_end_time = get_datetime(self.reservation_time) + timedelta(hours=1)
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js
deleted file mode 100644
index fe3dc57..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js
+++ /dev/null
@@ -1,18 +0,0 @@
-frappe.views.calendar["Restaurant Reservation"] = {
- field_map: {
- "start": "reservation_time",
- "end": "reservation_end_time",
- "id": "name",
- "title": "customer_name",
- "allDay": "allDay",
- },
- gantt: true,
- filters: [
- {
- "fieldtype": "Data",
- "fieldname": "customer_name",
- "label": __("Customer Name")
- }
- ],
- get_events_method: "frappe.desk.calendar.get_events"
-};
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py
deleted file mode 100644
index 11a3541..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestRestaurantReservation(unittest.TestCase):
- pass
diff --git a/erpnext/restaurant/doctype/restaurant_table/__init__.py b/erpnext/restaurant/doctype/restaurant_table/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js
deleted file mode 100644
index a55605c..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Table', {
- refresh: function(frm) {
-
- }
-});
diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
deleted file mode 100644
index 5fc6e62..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 1,
- "creation": "2017-09-15 12:45:24.717355",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "restaurant",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Restaurant",
- "length": 0,
- "no_copy": 0,
- "options": "Restaurant",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_seats",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "No of Seats",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fieldname": "minimum_seating",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Minimum Seating",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-09 12:13:24.382345",
- "modified_by": "Administrator",
- "module": "Restaurant",
- "name": "Restaurant Table",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Restaurant Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Hospitality",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py
deleted file mode 100644
index 29f8a1a..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import re
-
-from frappe.model.document import Document
-from frappe.model.naming import make_autoname
-
-
-class RestaurantTable(Document):
- def autoname(self):
- prefix = re.sub('-+', '-', self.restaurant.replace(' ', '-'))
- self.name = make_autoname(prefix + '-.##')
diff --git a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py
deleted file mode 100644
index 00d14d2..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_records = [
- dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
- dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
- dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
- dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
-]
-
-class TestRestaurantTable(unittest.TestCase):
- pass
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 40c8a37..7742f26 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -142,7 +142,7 @@
self.update_lead_status()
if self.flags.is_new_doc:
- self.create_lead_address_contact()
+ self.link_lead_address_and_contact()
self.update_customer_groups()
@@ -176,62 +176,24 @@
if self.lead_name:
frappe.db.set_value("Lead", self.lead_name, "status", "Converted")
- def create_lead_address_contact(self):
+ def link_lead_address_and_contact(self):
if self.lead_name:
- # assign lead address to customer (if already not set)
- address_names = frappe.get_all('Dynamic Link', filters={
- "parenttype":"Address",
- "link_doctype":"Lead",
- "link_name":self.lead_name
- }, fields=["parent as name"])
+ # assign lead address and contact to customer (if already not set)
+ linked_contacts_and_addresses = frappe.get_all(
+ "Dynamic Link",
+ filters=[
+ ["parenttype", "in", ["Contact", "Address"]],
+ ["link_doctype", "=", "Lead"],
+ ["link_name", "=", self.lead_name],
+ ],
+ fields=["parent as name", "parenttype as doctype"],
+ )
- for address_name in address_names:
- address = frappe.get_doc('Address', address_name.get('name'))
- if not address.has_link('Customer', self.name):
- address.append('links', dict(link_doctype='Customer', link_name=self.name))
- address.save(ignore_permissions=self.flags.ignore_permissions)
-
- lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True)
-
- if not lead.lead_name:
- frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name))
-
- contact_names = frappe.get_all('Dynamic Link', filters={
- "parenttype":"Contact",
- "link_doctype":"Lead",
- "link_name":self.lead_name
- }, fields=["parent as name"])
-
- for contact_name in contact_names:
- contact = frappe.get_doc('Contact', contact_name.get('name'))
- if not contact.has_link('Customer', self.name):
- contact.append('links', dict(link_doctype='Customer', link_name=self.name))
- contact.save(ignore_permissions=self.flags.ignore_permissions)
-
- if not contact_names:
- lead.lead_name = lead.lead_name.lstrip().split(" ")
- lead.first_name = lead.lead_name[0]
- lead.last_name = " ".join(lead.lead_name[1:])
-
- # create contact from lead
- contact = frappe.new_doc('Contact')
- contact.first_name = lead.first_name
- contact.last_name = lead.last_name
- contact.gender = lead.gender
- contact.salutation = lead.salutation
- contact.email_id = lead.email_id
- contact.phone = lead.phone
- contact.mobile_no = lead.mobile_no
- contact.is_primary_contact = 1
- contact.append('links', dict(link_doctype='Customer', link_name=self.name))
- if lead.email_id:
- contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1))
- if lead.mobile_no:
- contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1))
- contact.flags.ignore_permissions = self.flags.ignore_permissions
- contact.autoname()
- if not frappe.db.exists("Contact", contact.name):
- contact.insert()
+ for row in linked_contacts_and_addresses:
+ linked_doc = frappe.get_doc(row.doctype, row.name)
+ if not linked_doc.has_link('Customer', self.name):
+ linked_doc.append('links', dict(link_doctype='Customer', link_name=self.name))
+ linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
def validate_name_with_customer_group(self):
if frappe.db.exists("Customer Group", self.name):
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 79e9e17..eb98e6c 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -457,12 +457,8 @@
make_delivery_note_based_on_delivery_date() {
var me = this;
- var delivery_dates = [];
- $.each(this.frm.doc.items || [], function(i, d) {
- if(!delivery_dates.includes(d.delivery_date)) {
- delivery_dates.push(d.delivery_date);
- }
- });
+ var delivery_dates = this.frm.doc.items.map(i => i.delivery_date);
+ delivery_dates = [ ...new Set(delivery_dates) ];
var item_grid = this.frm.fields_dict["items"].grid;
if(!item_grid.get_selected().length && delivery_dates.length > 1) {
@@ -500,14 +496,7 @@
if(!dates) return;
- $.each(dates, function(i, d) {
- $.each(item_grid.grid_rows || [], function(j, row) {
- if(row.doc.delivery_date == d) {
- row.doc.__checked = 1;
- }
- });
- })
- me.make_delivery_note();
+ me.make_delivery_note(dates);
dialog.hide();
});
dialog.show();
@@ -516,10 +505,13 @@
}
}
- make_delivery_note() {
+ make_delivery_note(delivery_dates) {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
- frm: this.frm
+ frm: this.frm,
+ args: {
+ delivery_dates
+ }
})
}
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index cc95185..0f5b1e3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -565,6 +565,13 @@
}
if not skip_item_mapping:
+ def condition(doc):
+ # make_mapped_doc sets js `args` into `frappe.flags.args`
+ if frappe.flags.args and frappe.flags.args.delivery_dates:
+ if cstr(doc.delivery_date) not in frappe.flags.args.delivery_dates:
+ return False
+ return abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+
mapper["Sales Order Item"] = {
"doctype": "Delivery Note Item",
"field_map": {
@@ -573,7 +580,7 @@
"parent": "against_sales_order",
},
"postprocess": update_item,
- "condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+ "condition": condition
}
target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py
index fb86e61..e1ef635 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.py
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.py
@@ -16,7 +16,7 @@
self.toggle_editable_rate_for_bundle_items()
def validate(self):
- for key in ["cust_master_name", "campaign_naming_by", "customer_group", "territory",
+ for key in ["cust_master_name", "customer_group", "territory",
"maintain_same_sales_rate", "editable_price_list_rate", "selling_price_list"]:
frappe.db.set_default(key, self.get(key, ""))
diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
index 777b02c..dd49f13 100644
--- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
+++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
@@ -23,19 +23,24 @@
row = []
outstanding_amt = get_customer_outstanding(d.name, filters.get("company"),
- ignore_outstanding_sales_order=d.bypass_credit_limit_check_at_sales_order)
+ ignore_outstanding_sales_order=d.bypass_credit_limit_check)
credit_limit = get_credit_limit(d.name, filters.get("company"))
bal = flt(credit_limit) - flt(outstanding_amt)
if customer_naming_type == "Naming Series":
- row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal,
- d.bypass_credit_limit_check, d.is_frozen,
- d.disabled]
+ row = [
+ d.name, d.customer_name, credit_limit,
+ outstanding_amt, bal, d.bypass_credit_limit_check,
+ d.is_frozen, d.disabled
+ ]
else:
- row = [d.name, credit_limit, outstanding_amt, bal,
- d.bypass_credit_limit_check_at_sales_order, d.is_frozen, d.disabled]
+ row = [
+ d.name, credit_limit, outstanding_amt, bal,
+ d.bypass_credit_limit_check, d.is_frozen,
+ d.disabled
+ ]
if credit_limit:
data.append(row)
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index 0c0acc7..3e22d0f 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -68,7 +68,8 @@
(soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount,
(soi.base_amount - (soi.billed_amt * IFNULL(so.conversion_rate, 1))) as pending_amount,
soi.warehouse as warehouse,
- so.company, soi.name
+ so.company, soi.name,
+ soi.description as description
FROM
`tabSales Order` so,
(`tabSales Order Item` soi
@@ -84,7 +85,7 @@
and so.docstatus = 1
{conditions}
GROUP BY soi.name
- ORDER BY so.transaction_date ASC
+ ORDER BY so.transaction_date ASC, soi.item_code ASC
""".format(conditions=conditions), filters, as_dict=1)
return data
@@ -184,6 +185,12 @@
"options": "Item",
"width": 100
})
+ columns.append({
+ "label":_("Description"),
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "width": 100
+ })
columns.extend([
{
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 540aca2..16e3847 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -486,7 +486,7 @@
"options": "Competitor Detail"
},
{
- "fieldtype": "Text",
+ "fieldtype": "Small Text",
"label": __("Detailed Reason"),
"fieldname": "detailed_reason"
},
@@ -499,7 +499,7 @@
method: 'declare_enquiry_lost',
args: {
'lost_reasons_list': values.lost_reason,
- 'competitors': values.competitors,
+ 'competitors': values.competitors ? values.competitors : [],
'detailed_reason': values.detailed_reason
},
callback: function(r) {
diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json
index a851ace..5bce3ca 100644
--- a/erpnext/selling/workspace/retail/retail.json
+++ b/erpnext/selling/workspace/retail/retail.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Point Of Sale\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings & Configurations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loyalty Program\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening & Closing\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Point Of Sale\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings & Configurations\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loyalty Program\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening & Closing\",\"col\":4}}]",
"creation": "2020-03-02 17:18:32.505616",
"docstatus": 0,
"doctype": "Workspace",
@@ -14,7 +14,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Settings & Configurations",
- "link_count": 0,
+ "link_count": 2,
"onboard": 0,
"type": "Card Break"
},
@@ -44,7 +44,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Loyalty Program",
- "link_count": 0,
+ "link_count": 2,
"onboard": 0,
"type": "Card Break"
},
@@ -74,7 +74,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Opening & Closing",
- "link_count": 0,
+ "link_count": 2,
"onboard": 0,
"type": "Card Break"
},
@@ -101,7 +101,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:01.840989",
+ "modified": "2022-01-13 18:07:56.711095",
"modified_by": "Administrator",
"module": "Selling",
"name": "Retail",
@@ -110,7 +110,7 @@
"public": 1,
"restrict_to_domain": "Retail",
"roles": [],
- "sequence_id": 22,
+ "sequence_id": 22.0,
"shortcuts": [
{
"doc_view": "",
diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json
index db2e6ba..a700ad8 100644
--- a/erpnext/selling/workspace/selling/selling.json
+++ b/erpnext/selling/workspace/selling/selling.json
@@ -5,7 +5,7 @@
"label": "Sales Order Trends"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Selling\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Sales Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Selling\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Selling\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Sales Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Selling\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-01-28 11:49:12.092882",
"docstatus": 0,
"doctype": "Workspace",
@@ -562,7 +562,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:01.990703",
+ "modified": "2022-01-13 17:43:02.778627",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling",
@@ -571,7 +571,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 23,
+ "sequence_id": 23.0,
"shortcuts": [
{
"color": "Grey",
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 45e8dcc..dd185fc 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -213,6 +213,9 @@
["default_payroll_payable_account", {"root_type": "Liability"}],
["round_off_account", {"root_type": "Expense"}],
["write_off_account", {"root_type": "Expense"}],
+ ["default_deferred_expense_account", {}],
+ ["default_deferred_revenue_account", {}],
+ ["default_expense_claim_payable_account", {}],
["default_discount_account", {}],
["discount_allowed_account", {"root_type": "Expense"}],
["discount_received_account", {"root_type": "Income"}],
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index b41a01b..12e73b6 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -2,16 +2,18 @@
# License: GNU General Public License v3. See license.txt
import copy
+from urllib.parse import quote
import frappe
from frappe import _
-from frappe.utils import cint, cstr, nowdate
+from frappe.utils import cint
from frappe.utils.nestedset import NestedSet
from frappe.website.utils import clear_cache
from frappe.website.website_generator import WebsiteGenerator
-from six.moves.urllib.parse import quote
+
from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
+
class ItemGroup(NestedSet, WebsiteGenerator):
nsm_parent_field = 'parent_item_group'
website = frappe._dict(
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index bafaab8..1d7bad2 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -189,7 +189,7 @@
user_type_limit = {}
for user_type, data in user_types.items():
- user_type_limit.setdefault(frappe.scrub(user_type), 10)
+ user_type_limit.setdefault(frappe.scrub(user_type), 20)
update_site_config('user_type_doctype_limit', user_type_limit)
@@ -204,15 +204,33 @@
'apply_user_permission_on': 'Employee',
'user_id_field': 'user_id',
'doctypes': {
- 'Salary Slip': ['read'],
+ # masters
+ 'Holiday List': ['read'],
'Employee': ['read', 'write'],
+ # payroll
+ 'Salary Slip': ['read'],
+ 'Employee Benefit Application': ['read', 'write', 'create', 'delete'],
+ # expenses
'Expense Claim': ['read', 'write', 'create', 'delete'],
+ 'Employee Advance': ['read', 'write', 'create', 'delete'],
+ # leave and attendance
'Leave Application': ['read', 'write', 'create', 'delete'],
'Attendance Request': ['read', 'write', 'create', 'delete'],
'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
+ # tax
'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
- 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
+ # projects
+ 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
+ # trainings
+ 'Training Program': ['read'],
+ 'Training Feedback': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
+ # shifts
+ 'Shift Request': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
+ # misc
+ 'Employee Grievance': ['read', 'write', 'create', 'delete'],
+ 'Employee Referral': ['read', 'write', 'create', 'delete'],
+ 'Travel Request': ['read', 'write', 'create', 'delete']
}
}
}
diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
index e47837f..c5640bc 100644
--- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
+++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Manufacturing Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Education Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Hotel Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"CRM Settings\",\"col\":3}}]",
"creation": "2020-03-12 14:47:51.166455",
"docstatus": 0,
"doctype": "Workspace",
@@ -10,7 +10,7 @@
"idx": 0,
"label": "ERPNext Settings",
"links": [],
- "modified": "2021-11-05 21:32:55.323591",
+ "modified": "2022-01-13 19:18:59.362820",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@@ -19,7 +19,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 12,
+ "sequence_id": 12.0,
"shortcuts": [
{
"icon": "project",
@@ -105,13 +105,6 @@
"type": "DocType"
},
{
- "icon": "non-profit",
- "label": "Healthcare Settings",
- "link_to": "Healthcare Settings",
- "restrict_to_domain": "Healthcare",
- "type": "DocType"
- },
- {
"icon": "setting",
"label": "Domain Settings",
"link_to": "Domain Settings",
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
index f9c585c0..19ff2a0 100644
--- a/erpnext/setup/workspace/home/home.json
+++ b/erpnext/setup/workspace/home/home.json
@@ -1,18 +1,13 @@
{
"charts": [],
- "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
"creation": "2020-01-23 13:46:38.833076",
- "developer_mode_only": 0,
- "disable_user_customization": 0,
"docstatus": 0,
"doctype": "Workspace",
- "extends_another_page": 0,
"for_user": "",
"hide_custom": 0,
"icon": "getting-started",
"idx": 0,
- "is_default": 0,
- "is_standard": 0,
"label": "Home",
"links": [
{
@@ -276,18 +271,16 @@
"type": "Link"
}
],
- "modified": "2021-11-22 12:50:15.771366",
+ "modified": "2022-01-13 17:24:17.002665",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
"owner": "Administrator",
"parent_page": "",
- "pin_to_bottom": 0,
- "pin_to_top": 0,
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 1,
+ "sequence_id": 1.0,
"shortcuts": [
{
"label": "Item",
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 5593101..96751d6 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -292,6 +292,7 @@
join `tabStock Ledger Entry` ignore index (item_code, warehouse)
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
+ and `tabStock Ledger Entry`.is_cancelled = 0
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
@@ -336,4 +337,4 @@
).run()
flt_reserved_batch_qty = flt(reserved_batch_qty[0][0])
- return flt_reserved_batch_qty
\ No newline at end of file
+ return flt_reserved_batch_qty
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 8e79f0e..56dc71c 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -33,6 +33,7 @@
"oldfieldtype": "Link",
"options": "Warehouse",
"read_only": 1,
+ "reqd": 1,
"search_index": 1
},
{
@@ -46,6 +47,7 @@
"oldfieldtype": "Link",
"options": "Item",
"read_only": 1,
+ "reqd": 1,
"search_index": 1
},
{
@@ -169,10 +171,11 @@
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2021-03-30 23:09:39.572776",
+ "modified": "2022-01-30 17:04:54.715288",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -200,5 +203,6 @@
"quick_entry": 1,
"search_fields": "item_code,warehouse",
"sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 0ef7ce2..c34e9d0 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -123,7 +123,7 @@
self.db_set('projected_qty', self.projected_qty)
def on_doctype_update():
- frappe.db.add_index("Bin", ["item_code", "warehouse"])
+ frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse")
def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False):
diff --git a/erpnext/stock/doctype/bin/test_bin.py b/erpnext/stock/doctype/bin/test_bin.py
index 9c390d9..250126c 100644
--- a/erpnext/stock/doctype/bin/test_bin.py
+++ b/erpnext/stock/doctype/bin/test_bin.py
@@ -1,9 +1,36 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
+import frappe
-# test_records = frappe.get_test_records('Bin')
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.utils import _create_bin
+from erpnext.tests.utils import ERPNextTestCase
-class TestBin(unittest.TestCase):
- pass
+
+class TestBin(ERPNextTestCase):
+
+
+ def test_concurrent_inserts(self):
+ """ Ensure no duplicates are possible in case of concurrent inserts"""
+ item_code = "_TestConcurrentBin"
+ make_item(item_code)
+ warehouse = "_Test Warehouse - _TC"
+
+ bin1 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
+ bin1.insert()
+
+ bin2 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
+ with self.assertRaises(frappe.UniqueValidationError):
+ bin2.insert()
+
+ # util method should handle it
+ bin = _create_bin(item_code, warehouse)
+ self.assertEqual(bin.item_code, item_code)
+
+ frappe.db.rollback()
+
+ def test_index_exists(self):
+ indexes = frappe.db.sql("show index from tabBin where Non_unique = 0", as_dict=1)
+ if not any(index.get("Key_name") == "unique_item_warehouse" for index in indexes):
+ self.fail(f"Expected unique index on item-warehouse")
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 70d48a4..d1e2244 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -14,6 +14,7 @@
from erpnext.controllers.selling_controller import SellingController
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
+from erpnext.stock.utils import calculate_mapped_packed_items_return
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -128,8 +129,12 @@
self.validate_uom_is_integer("uom", "qty")
self.validate_with_previous_doc()
- from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
- make_packing_list(self)
+ # Keeps mapped packed_items in case product bundle is updated.
+ if self.is_return and self.return_against:
+ calculate_mapped_packed_items_return(self)
+ else:
+ from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+ make_packing_list(self)
if self._action != 'submit' and not self.is_return:
set_batch_nos(self, 'warehouse', throw=True)
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 4f89a19..bd18e78 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -386,8 +386,7 @@
self.assertEqual(actual_qty, 25)
# return bundled item
- dn1 = create_delivery_note(item_code='_Test Product Bundle Item', is_return=1,
- return_against=dn.name, qty=-2, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn1 = create_return_delivery_note(source_name=dn.name, rate=500, qty=-2)
# qty after return
actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1")
@@ -823,6 +822,15 @@
automatically_fetch_payment_terms(enable=0)
+def create_return_delivery_note(**args):
+ args = frappe._dict(args)
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ doc = make_return_doc("Delivery Note", args.source_name, None)
+ doc.items[0].rate = args.rate
+ doc.items[0].qty = args.qty
+ doc.submit()
+ return doc
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index f361c88..2a30ca1 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -380,8 +380,7 @@
// Show Stock Levels only if is_stock_item
if (frm.doc.is_stock_item) {
frappe.require('item-dashboard.bundle.js', function() {
- frm.dashboard.parent.find('.stock-levels').remove();
- const section = frm.dashboard.add_section('', __("Stock Levels"), 'stock-levels');
+ const section = frm.dashboard.add_section('', __("Stock Levels"));
erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
parent: section,
item_code: frm.doc.name,
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index bbbebc4..e71cdb3 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -28,6 +28,7 @@
"standard_rate",
"is_fixed_asset",
"auto_create_assets",
+ "is_grouped_asset",
"asset_category",
"asset_naming_series",
"over_delivery_receipt_allowance",
@@ -899,6 +900,13 @@
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission"
+ },
+ {
+ "default": "0",
+ "depends_on": "auto_create_assets",
+ "fieldname": "is_grouped_asset",
+ "fieldtype": "Check",
+ "label": "Create Grouped Asset"
}
],
"icon": "fa fa-tag",
@@ -906,7 +914,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-12-14 04:13:16.857534",
+ "modified": "2022-01-18 12:57:54.273202",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
@@ -976,6 +984,7 @@
"show_preview_popup": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "item_name",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index dc54c3a..2c78b2c 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -8,6 +8,7 @@
import frappe
from frappe import _
+from frappe.model.document import Document
from frappe.utils import (
cint,
cstr,
@@ -22,7 +23,6 @@
)
from frappe.utils.html_utils import clean_html
from frappe.website.utils import clear_cache
-from frappe.model.document import Document
import erpnext
from erpnext.controllers.item_variant import (
@@ -224,18 +224,20 @@
self.item_code))
def add_default_uom_in_conversion_factor_table(self):
- uom_conv_list = [d.uom for d in self.get("uoms")]
- if self.stock_uom not in uom_conv_list:
- ch = self.append('uoms', {})
- ch.uom = self.stock_uom
- ch.conversion_factor = 1
+ if not self.is_new() and self.has_value_changed("stock_uom"):
+ self.uoms = []
+ frappe.msgprint(
+ _("Successfully changed Stock UOM, please redefine conversion factors for new UOM."),
+ alert=True,
+ )
- to_remove = []
- for d in self.get("uoms"):
- if d.conversion_factor == 1 and d.uom != self.stock_uom:
- to_remove.append(d)
+ uoms_list = [d.uom for d in self.get("uoms")]
- [self.remove(d) for d in to_remove]
+ if self.stock_uom not in uoms_list:
+ self.append("uoms", {
+ "uom": self.stock_uom,
+ "conversion_factor": 1
+ })
def update_website_item(self):
"""Update Website Item if change in Item impacts it."""
@@ -352,14 +354,6 @@
frappe.throw(_("Barcode {0} is not a valid {1} code").format(
item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
- if item_barcode.barcode != item_barcode.name:
- # if barcode is getting updated , the row name has to reset.
- # Delete previous old row doc and re-enter row as if new to reset name in db.
- item_barcode.set("__islocal", True)
- item_barcode_entry_name = item_barcode.name
- item_barcode.name = None
- frappe.delete_doc("Item Barcode", item_barcode_entry_name)
-
def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory'''
warehouse = []
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 4028d93..0957ce0 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -584,6 +584,16 @@
except frappe.ValidationError as e:
self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}")
+ def test_erasure_of_old_conversions(self):
+ item = create_item("_item change uom")
+ item.stock_uom = "Gram"
+ item.append("uoms", frappe._dict(uom="Box", conversion_factor=2))
+ item.save()
+ item.reload()
+ item.stock_uom = "Nos"
+ item.save()
+ self.assertEqual(len(item.uoms), 1)
+
def test_validate_stock_item(self):
self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item")
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index cd7e63b..0ba97d5 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -1,7 +1,7 @@
{
"actions": [],
"autoname": "REPOST-ITEM-VAL-.######",
- "creation": "2020-10-22 22:27:07.742161",
+ "creation": "2022-01-11 15:03:38.273179",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -129,7 +129,7 @@
"reqd": 1
},
{
- "default": "0",
+ "default": "1",
"fieldname": "allow_negative_stock",
"fieldtype": "Check",
"label": "Allow Negative Stock"
@@ -177,7 +177,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-11-24 02:18:10.524560",
+ "modified": "2022-01-18 10:57:33.450907",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
@@ -227,5 +227,6 @@
}
],
"sort_field": "modified",
- "sort_order": "DESC"
-}
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index fb3b355..01c5e3e 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -27,8 +27,7 @@
self.item_code = None
self.warehouse = None
- self.allow_negative_stock = self.allow_negative_stock or \
- cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+ self.allow_negative_stock = 1
def set_company(self):
if self.based_on == "Transaction":
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 2947faf..ee08e38 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -402,10 +402,16 @@
def get_auto_serial_nos(serial_no_series, qty):
serial_nos = []
for i in range(cint(qty)):
- serial_nos.append(make_autoname(serial_no_series, "Serial No"))
+ serial_nos.append(get_new_serial_number(serial_no_series))
return "\n".join(serial_nos)
+def get_new_serial_number(series):
+ sr_no = make_autoname(series, "Serial No")
+ if frappe.db.exists("Serial No", sr_no):
+ sr_no = get_new_serial_number(series)
+ return sr_no
+
def auto_make_serial_nos(args):
serial_nos = get_serial_nos(args.get('serial_no'))
created_numbers = []
@@ -459,6 +465,13 @@
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
if s.strip()]
+def clean_serial_no_string(serial_no: str) -> str:
+ if not serial_no:
+ return ""
+
+ serial_no_list = get_serial_nos(serial_no)
+ return "\n".join(serial_no_list)
+
def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
if args.get(field):
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index 99000d1..f8cea71 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -8,8 +8,10 @@
import frappe
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -21,6 +23,10 @@
class TestSerialNo(ERPNextTestCase):
+
+ def tearDown(self):
+ frappe.db.rollback()
+
def test_cannot_create_direct(self):
frappe.delete_doc_if_exists("Serial No", "_TCSER0001")
@@ -176,6 +182,24 @@
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
self.assertEqual(sn_doc.purchase_document_no, se.name)
+ def test_auto_creation_of_serial_no(self):
+ """
+ Test if auto created Serial No excludes existing serial numbers
+ """
+ item_code = make_item("_Test Auto Serial Item ", {
+ "has_serial_no": 1,
+ "serial_no_series": "XYZ.###"
+ }).item_code
+
+ # Reserve XYZ005
+ pr_1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no="XYZ005")
+ # XYZ005 is already used and will throw an error if used again
+ pr_2 = make_purchase_receipt(item_code=item_code, qty=10)
+
+ self.assertEqual(get_serial_nos(pr_1.get("items")[0].serial_no)[0], "XYZ005")
+ for serial_no in get_serial_nos(pr_2.get("items")[0].serial_no):
+ self.assertNotEqual(serial_no, "XYZ005")
+
def test_serial_no_sanitation(self):
"Test if Serial No input is sanitised before entering the DB."
item_code = "_Test Serialized Item"
@@ -192,7 +216,28 @@
self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021")
- frappe.db.rollback()
+ def test_correct_serial_no_incoming_rate(self):
+ """ Check correct consumption rate based on serial no record.
+ """
+ item_code = "_Test Serialized Item"
+ warehouse = "_Test Warehouse - _TC"
+ serial_nos = ["LOWVALUATION", "HIGHVALUATION"]
- def tearDown(self):
- frappe.db.rollback()
+ in1 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=42,
+ serial_no=serial_nos[0])
+ in2 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=113,
+ serial_no=serial_nos[1])
+
+ out = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True)
+
+ # change serial no
+ out.items[0].serial_no = serial_nos[1]
+ out.save()
+ out.submit()
+
+ value_diff = frappe.db.get_value("Stock Ledger Entry",
+ {"voucher_no": out.name, "voucher_type": "Delivery Note"},
+ "stock_value_difference"
+ )
+ self.assertEqual(value_diff, -113)
+
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 93e303c..c51c9bc 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -8,6 +8,7 @@
import frappe
from frappe import _
from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder.functions import Sum
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
import erpnext
@@ -85,8 +86,11 @@
self.validate_warehouse()
self.validate_work_order()
self.validate_bom()
- self.mark_finished_and_scrap_items()
- self.validate_finished_goods()
+
+ if self.purpose in ("Manufacture", "Repack"):
+ self.mark_finished_and_scrap_items()
+ self.validate_finished_goods()
+
self.validate_with_material_request()
self.validate_batch()
self.validate_inspection()
@@ -109,8 +113,12 @@
self.set_actual_qty()
self.calculate_rate_and_amount()
self.validate_putaway_capacity()
- self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
- self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
+
+ if not self.get("purpose") == "Manufacture":
+ # ignore scrap item wh difference and empty source/target wh
+ # in Manufacture Entry
+ self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
+ self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
def on_submit(self):
self.update_stock_ledger()
@@ -701,26 +709,25 @@
validate_bom_no(item_code, d.bom_no)
def mark_finished_and_scrap_items(self):
- if self.purpose in ("Repack", "Manufacture"):
- if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
- return
+ if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
+ return
- finished_item = self.get_finished_item()
+ finished_item = self.get_finished_item()
- if not finished_item and self.purpose == "Manufacture":
- # In case of independent Manufacture entry, don't auto set
- # user must decide and set
- return
+ if not finished_item and self.purpose == "Manufacture":
+ # In case of independent Manufacture entry, don't auto set
+ # user must decide and set
+ return
- for d in self.items:
- if d.t_warehouse and not d.s_warehouse:
- if self.purpose=="Repack" or d.item_code == finished_item:
- d.is_finished_item = 1
- else:
- d.is_scrap_item = 1
+ for d in self.items:
+ if d.t_warehouse and not d.s_warehouse:
+ if self.purpose=="Repack" or d.item_code == finished_item:
+ d.is_finished_item = 1
else:
- d.is_finished_item = 0
- d.is_scrap_item = 0
+ d.is_scrap_item = 1
+ else:
+ d.is_finished_item = 0
+ d.is_scrap_item = 0
def get_finished_item(self):
finished_item = None
@@ -733,9 +740,9 @@
def validate_finished_goods(self):
"""
- 1. Check if FG exists
- 2. Check if Multiple FG Items are present
- 3. Check FG Item and Qty against WO if present
+ 1. Check if FG exists (mfg, repack)
+ 2. Check if Multiple FG Items are present (mfg)
+ 3. Check FG Item and Qty against WO if present (mfg)
"""
production_item, wo_qty, finished_items = None, 0, []
@@ -748,8 +755,9 @@
for d in self.get('items'):
if d.is_finished_item:
if not self.work_order:
+ # Independent MFG Entry/ Repack Entry, no WO to match against
finished_items.append(d.item_code)
- continue # Independent Manufacture Entry, no WO to match against
+ continue
if d.item_code != production_item:
frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
@@ -762,19 +770,17 @@
finished_items.append(d.item_code)
- if len(set(finished_items)) > 1:
+ if not finished_items:
frappe.throw(
- msg=_("Multiple items cannot be marked as finished item"),
- title=_("Note"),
- exc=FinishedGoodError
+ msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
+ title=_("Missing Finished Good"), exc=FinishedGoodError
)
if self.purpose == "Manufacture":
- if not finished_items:
+ if len(set(finished_items)) > 1:
frappe.throw(
- msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
- title=_("Missing Finished Good"),
- exc=FinishedGoodError
+ msg=_("Multiple items cannot be marked as finished item"),
+ title=_("Note"), exc=FinishedGoodError
)
allowance_percentage = flt(
@@ -1275,22 +1281,29 @@
if not self.pro_doc:
self.set_work_order_details()
- scrap_items = frappe.db.sql('''
- SELECT
- JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description
- FROM
- `tabJob Card` JC, `tabJob Card Scrap Item` JCSI
- WHERE
- JCSI.parent = JC.name AND JC.docstatus = 1
- AND JCSI.item_code IS NOT NULL AND JC.work_order = %s
- GROUP BY
- JCSI.item_code
- ''', self.work_order, as_dict=1)
-
- pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty)
- if pending_qty <=0:
+ if not self.pro_doc.operations:
return []
+ job_card = frappe.qb.DocType('Job Card')
+ job_card_scrap_item = frappe.qb.DocType('Job Card Scrap Item')
+
+ scrap_items = (
+ frappe.qb.from_(job_card)
+ .select(
+ Sum(job_card_scrap_item.stock_qty).as_('stock_qty'),
+ job_card_scrap_item.item_code, job_card_scrap_item.item_name,
+ job_card_scrap_item.description, job_card_scrap_item.stock_uom)
+ .join(job_card_scrap_item)
+ .on(job_card_scrap_item.parent == job_card.name)
+ .where(
+ (job_card_scrap_item.item_code.isnotnull())
+ & (job_card.work_order == self.work_order)
+ & (job_card.docstatus == 1))
+ .groupby(job_card_scrap_item.item_code)
+ ).run(as_dict=1)
+
+ pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty)
+
used_scrap_items = self.get_used_scrap_items()
for row in scrap_items:
row.stock_qty -= flt(used_scrap_items.get(row.item_code))
@@ -1304,6 +1317,9 @@
return scrap_items
+ def get_completed_job_card_qty(self):
+ return flt(min([d.completed_qty for d in self.pro_doc.operations]))
+
def get_used_scrap_items(self):
used_scrap_items = defaultdict(float)
data = frappe.get_all(
@@ -1429,14 +1445,15 @@
qty = req_qty_each * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code):
+ precision = frappe.get_precision("Stock Entry Detail", "qty")
for d in backflushed_materials.get(item.item_code):
- if d.get(item.warehouse):
+ if d.get(item.warehouse) > 0:
if (qty > req_qty):
- qty = (qty/trans_qty) * flt(self.fg_completed_qty)
+ qty = ((flt(qty, precision) - flt(d.get(item.warehouse), precision))
+ / (flt(trans_qty, precision) - flt(produced_qty, precision))
+ ) * flt(self.fg_completed_qty)
- if consumed_qty and frappe.db.get_single_value("Manufacturing Settings",
- "material_consumption"):
- qty -= consumed_qty
+ d[item.warehouse] -= qty
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
qty = frappe.utils.ceil(qty)
@@ -1656,6 +1673,8 @@
for d in self.get("items"):
item_code = d.get('original_item') or d.get('item_code')
reserve_warehouse = item_wh.get(item_code)
+ if not (reserve_warehouse and item_code):
+ continue
stock_bin = get_bin(item_code, reserve_warehouse)
stock_bin.update_reserved_qty_for_sub_contracting()
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b874874..306f2c3 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -226,9 +226,47 @@
mtn.cancel()
- def test_repack_no_change_in_valuation(self):
- company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
+ def test_repack_multiple_fg(self):
+ "Test `is_finished_item` for one item repacked into two items."
+ make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100)
+ repack = frappe.copy_doc(test_records[3])
+ repack.posting_date = nowdate()
+ repack.posting_time = nowtime()
+
+ repack.items[0].qty = 100.0
+ repack.items[0].transfer_qty = 100.0
+ repack.items[1].qty = 50.0
+
+ repack.append("items", {
+ "conversion_factor": 1.0,
+ "cost_center": "_Test Cost Center - _TC",
+ "doctype": "Stock Entry Detail",
+ "expense_account": "Stock Adjustment - _TC",
+ "basic_rate": 150,
+ "item_code": "_Test Item 2",
+ "parentfield": "items",
+ "qty": 50.0,
+ "stock_uom": "_Test UOM",
+ "t_warehouse": "_Test Warehouse - _TC",
+ "transfer_qty": 50.0,
+ "uom": "_Test UOM"
+ })
+ repack.set_stock_entry_type()
+ repack.insert()
+
+ self.assertEqual(repack.items[1].is_finished_item, 1)
+ self.assertEqual(repack.items[2].is_finished_item, 1)
+
+ repack.items[1].is_finished_item = 0
+ repack.items[2].is_finished_item = 0
+
+ # must raise error if 0 fg in repack entry
+ self.assertRaises(FinishedGoodError, repack.validate_finished_goods)
+
+ repack.delete() # teardown
+
+ def test_repack_no_change_in_valuation(self):
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
qty=50, basic_rate=100)
@@ -813,6 +851,34 @@
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0)
+ def test_zero_incoming_rate(self):
+ """ Make sure incoming rate of 0 is allowed while consuming.
+
+ qty | rate | valuation rate
+ 1 | 100 | 100
+ 1 | 0 | 50
+ -1 | 100 | 0
+ -1 | 0 <--- assert this
+ """
+ item_code = "_TestZeroVal"
+ warehouse = "_Test Warehouse - _TC"
+ create_item('_TestZeroVal')
+ _receipt = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=100)
+ receipt2 = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=0, do_not_save=True)
+ receipt2.items[0].allow_zero_valuation_rate = 1
+ receipt2.save()
+ receipt2.submit()
+
+ issue = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse)
+
+ value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue.name, "voucher_type": "Stock Entry"}, "stock_value_difference")
+ self.assertEqual(value_diff, -100)
+
+ issue2 = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse)
+ value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue2.name, "voucher_type": "Stock Entry"}, "stock_value_difference")
+ self.assertEqual(value_diff, 0)
+
+
def test_gle_for_opening_stock_entry(self):
mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1",
company="_Test Company with perpetual inventory", qty=50, basic_rate=100,
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index cafbd75..a1030d5 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -5,7 +5,10 @@
from frappe.core.page.permission_manager.permission_manager import reset
from frappe.utils import add_days, today
-from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.delivery_note.test_delivery_note import (
+ create_delivery_note,
+ create_return_delivery_note,
+)
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
create_landed_cost_voucher,
@@ -232,8 +235,7 @@
self.assertEqual(outgoing_rate, 100)
# Return Entry: Qty = -2, Rate = 150
- return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=bundled_item, qty=-2, rate=150,
- company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+ return_dn = create_return_delivery_note(source_name=dn.name, rate=150, qty=-2)
# check incoming rate for Return entry
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index c4ddc9e..428370c 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -6,7 +6,7 @@
import frappe
-from frappe.utils import add_days, flt, nowdate, nowtime, random_string
+from frappe.utils import add_days, cstr, flt, nowdate, nowtime, random_string
from erpnext.accounts.utils import get_stock_and_account_balance
from erpnext.stock.doctype.item.test_item import create_item
@@ -439,8 +439,8 @@
self.assertRaises(frappe.ValidationError, sr.submit)
def test_serial_no_cancellation(self):
-
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
item = create_item("Stock-Reco-Serial-Item-9", is_stock_item=1)
if not item.has_serial_no:
item.has_serial_no = 1
@@ -466,6 +466,31 @@
self.assertEqual(len(active_sr_no), 10)
+ def test_serial_no_creation_and_inactivation(self):
+ item = create_item("_TestItemCreatedWithStockReco", is_stock_item=1)
+ if not item.has_serial_no:
+ item.has_serial_no = 1
+ item.save()
+
+ item_code = item.name
+ warehouse = "_Test Warehouse - _TC"
+
+ sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse,
+ serial_no="SR-CREATED-SR-NO", qty=1, do_not_submit=True, rate=100)
+ sr.save()
+ self.assertEqual(cstr(sr.items[0].current_serial_no), "")
+ sr.submit()
+
+ active_sr_no = frappe.get_all("Serial No",
+ filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+ self.assertEqual(len(active_sr_no), 1)
+
+ sr.cancel()
+ active_sr_no = frappe.get_all("Serial No",
+ filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+ self.assertEqual(len(active_sr_no), 0)
+
+
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)
if not batch_item_doc.has_batch_no:
diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
index 44e1386..87097c7 100644
--- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
+++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
@@ -55,7 +55,8 @@
return frappe.db.sql("""select item_code, batch_no, warehouse,
posting_date, actual_qty
from `tabStock Ledger Entry`
- where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
+ where is_cancelled = 0
+ and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
conditions, as_dict=1)
def get_item_warehouse_batch_map(filters, float_precision):
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
index 5f6184d..058af77 100644
--- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -91,7 +91,7 @@
voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
svd_list = frappe.get_list(
'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
- filters=[('voucher_no', 'in', voucher_nos)]
+ filters=[('voucher_no', 'in', voucher_nos), ("is_cancelled", "=", 0)]
)
assign_item_groups_to_svd_list(svd_list)
return svd_list
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 3f49065..cfa1e47 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -76,6 +76,7 @@
on sle.voucher_no = se.name
where
actual_qty < 0
+ and is_cancelled = 0
and voucher_type not in ('Delivery Note', 'Sales Invoice')
%s
group by item_code""" % condition, as_dict=1)
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index fe2417b..ef7c2cc 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -86,10 +86,10 @@
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
- if (column.fieldname == "out_qty" && data.out_qty < 0) {
+ if (column.fieldname == "out_qty" && data && data.out_qty < 0) {
value = "<span style='color:red'>" + value + "</span>";
}
- else if (column.fieldname == "in_qty" && data.in_qty > 0) {
+ else if (column.fieldname == "in_qty" && data && data.in_qty > 0) {
value = "<span style='color:green'>" + value + "</span>";
}
diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py
index 1dcf863..525af40 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -1,6 +1,8 @@
import unittest
from typing import List, Tuple
+import frappe
+
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
DEFAULT_FILTERS = {
@@ -10,8 +12,12 @@
}
+batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc")
+
REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
("Stock Ledger", {"_optional": True}),
+ ("Stock Ledger", {"batch_no": batch}),
+ ("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}),
("Stock Balance", {"_optional": True}),
("Stock Projected Qty", {"_optional": True}),
("Batch-Wise Balance History", {}),
@@ -40,6 +46,13 @@
("Item Variant Details", {"item": "_Test Variant Item",}),
("Total Stock Summary", {"group_by": "warehouse",}),
("Batch Item Expiry Status", {}),
+ ("Incorrect Stock Value Report", {"company": "_Test Company with perpetual inventory"}),
+ ("Incorrect Serial No Valuation", {}),
+ ("Incorrect Balance Qty After Transaction", {}),
+ ("Supplier-Wise Sales Analytics", {}),
+ ("Item Prices", {"items": "Enabled Items only"}),
+ ("Delayed Item Report", {"based_on": "Sales Invoice"}),
+ ("Delayed Item Report", {"based_on": "Delivery Note"}),
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
("Stock Ledger Invariant Check",
{
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 107bb23..0a7ab40 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -105,6 +105,7 @@
def validate_serial_no(sle):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
for sn in get_serial_nos(sle.serial_no):
args = copy.deepcopy(sle)
args.serial_no = sn
@@ -423,6 +424,8 @@
return sorted(entries_to_fix, key=lambda k: k['timestamp'])
def process_sle(self, sle):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
# previous sle data for this warehouse
self.wh_data = self.data[sle.warehouse]
@@ -437,7 +440,7 @@
if not self.args.get("sle_id"):
self.get_dynamic_incoming_outgoing_rate(sle)
- if sle.serial_no:
+ if get_serial_nos(sle.serial_no):
self.get_serialized_values(sle)
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
if sle.voucher_type == "Stock Reconciliation":
@@ -449,8 +452,9 @@
# assert
self.wh_data.valuation_rate = sle.valuation_rate
self.wh_data.qty_after_transaction = sle.qty_after_transaction
- self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]]
self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
+ if self.valuation_method != "Moving Average":
+ self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]]
else:
if self.valuation_method == "Moving Average":
self.get_moving_average_values(sle)
@@ -602,9 +606,9 @@
incoming_rate = self.wh_data.valuation_rate
stock_value_change = 0
- if incoming_rate:
+ if actual_qty > 0:
stock_value_change = actual_qty * incoming_rate
- elif actual_qty < 0:
+ else:
# In case of delivery/stock issue, get average purchase rate
# of serial nos of current entry
if not sle.is_cancelled:
@@ -646,6 +650,7 @@
where
company = %s
and actual_qty > 0
+ and is_cancelled = 0
and (serial_no = %s
or serial_no like %s
or serial_no like %s
@@ -901,6 +906,7 @@
item_code = %s
AND warehouse = %s
AND valuation_rate >= 0
+ AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
@@ -911,6 +917,7 @@
where
item_code = %s
AND valuation_rate > 0
+ AND is_cancelled = 0
AND NOT(voucher_no = %s AND voucher_type = %s)
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type))
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 3c70b41..7c63c17 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -103,7 +103,7 @@
serial_nos = get_serial_nos_data_after_transactions(args)
return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
- if last_entry else (0.0, 0.0, 0.0))
+ if last_entry else (0.0, 0.0, None))
else:
return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
else:
@@ -176,13 +176,7 @@
def get_bin(item_code, warehouse):
bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin:
- bin_obj = frappe.get_doc({
- "doctype": "Bin",
- "item_code": item_code,
- "warehouse": warehouse,
- })
- bin_obj.flags.ignore_permissions = 1
- bin_obj.insert()
+ bin_obj = _create_bin(item_code, warehouse)
else:
bin_obj = frappe.get_doc('Bin', bin, for_update=True)
bin_obj.flags.ignore_permissions = True
@@ -192,16 +186,24 @@
bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse})
if not bin_record:
- bin_obj = frappe.get_doc({
- "doctype": "Bin",
- "item_code": item_code,
- "warehouse": warehouse,
- })
+ bin_obj = _create_bin(item_code, warehouse)
+ bin_record = bin_obj.name
+ return bin_record
+
+def _create_bin(item_code, warehouse):
+ """Create a bin and take care of concurrent inserts."""
+
+ bin_creation_savepoint = "create_bin"
+ try:
+ frappe.db.savepoint(bin_creation_savepoint)
+ bin_obj = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
bin_obj.flags.ignore_permissions = 1
bin_obj.insert()
- bin_record = bin_obj.name
+ except frappe.UniqueValidationError:
+ frappe.db.rollback(save_point=bin_creation_savepoint) # preserve transaction in postgres
+ bin_obj = frappe.get_last_doc("Bin", {"item_code": item_code, "warehouse": warehouse})
- return bin_record
+ return bin_obj
def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
"""WARNING: This function is deprecated. Inline this function instead of using it."""
@@ -419,6 +421,19 @@
if reposting_in_progress:
frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1)
+
+def calculate_mapped_packed_items_return(return_doc):
+ parent_items = set([item.parent_item for item in return_doc.packed_items])
+ against_doc = frappe.get_doc(return_doc.doctype, return_doc.return_against)
+
+ for original_bundle, returned_bundle in zip(against_doc.items, return_doc.items):
+ if original_bundle.item_code in parent_items:
+ for returned_packed_item, original_packed_item in zip(return_doc.packed_items, against_doc.packed_items):
+ if returned_packed_item.parent_item == original_bundle.item_code:
+ returned_packed_item.parent_detail_docname = returned_bundle.name
+ returned_packed_item.qty = (original_packed_item.qty / original_bundle.qty) * returned_bundle.qty
+
+
def check_pending_reposting(posting_date: str, throw_error: bool = True) -> bool:
"""Check if there are pending reposting job till the specified posting date."""
diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json
index 4df27f5..ed33067 100644
--- a/erpnext/stock/workspace/stock/stock.json
+++ b/erpnext/stock/workspace/stock/stock.json
@@ -1,10 +1,11 @@
{
"charts": [
{
- "chart_name": "Warehouse wise Stock Value"
+ "chart_name": "Warehouse wise Stock Value",
+ "label": "Warehouse wise Stock Value"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Stock\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Receipt\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Delivery Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Masters & Reports\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Transactions\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Serial No and Batch\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Incorrect Data Report\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:10.096528",
"docstatus": 0,
"doctype": "Workspace",
@@ -706,7 +707,7 @@
"type": "Link"
}
],
- "modified": "2021-11-23 04:34:00.420870",
+ "modified": "2022-01-13 17:47:38.339931",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
@@ -715,7 +716,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 24,
+ "sequence_id": 24.0,
"shortcuts": [
{
"color": "Green",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index bfbffe2..4dbb0e7 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -111,6 +111,7 @@
filters: [
['DocType', 'issingle', '=', 0],
['DocType', 'istable', '=', 0],
+ ['DocType', 'is_submittable', '=', 0],
['DocType', 'name', 'not in', invalid_doctypes],
['DocType', 'module', 'not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
]
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index ea617fd..526b6aa 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -29,6 +29,7 @@
class ServiceLevelAgreement(Document):
def validate(self):
+ self.validate_selected_doctype()
self.validate_doc()
self.validate_status_field()
self.check_priorities()
@@ -106,6 +107,23 @@
frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format(
frappe.bold(self.entity_type), frappe.bold(self.entity)))
+ def validate_selected_doctype(self):
+ invalid_doctypes = list(frappe.model.core_doctypes_list)
+ invalid_doctypes.extend(['Cost Center', 'Company'])
+ valid_document_types = frappe.get_all('DocType', {
+ 'issingle': 0,
+ 'istable': 0,
+ 'is_submittable': 0,
+ 'name': ['not in', invalid_doctypes],
+ 'module': ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
+ }, pluck="name")
+
+ if self.document_type not in valid_document_types:
+ frappe.throw(
+ msg=_("Please select valid document type."),
+ title=_("Invalid Document Type")
+ )
+
def validate_status_field(self):
meta = frappe.get_meta(self.document_type)
if not meta.get_field("status"):
@@ -247,9 +265,15 @@
]
customer = doc.get('customer')
- or_filters.append(
- ["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)]
- )
+ if customer:
+ or_filters.extend([
+ ["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)],
+ ["Service Level Agreement", "entity_type", "is", "not set"]
+ ])
+ else:
+ or_filters.append(
+ ["Service Level Agreement", "entity_type", "is", "not set"]
+ )
default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]]
default_sla = frappe.get_all("Service Level Agreement", filters=default_sla_filter,
@@ -361,11 +385,18 @@
sla = get_active_service_level_agreement_for(doc)
if not sla:
+ remove_sla_if_applied(doc)
return
process_sla(doc, sla)
+def remove_sla_if_applied(doc):
+ doc.service_level_agreement = None
+ doc.response_by = None
+ doc.resolution_by = None
+
+
def process_sla(doc, sla):
if not doc.creation:
@@ -670,7 +701,7 @@
update_response_and_resolution_metrics(parent, for_resolution)
update_agreement_status(parent, for_resolution)
- parent.save()
+ parent.save(ignore_permissions=True)
def reset_expected_response_and_resolution(doc):
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py
deleted file mode 100644
index 22e2c37..0000000
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from frappe import _
-
-
-def get_data():
- return {
- 'fieldname': 'service_level_agreement',
- 'transactions': [
- {
- 'label': _('Issue'),
- 'items': ['Issue']
- }
- ]
- }
diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
index b07c862..a34124f 100644
--- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
@@ -244,6 +244,13 @@
applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
self.assertEqual(applied_sla, lead_sla.name)
+ # check if SLA is removed if condition fails
+ lead.reload()
+ lead.source = None
+ lead.save()
+ applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
+ self.assertFalse(applied_sla)
+
def tearDown(self):
for d in frappe.get_all("Service Level Agreement"):
frappe.delete_doc("Service Level Agreement", d.name, force=1)
diff --git a/erpnext/support/workspace/support/support.json b/erpnext/support/workspace/support/support.json
index d68c7c7..8ca3a67 100644
--- a/erpnext/support/workspace/support/support.json
+++ b/erpnext/support/workspace/support/support.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Issue\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Maintenance Visit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Issues\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Warranty\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Issue\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Maintenance Visit\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Service Level Agreement\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Issues\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Service Level Agreement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Warranty\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:48:23.224699",
"docstatus": 0,
"doctype": "Workspace",
@@ -169,7 +169,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:02.699924",
+ "modified": "2022-01-13 17:48:27.247406",
"modified_by": "Administrator",
"module": "Support",
"name": "Support",
@@ -178,7 +178,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 25,
+ "sequence_id": 25.0,
"shortcuts": [
{
"color": "Yellow",
diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index bc9f04e..2bd7e9e 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -92,6 +92,8 @@
for key, value in settings_dict.items():
setattr(settings, key, value)
settings.save()
+ # singles are cached by default, clear to avoid flake
+ frappe.db.value_cache[settings] = {}
yield # yield control to calling function
finally:
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 1d8b3a8..feea228 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -181,8 +181,6 @@
if len(child_table_values) > 1:
self.set(default_field, None)
- else:
- self.set(default_field, list(child_table_values)[0])
def delete_events(ref_type, ref_name):
events = frappe.db.sql_list(""" SELECT
diff --git a/erpnext/utilities/workspace/utilities/utilities.json b/erpnext/utilities/workspace/utilities/utilities.json
index 02a8af5..5b81e03 100644
--- a/erpnext/utilities/workspace/utilities/utilities.json
+++ b/erpnext/utilities/workspace/utilities/utilities.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Video\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Video\",\"col\":4}}]",
"creation": "2020-09-10 12:21:22.335307",
"docstatus": 0,
"doctype": "Workspace",
@@ -40,7 +40,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:03.350805",
+ "modified": "2022-01-13 17:50:10.067510",
"modified_by": "Administrator",
"module": "Utilities",
"name": "Utilities",
@@ -49,7 +49,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 30,
+ "sequence_id": 30.0,
"shortcuts": [],
"title": "Utilities"
}
\ No newline at end of file