From d46b23699c145de64c8998146d2152ad12314dfa Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 23 Feb 2021 16:38:52 +0530 Subject: [PATCH] fix: optimize reposting of gle and sle (#24702) * fix(india): escape for special characters in JSON (#24695) JSON does not accept special whitespace characters like tab, carriage return, line feed Ref: https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf Related issue: ISS-20-21-09811 * fix: Accounting Dimension creation background job timeout * fix(regional): vehicle no is mandatory for ewaybill generation (#24679) * fix: vehicle no required for e-invoice * fix: ewaybill generation dialog condition * fix: excluding unidentified accounts from gstr-1 * fix: optimize reposting of sle and gle (#24694) * fix: optimize update_gl_entries_after method * fix: Optimized reposting patch * fix: accounting dimensions * added reload_doc in patch * Update item_reposting_for_incorrect_sl_and_gl.py Co-authored-by: Rohit Waghchaure * fix: Replaced spaces with tabs * fix: merge conflict * fix: test cases Co-authored-by: Ankush Menat Co-authored-by: Deepesh Garg Co-authored-by: Saqib Co-authored-by: pateljannat Co-authored-by: Rohit Waghchaure --- .../accounting_dimension.py | 19 ++++--- erpnext/accounts/doctype/gl_entry/gl_entry.py | 50 ++++++++----------- .../doctype/pos_invoice/pos_invoice.js | 7 +++ .../doctype/pos_invoice/pos_invoice.py | 8 ++- .../pos_invoice_merge_log.py | 3 ++ .../doctype/pos_profile/pos_profile.json | 34 +++++++++++-- .../doctype/sales_invoice/sales_invoice.py | 4 +- erpnext/accounts/general_ledger.py | 14 +++--- erpnext/accounts/utils.py | 3 +- erpnext/controllers/accounts_controller.py | 1 + erpnext/controllers/selling_controller.py | 10 +++- erpnext/controllers/stock_controller.py | 14 ++++-- erpnext/controllers/taxes_and_totals.py | 2 +- .../mpesa_settings/test_mpesa_settings.py | 3 ++ erpnext/hr/doctype/employee/test_employee.py | 3 +- erpnext/patches.txt | 3 +- .../item_reposting_for_incorrect_sl_and_gl.py | 31 +++++++++--- .../v13_0/replace_pos_payment_mode_table.py | 4 +- .../v13_0/update_vehicle_no_reqd_condition.py | 9 ++++ .../payroll_entry/test_payroll_entry.py | 34 ------------- erpnext/regional/india/e_invoice/einvoice.js | 1 - erpnext/regional/india/e_invoice/utils.py | 2 +- erpnext/regional/report/gstr_1/gstr_1.py | 4 +- .../regional/united_arab_emirates/setup.py | 2 + .../page/point_of_sale/pos_controller.js | 38 ++++++++------ .../page/point_of_sale/pos_item_cart.js | 29 ++++++++--- .../page/point_of_sale/pos_item_details.js | 44 +++++++++++----- .../point_of_sale/pos_past_order_summary.js | 10 +++- erpnext/stock/doctype/item/item.py | 11 ++-- erpnext/stock/get_item_details.py | 2 +- erpnext/stock/stock_ledger.py | 13 ++--- 31 files changed, 257 insertions(+), 155 deletions(-) create mode 100644 erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 52e9ff8b76..239588f897 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -33,11 +33,11 @@ class AccountingDimension(Document): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) else: - frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self) + frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long') def on_trash(self): if frappe.flags.in_test: - delete_accounting_dimension(doc=self) + delete_accounting_dimension(doc=self, queue='long') else: frappe.enqueue(delete_accounting_dimension, doc=self) @@ -48,6 +48,9 @@ class AccountingDimension(Document): if not self.fieldname: self.fieldname = scrub(self.label) + def on_update(self): + frappe.flags.accounting_dimensions = None + def make_dimension_in_accounting_doctypes(doc): doclist = get_doctypes_with_dimensions() doc_count = len(get_accounting_dimensions()) @@ -165,9 +168,9 @@ def toggle_disabling(doc): frappe.clear_cache(doctype=doctype) def get_doctypes_with_dimensions(): - doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", + doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset", "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", - "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", + "Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", @@ -176,12 +179,14 @@ def get_doctypes_with_dimensions(): return doclist def get_accounting_dimensions(as_list=True): - accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]) + if frappe.flags.accounting_dimensions is None: + frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension", + fields=["label", "fieldname", "disabled", "document_type"]) if as_list: - return [d.fieldname for d in accounting_dimensions] + return [d.fieldname for d in frappe.flags.accounting_dimensions] else: - return accounting_dimensions + return frappe.flags.accounting_dimensions def get_checks_for_pl_and_bs_accounts(): dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index b0a864f76c..ce76d0a39c 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -27,30 +27,30 @@ class GLEntry(Document): def validate(self): self.flags.ignore_submit_comment = True - self.check_mandatory() self.validate_and_set_fiscal_year() self.pl_must_have_cost_center() - self.validate_cost_center() if not self.flags.from_repost: + self.check_mandatory() + self.validate_cost_center() self.check_pl_account() self.validate_party() self.validate_currency() - def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): - if not from_repost: + def on_update(self): + adv_adj = self.flags.adv_adj + if not self.flags.from_repost: self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs() self.validate_allowed_dimensions() + validate_balance_type(self.account, adv_adj) + validate_frozen_account(self.account, adv_adj) - validate_frozen_account(self.account, adv_adj) - validate_balance_type(self.account, adv_adj) - - # Update outstanding amt on against voucher - if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ - and self.against_voucher and update_outstanding == 'Yes' and not from_repost: - update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, - self.against_voucher) + # Update outstanding amt on against voucher + if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] + and self.against_voucher and self.flags.update_outstanding == 'Yes'): + update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, + self.against_voucher) def check_mandatory(self): mandatory = ['account','voucher_type','voucher_no','company'] @@ -58,7 +58,7 @@ class GLEntry(Document): if not self.get(k): frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) - account_type = frappe.db.get_value("Account", self.account, "account_type") + account_type = frappe.get_cached_value("Account", self.account, "account_type") if not (self.party_type and self.party): if account_type == "Receivable": frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}") @@ -73,7 +73,7 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account)) def pl_must_have_cost_center(self): - if frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss": + if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": if not self.cost_center and self.voucher_type != 'Period Closing Voucher': frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.") .format(self.voucher_type, self.voucher_no, self.account)) @@ -140,25 +140,16 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account, self.company)) def validate_cost_center(self): - if not hasattr(self, "cost_center_company"): - self.cost_center_company = {} + if not self.cost_center: return - def _get_cost_center_company(): - if not self.cost_center_company.get(self.cost_center): - self.cost_center_company[self.cost_center] = frappe.db.get_value( - "Cost Center", self.cost_center, "company") + is_group, company = frappe.get_cached_value('Cost Center', + self.cost_center, ['is_group', 'company']) - return self.cost_center_company[self.cost_center] - - def _check_is_group(): - return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group')) - - if self.cost_center and _get_cost_center_company() != self.company: + if company != self.company: frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") .format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) - if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \ - and self.cost_center and _check_is_group(): + if (self.voucher_type != 'Period Closing Voucher' and is_group): frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format( self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) @@ -184,7 +175,6 @@ class GLEntry(Document): if not self.fiscal_year: self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] - def validate_balance_type(account, adv_adj=False): if not adv_adj and account: balance_must_be = frappe.db.get_value("Account", account, "balance_must_be") @@ -250,7 +240,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga def validate_frozen_account(account, adv_adj=None): - frozen_account = frappe.db.get_value("Account", account, "freeze_account") + frozen_account = frappe.get_cached_value("Account", account, "freeze_account") if frozen_account == 'Yes' and not adv_adj: frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None, 'frozen_accounts_modifier') diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index 465f0d31c4..493bd44802 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -2,6 +2,7 @@ // For license information, please see license.txt {% include 'erpnext/selling/sales_common.js' %}; +frappe.provide("erpnext.accounts"); erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({ setup(doc) { @@ -9,6 +10,10 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend( this._super(doc); }, + company: function() { + erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); + }, + onload(doc) { this._super(); this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log']; @@ -16,6 +21,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend( this.frm.script_manager.trigger("is_pos"); this.frm.refresh_fields(); } + + erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype); }, refresh(doc) { diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 9d8f848b02..0c1406c1ce 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -93,7 +93,7 @@ class POSInvoice(SalesInvoice): mode_of_payment=pay.mode_of_payment, status="Paid"), fieldname="grand_total") - if pay.amount != paid_amt: + if paid_amt and pay.amount != paid_amt: return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment)) def validate_stock_availablility(self): @@ -311,7 +311,9 @@ class POSInvoice(SalesInvoice): self.set(fieldname, profile.get(fieldname)) if self.customer: - customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) + customer_price_list, customer_group, customer_currency = frappe.db.get_value( + "Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency'] + ) customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list') selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list') if customer_currency != profile.get('currency'): @@ -322,6 +324,8 @@ class POSInvoice(SalesInvoice): if selling_price_list: self.set('selling_price_list', selling_price_list) + if customer_currency != profile.get('currency'): + self.set('currency', customer_currency) # set pos values in items for item in self.get("items"): diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 5496804474..58409cd3c6 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -120,6 +120,7 @@ class POSInvoiceMergeLog(Document): i.qty = i.qty + item.qty if not found: item.rate = item.net_rate + item.price_list_rate = 0 si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"}) items.append(si_item) @@ -157,6 +158,8 @@ class POSInvoiceMergeLog(Document): invoice.set('taxes', taxes) invoice.additional_discount_percentage = 0 invoice.discount_amount = 0.0 + invoice.taxes_and_charges = None + invoice.ignore_pricing_rule = 1 return invoice diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 750ed82d15..4b69f6e2ef 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -12,8 +12,6 @@ "company", "country", "column_break_9", - "update_stock", - "ignore_pricing_rule", "warehouse", "campaign", "company_address", @@ -25,8 +23,14 @@ "hide_images", "hide_unavailable_items", "auto_add_item_to_cart", - "item_groups", "column_break_16", + "update_stock", + "ignore_pricing_rule", + "allow_rate_change", + "allow_discount_change", + "section_break_23", + "item_groups", + "column_break_25", "customer_groups", "section_break_16", "print_format", @@ -309,6 +313,7 @@ "default": "1", "fieldname": "update_stock", "fieldtype": "Check", + "hidden": 1, "label": "Update Stock", "read_only": 1 }, @@ -329,13 +334,34 @@ "fieldname": "auto_add_item_to_cart", "fieldtype": "Check", "label": "Automatically Add Filtered Item To Cart" + }, + { + "default": "0", + "fieldname": "allow_rate_change", + "fieldtype": "Check", + "label": "Allow User to Edit Rate" + }, + { + "default": "0", + "fieldname": "allow_discount_change", + "fieldtype": "Check", + "label": "Allow User to Edit Discount" + }, + { + "collapsible": 1, + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" } ], "icon": "icon-cog", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-20 13:59:28.877572", + "modified": "2021-01-06 14:42:41.713864", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c39bd3954c..903a2ef5f7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -456,7 +456,9 @@ class SalesInvoice(SellingController): if not for_validate and not self.customer: self.customer = pos.customer - self.ignore_pricing_rule = pos.ignore_pricing_rule + if not for_validate: + self.ignore_pricing_rule = pos.ignore_pricing_rule + if pos.get('account_for_change_amount'): self.account_for_change_amount = pos.get('account_for_change_amount') diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 287c79f13f..b42c0c61d9 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -44,9 +44,9 @@ def validate_accounting_period(gl_map): frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}") .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) -def process_gl_map(gl_map, merge_entries=True): +def process_gl_map(gl_map, merge_entries=True, precision=None): if merge_entries: - gl_map = merge_similar_entries(gl_map) + gl_map = merge_similar_entries(gl_map, precision) for entry in gl_map: # toggle debit, credit if negative entry if flt(entry.debit) < 0: @@ -69,7 +69,7 @@ def process_gl_map(gl_map, merge_entries=True): return gl_map -def merge_similar_entries(gl_map): +def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() for entry in gl_map: @@ -88,7 +88,9 @@ def merge_similar_entries(gl_map): company = gl_map[0].company if gl_map else erpnext.get_default_company() company_currency = erpnext.get_company_currency(company) - precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) + + if not precision: + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) # filter zero debit and credit entries merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map) @@ -132,8 +134,8 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.update(args) gle.flags.ignore_permissions = 1 gle.flags.from_repost = from_repost - gle.insert() - gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) + gle.flags.adv_adj = adv_adj + gle.flags.update_outstanding = update_outstanding or 'Yes' gle.submit() if not from_repost: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 60d1e20fea..5eb2aab393 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -902,10 +902,9 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa warehouse_account = get_warehouse_account_map(company) gle = get_voucherwise_gl_entries(stock_vouchers, posting_date) - for voucher_type, voucher_no in stock_vouchers: existing_gle = gle.get((voucher_type, voucher_no), []) - voucher_obj = frappe.get_doc(voucher_type, voucher_no) + voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no) expected_gle = voucher_obj.get_gl_entries(warehouse_account) if expected_gle: if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a838259862..e6b14c2e40 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -302,6 +302,7 @@ class AccountsController(TransactionBase): args["doctype"] = self.doctype args["name"] = self.name args["child_docname"] = item.name + args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0 if not args.get("transaction_date"): args["transaction_date"] = args.get("posting_date") diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index aa7b27adf4..3e82d46c2d 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -473,13 +473,19 @@ class SellingController(StockController): non_stock_items = [d.item_code, d.description] if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1: + duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code)) + duplicate_items_msg += "

" + duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format( + frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"), + get_link_to_form("Selling Settings", "Selling Settings") + ) if stock_items in check_list: - frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) + frappe.throw(duplicate_items_msg) else: check_list.append(stock_items) else: if non_stock_items in chk_dupl_itm: - frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) + frappe.throw(duplicate_items_msg) else: chk_dupl_itm.append(non_stock_items) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 8c0a62c026..cb44b73caf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -74,7 +74,7 @@ class StockController(AccountsController): gl_list = [] warehouse_with_no_account = [] - precision = frappe.get_precision("GL Entry", "debit_in_account_currency") + precision = self.get_debit_field_precision() for item_row in voucher_details: sle_list = sle_map.get(item_row.name) @@ -131,7 +131,13 @@ class StockController(AccountsController): if frappe.db.get_value("Warehouse", wh, "company"): frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company)) - return process_gl_map(gl_list) + return process_gl_map(gl_list, precision=precision) + + def get_debit_field_precision(self): + if not frappe.flags.debit_field_precision: + frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency") + + return frappe.flags.debit_field_precision def update_stock_ledger_entries(self, sle): sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, @@ -244,7 +250,7 @@ class StockController(AccountsController): .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing")) else: - is_expense_account = frappe.db.get_value("Account", + is_expense_account = frappe.get_cached_value("Account", item.get("expense_account"), "report_type")=="Profit and Loss" if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account: frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account") @@ -492,7 +498,7 @@ class StockController(AccountsController): elif not is_reposting_pending(): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) - + def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 1f50e9c14d..6c7eb92221 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -107,7 +107,7 @@ class calculate_taxes_and_totals(object): elif item.discount_amount and item.pricing_rules: item.rate = item.price_list_rate - item.discount_amount - if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']: + if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']: item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 18d2732313..08b2bc2fa8 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -50,6 +50,7 @@ class TestMpesaSettings(unittest.TestCase): create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") pos_invoice = create_pos_invoice(do_not_submit=1) pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500}) @@ -195,6 +196,8 @@ class TestMpesaSettings(unittest.TestCase): pr.delete() pos_invoice.delete() + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") + def create_mpesa_settings(payment_gateway_name="Express"): if frappe.db.exists("Mpesa Settings", payment_gateway_name): return frappe.get_doc("Mpesa Settings", payment_gateway_name) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index c0e614ac08..b88daaa34f 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -21,8 +21,7 @@ class TestEmployee(unittest.TestCase): from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders - employees_born_today = get_employees_who_are_born_today() - self.assertTrue(employees_born_today.get("_Test Company")) + self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()]) frappe.db.sql("delete from `tabEmail Queue`") diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0003bb3b1a..80e2f1c01a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -677,7 +677,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.rename_pos_closing_doctype -erpnext.patches.v13_0.replace_pos_payment_mode_table +erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29 erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22 erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive execute:frappe.reload_doc("HR", "doctype", "Employee Advance") @@ -752,3 +752,4 @@ erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v12_0.add_state_code_for_ladakh +erpnext.patches.v13_0.update_vehicle_no_reqd_condition diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index f60e0d3036..06f7f989bb 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -4,11 +4,26 @@ from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after def execute(): - data = frappe.db.sql(''' SELECT name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time - from `tabStock Ledger Entry` where creation > '2020-12-26 12:58:55.903836' and is_cancelled = 0 - order by timestamp(posting_date, posting_time) asc, creation asc''', as_dict=1) + frappe.reload_doc('stock', 'doctype', 'repost_item_valuation') - for index, d in enumerate(data): + reposting_project_deployed_on = frappe.db.get_value("DocType", "Repost Item Valuation", "creation") + + data = frappe.db.sql(''' + SELECT + name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time + FROM + `tabStock Ledger Entry` + WHERE + creation > %s + and is_cancelled = 0 + ORDER BY timestamp(posting_date, posting_time) asc, creation asc + ''', reposting_project_deployed_on, as_dict=1) + + frappe.db.auto_commit_on_many_writes = 1 + print("Reposting Stock Ledger Entries...") + total_sle = len(data) + i = 0 + for d in data: update_entries_after({ "item_code": d.item_code, "warehouse": d.warehouse, @@ -19,9 +34,13 @@ def execute(): "sle_id": d.name }, allow_negative_stock=True) - frappe.db.auto_commit_on_many_writes = 1 + i += 1 + if i%100 == 0: + print(i, "/", total_sle) + + print("Reposting General Ledger Entries...") for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): update_gl_entries_after('2020-12-25', '01:58:55', company=row.name) - frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file + frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py index 1ca211bf1b..7cb264830a 100644 --- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -6,12 +6,10 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("accounts", "doctype", "POS Payment Method") + frappe.reload_doc("accounts", "doctype", "pos_payment_method") pos_profiles = frappe.get_all("POS Profile") for pos_profile in pos_profiles: - if not pos_profile.get("payments"): return - payments = frappe.db.sql(""" select idx, parentfield, parenttype, parent, mode_of_payment, `default` from `tabSales Invoice Payment` where parent=%s """, pos_profile.name, as_dict=1) diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py new file mode 100644 index 0000000000..c26cddbe4e --- /dev/null +++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + if frappe.db.exists('Custom Field', { 'fieldname': 'vehicle_no' }): + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', '') diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index e098ec79b0..9e68df99eb 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -41,40 +41,6 @@ class TestPayrollEntry(unittest.TestCase): make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account=company_doc.default_payroll_payable_account, currency=company_doc.default_currency) - def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use - company = erpnext.get_default_company() - employee = make_employee("test_muti_currency_employee@payroll.com", company=company) - for data in frappe.get_all('Salary Component', fields = ["name"]): - if not frappe.db.get_value('Salary Component Account', - {'parent': data.name, 'company': company}, 'name'): - get_salary_component_account(data.name) - - company_doc = frappe.get_doc('Company', company) - salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD') - create_salary_structure_assignment(employee, salary_structure.name, company=company) - frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"}))) - salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure") - dates = get_start_end_dates('Monthly', nowdate()) - payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, - payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70) - payroll_entry.make_payment_entry() - - salary_slip.load_from_db() - - payroll_je = salary_slip.journal_entry - payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je) - - self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit) - self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit) - - payment_entry = frappe.db.sql(''' - Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea - Where je.name = jea.parent - And jea.reference_name = %s - ''', (payroll_entry.name), as_dict=1) - - self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit) - self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit) def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use for data in frappe.get_all('Salary Component', fields = ["name"]): diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index a5306839b9..e8a7c30e19 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -188,7 +188,6 @@ const get_ewaybill_fields = (frm) => { 'fieldname': 'vehicle_no', 'label': 'Vehicle No', 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.mode_of_transport === "Road")', 'default': frm.doc.vehicle_no }, { diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 410c09396d..438ec79ed1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -160,7 +160,7 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.description = d.item_name.replace('"', '\\"') + item.description = json.dumps(d.item_name)[1:-1] item.qty = abs(item.qty) item.discount_amount = 0 diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 96dc3f728d..09b04ff367 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -236,6 +236,7 @@ class Gstr1Report(object): self.cgst_sgst_invoices = [] unidentified_gst_accounts = [] + unidentified_gst_accounts_invoice = [] for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: if account in self.gst_accounts.cess_account: self.invoice_cess.setdefault(parent, tax_amount) @@ -251,6 +252,7 @@ class Gstr1Report(object): if not (cgst_or_sgst or account in self.gst_accounts.igst_account): if "gst" in account.lower() and account not in unidentified_gst_accounts: unidentified_gst_accounts.append(account) + unidentified_gst_accounts_invoice.append(parent) continue for item_code, tax_amounts in item_wise_tax_detail.items(): @@ -273,7 +275,7 @@ class Gstr1Report(object): # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): - if invoice not in self.items_based_on_tax_rate \ + if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 013ae5cf73..776a82c730 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -110,9 +110,11 @@ def make_custom_fields(): 'Purchase Order': purchase_invoice_fields + invoice_fields, 'Purchase Receipt': purchase_invoice_fields + invoice_fields, 'Sales Invoice': sales_invoice_fields + invoice_fields, + 'POS Invoice': sales_invoice_fields + invoice_fields, 'Sales Order': sales_invoice_fields + invoice_fields, 'Delivery Note': sales_invoice_fields + invoice_fields, 'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt], + 'POS Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt], 'Purchase Invoice Item': invoice_item_fields, 'Sales Order Item': invoice_item_fields, 'Delivery Note Item': invoice_item_fields, diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index d4cde43359..45b4e30bf0 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -69,6 +69,10 @@ erpnext.PointOfSale.Controller = class { dialog.fields_dict.balance_details.grid.refresh(); }); } + const pos_profile_query = { + query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', + filters: { company: frappe.defaults.get_default('company') } + } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), static: true, @@ -80,6 +84,7 @@ erpnext.PointOfSale.Controller = class { { fieldtype: 'Link', label: __('POS Profile'), options: 'POS Profile', fieldname: 'pos_profile', reqd: 1, + get_query: () => pos_profile_query, onchange: () => fetch_pos_payment_methods() }, { @@ -124,9 +129,8 @@ erpnext.PointOfSale.Controller = class { }); frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => { + Object.assign(this.settings, profile); this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group); - this.settings.hide_images = profile.hide_images; - this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart; this.make_app(); }); } @@ -255,11 +259,9 @@ erpnext.PointOfSale.Controller = class { get_frm: () => this.frm, cart_item_clicked: (item_code, batch_no, uom) => { - const item_row = this.frm.doc.items.find( - i => i.item_code === item_code - && i.uom === uom - && (!batch_no || (batch_no && i.batch_no === batch_no)) - ); + const search_field = batch_no ? 'batch_no' : 'item_code'; + const search_value = batch_no || item_code; + const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom); this.item_details.toggle_item_details_section(item_row); }, @@ -281,6 +283,7 @@ erpnext.PointOfSale.Controller = class { init_item_details() { this.item_details = new erpnext.PointOfSale.ItemDetails({ wrapper: this.$components_wrapper, + settings: this.settings, events: { get_frm: () => this.frm, @@ -415,6 +418,11 @@ erpnext.PointOfSale.Controller = class { () => this.item_selector.toggle_component(true) ]); }, + delete_order: (name) => { + frappe.model.delete_doc(this.frm.doc.doctype, name, () => { + this.recent_order_list.refresh_list(); + }); + }, new_order: () => { frappe.run_serially([ () => frappe.dom.freeze(), @@ -696,14 +704,14 @@ erpnext.PointOfSale.Controller = class { frappe.dom.freeze(); const { doctype, name, current_item } = this.item_details; - frappe.model.set_value(doctype, name, 'qty', 0); - - this.frm.script_manager.trigger('qty', doctype, name).then(() => { - frappe.model.clear_doc(doctype, name); - this.update_cart_html(current_item, true); - this.item_details.toggle_item_details_section(undefined); - frappe.dom.unfreeze(); - }) + frappe.model.set_value(doctype, name, 'qty', 0) + .then(() => { + frappe.model.clear_doc(doctype, name); + this.update_cart_html(current_item, true); + this.item_details.toggle_item_details_section(undefined); + frappe.dom.unfreeze(); + }) + .catch(e => console.log(e)); } } diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 03d99c6bfa..de70f167a5 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -5,6 +5,8 @@ erpnext.PointOfSale.ItemCart = class { this.customer_info = undefined; this.hide_images = settings.hide_images; this.allowed_customer_groups = settings.customer_groups; + this.allow_rate_change = settings.allow_rate_change; + this.allow_discount_change = settings.allow_discount_change; this.init_component(); } @@ -201,7 +203,7 @@ erpnext.PointOfSale.ItemCart = class { me.events.checkout(); me.toggle_checkout_btn(false); - me.$add_discount_elem.removeClass("d-none"); + me.allow_discount_change && me.$add_discount_elem.removeClass("d-none"); }); this.$totals_section.on('click', '.edit-cart-btn', () => { @@ -479,8 +481,8 @@ erpnext.PointOfSale.ItemCart = class { update_totals_section(frm) { if (!frm) frm = this.events.get_frm(); - this.render_net_total(frm.doc.base_net_total); - this.render_grand_total(frm.doc.base_grand_total); + this.render_net_total(frm.doc.net_total); + this.render_grand_total(frm.doc.grand_total); const taxes = frm.doc.taxes.map(t => { return { @@ -549,7 +551,7 @@ erpnext.PointOfSale.ItemCart = class { get_cart_item({ item_code, batch_no, uom }) { const batch_attr = `[data-batch-no="${escape(batch_no)}"]`; const item_code_attr = `[data-item-code="${escape(item_code)}"]`; - const uom_attr = `[data-uom=${escape(uom)}]`; + const uom_attr = `[data-uom="${escape(uom)}"]`; const item_selector = batch_no ? `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`; @@ -671,7 +673,7 @@ erpnext.PointOfSale.ItemCart = class { update_selector_value_in_cart_item(selector, value, item) { const $item_to_update = this.get_cart_item(item); - $item_to_update.attr(`data-${selector}`, value); + $item_to_update.attr(`data-${selector}`, escape(value)); } toggle_checkout_btn(show_checkout) { @@ -706,14 +708,26 @@ erpnext.PointOfSale.ItemCart = class { on_numpad_event($btn) { const current_action = $btn.attr('data-button-value'); const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action); - - this.highlight_numpad_btn($btn, current_action); + const action_is_allowed = action_is_field_edit ? ( + (current_action == 'rate' && this.allow_rate_change) || + (current_action == 'discount_percentage' && this.allow_discount_change) || + (current_action == 'qty')) : true; const action_is_pressed_twice = this.prev_action === current_action; const first_click_event = !this.prev_action; const field_to_edit_changed = this.prev_action && this.prev_action != current_action; if (action_is_field_edit) { + if (!action_is_allowed) { + const label = current_action == 'rate' ? 'Rate'.bold() : 'Discount'.bold(); + const message = __('Editing {0} is not allowed as per POS Profile settings', [label]); + frappe.show_alert({ + indicator: 'red', + message: message + }); + frappe.utils.play_sound("error"); + return; + } if (first_click_event || field_to_edit_changed) { this.prev_action = current_action; @@ -757,6 +771,7 @@ erpnext.PointOfSale.ItemCart = class { this.numpad_value = current_action; } + this.highlight_numpad_btn($btn, current_action); this.events.numpad_event(this.numpad_value, this.prev_action); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index a4de9f165d..259631d14d 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -1,7 +1,9 @@ erpnext.PointOfSale.ItemDetails = class { - constructor({ wrapper, events }) { + constructor({ wrapper, events, settings }) { this.wrapper = wrapper; this.events = events; + this.allow_rate_change = settings.allow_rate_change; + this.allow_discount_change = settings.allow_discount_change; this.current_item = {}; this.init_component(); @@ -207,17 +209,27 @@ erpnext.PointOfSale.ItemDetails = class { bind_custom_control_change_event() { const me = this; if (this.rate_control) { - this.rate_control.df.onchange = function() { - if (this.value || flt(this.value) === 0) { - me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => { - const item_row = frappe.get_doc(me.doctype, me.name); - const doc = me.events.get_frm().doc; - - me.$item_price.html(format_currency(item_row.rate, doc.currency)); - me.render_discount_dom(item_row); - }); - } + if (this.allow_rate_change) { + this.rate_control.df.onchange = function() { + if (this.value || flt(this.value) === 0) { + me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => { + const item_row = frappe.get_doc(me.doctype, me.name); + const doc = me.events.get_frm().doc; + + me.$item_price.html(format_currency(item_row.rate, doc.currency)); + me.render_discount_dom(item_row); + }); + } + }; + } else { + this.rate_control.df.read_only = 1; } + this.rate_control.refresh(); + } + + if (this.discount_percentage_control && !this.allow_discount_change) { + this.discount_percentage_control.df.read_only = 1; + this.discount_percentage_control.refresh(); } if (this.warehouse_control) { @@ -294,8 +306,16 @@ erpnext.PointOfSale.ItemDetails = class { } frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { + const { item_code, batch_no, uom } = this.current_item; + const item_code_is_same = item_code === item_row.item_code; + const batch_is_same = batch_no == item_row.batch_no; + const uom_is_same = uom === item_row.uom; + // check if current_item is same as item_row + const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false; + const field_control = me[`${fieldname}_control`]; - if (field_control) { + + if (item_is_same && field_control && field_control.get_value() !== value) { field_control.set_value(value); cur_pos.update_cart_html(item_row); } diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 6fd4c26bea..598f50f192 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -265,6 +265,14 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_wrapper.addClass('d-none'); }); + this.$summary_container.on('click', '.delete-btn', () => { + this.events.delete_order(this.doc.name); + this.show_summary_placeholder(); + // this.toggle_component(false); + // this.$component.find('.no-summary-placeholder').removeClass('d-none'); + // this.$summary_wrapper.addClass('d-none'); + }); + this.$summary_container.on('click', '.new-btn', () => { this.events.new_order(); this.toggle_component(false); @@ -401,7 +409,7 @@ erpnext.PointOfSale.PastOrderSummary = class { return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }]; return [ - { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] }, + { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order', 'Delete Order'] }, { condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']}, { condition: this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt']} ]; diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index be845d9d9d..cda1069891 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -672,13 +672,14 @@ class Item(WebsiteGenerator): if not records: return document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations") - msg = _("The items {0} and {1} are present in the following {2} :
" - .format(frappe.bold(old_name), frappe.bold(new_name), document)) + msg = _("The items {0} and {1} are present in the following {2} : ").format( + frappe.bold(old_name), frappe.bold(new_name), document) + msg += '
' msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "

" - msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}" - .format(frappe.bold(old_name))) + msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format( + frappe.bold(old_name)) frappe.throw(_(msg), title=_("Merge not allowed")) @@ -971,7 +972,7 @@ class Item(WebsiteGenerator): frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) def check_if_linked_document_exists(self, field): - linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "Purchase Receipt Item", + linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item", "Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"] # For "Is Stock Item", following doctypes is important diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dfe8fea67b..873cfec85e 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -19,7 +19,7 @@ from erpnext.stock.doctype.item_manufacturer.item_manufacturer import get_item_m from six import string_types, iteritems -sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'] +sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'POS Invoice'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] @frappe.whitelist() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e4f5725c68..f54b3c1bb2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -58,8 +58,9 @@ def validate_cancellation(args): if repost_entry.status == 'In Progress': frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet.")) if repost_entry.status == 'Queued': - frappe.delete_doc("Repost Item Valuation", repost_entry.name) - + doc = frappe.get_doc("Repost Item Valuation", repost_entry.name) + doc.cancel() + doc.delete() def set_as_cancel(voucher_type, voucher_no): frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1, @@ -133,7 +134,7 @@ class update_entries_after(object): self.item_code = args.get("item_code") if self.args.sle_id: self.args['name'] = self.args.sle_id - + self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.get_precision() self.valuation_method = get_valuation_method(self.item_code) @@ -201,7 +202,7 @@ class update_entries_after(object): and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) order by timestamp(posting_date, posting_time) desc, creation desc limit 1""", args, as_dict=1) - + return sle[0] if sle else frappe._dict() @@ -224,7 +225,7 @@ class update_entries_after(object): if sle.dependant_sle_voucher_detail_no: entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) - + self.update_bin() if self.exceptions: @@ -439,7 +440,7 @@ class update_entries_after(object): # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): - doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no) + doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in (doc.items + doc.supplied_items): d.db_update()