From 725a7f90e9a873335caaa588d73ddb3de7383c33 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 19:45:19 +0530 Subject: [PATCH 001/112] fix: use dynamic link for against field --- erpnext/accounts/doctype/gl_entry/gl_entry.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 5063ec6076..1adeaa54d8 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -17,6 +17,7 @@ "account_currency", "debit_in_account_currency", "credit_in_account_currency", + "against_type", "against", "against_voucher_type", "against_voucher", @@ -128,13 +129,21 @@ "label": "Credit Amount in Account Currency", "options": "account_currency" }, + { + "fieldname": "against_type", + "fieldtype": "Link", + "in_filter": 1, + "label": "Against Type", + "options": "DocType" + }, { "fieldname": "against", - "fieldtype": "Text", + "fieldtype": "Dynamic Link", "in_filter": 1, "label": "Against", "oldfieldname": "against", - "oldfieldtype": "Text" + "oldfieldtype": "Text", + "options": "against_type" }, { "fieldname": "against_voucher_type", @@ -286,7 +295,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-08-16 21:38:44.072267", + "modified": "2023-09-25 12:03:23.031733", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", From 19b220f39ca3596e416d99f3aef7c2485019b9fd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 19:49:17 +0530 Subject: [PATCH 002/112] fix: set against type in inv gl dict --- .../purchase_invoice/purchase_invoice.py | 29 +++++++++++++++++++ .../doctype/sales_invoice/sales_invoice.py | 14 +++++++++ 2 files changed, 43 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 55972719f8..1dc67ef689 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -623,6 +623,7 @@ class PurchaseInvoice(BuyingController): "party_type": "Supplier", "party": self.supplier, "due_date": self.due_date, + "against_type": "Account", "against": self.against_expense_account, "credit": base_grand_total, "credit_in_account_currency": base_grand_total @@ -692,6 +693,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": warehouse_account[item.warehouse]["account"], + "against_type": "Account", "against": warehouse_account[item.from_warehouse]["account"], "cost_center": item.cost_center, "project": item.project or self.project, @@ -712,6 +714,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": warehouse_account[item.from_warehouse]["account"], + "against_type": "Account", "against": warehouse_account[item.warehouse]["account"], "cost_center": item.cost_center, "project": item.project or self.project, @@ -729,6 +732,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": item.expense_account, + "against_type": "Supplier", "against": self.supplier, "debit": flt(item.base_net_amount, item.precision("base_net_amount")), "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -746,6 +750,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": item.expense_account, + "against_type": "Supplier", "against": self.supplier, "debit": warehouse_debit_amount, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -765,6 +770,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": account, + "against_type": "Account", "against": item.expense_account, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -785,6 +791,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": supplier_warehouse_account, + "against_type": "Account", "against": item.expense_account, "cost_center": item.cost_center, "project": item.project or self.project, @@ -842,6 +849,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": expense_account, + "against_type": "Supplier", "against": self.supplier, "debit": amount, "cost_center": item.cost_center, @@ -868,6 +876,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": expense_account, + "against_type": "Supplier", "against": self.supplier, "debit": discrepancy_caused_by_exchange_rate_difference, "cost_center": item.cost_center, @@ -881,6 +890,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": self.get_company_default("exchange_gain_loss_account"), + "against_type": "Supplier", "against": self.supplier, "credit": discrepancy_caused_by_exchange_rate_difference, "cost_center": item.cost_center, @@ -901,6 +911,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": expenses_included_in_asset_valuation, + "against_type": "Account", "against": expense_account, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -915,6 +926,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": expense_account, + "against_type": "Account", "against": expenses_included_in_asset_valuation, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -954,6 +966,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": self.stock_received_but_not_billed, + "against_type": "Supplier", "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or _("Accounting Entry for Stock"), @@ -993,6 +1006,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": item.expense_account, + "against_type": "Supplier", "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": base_asset_amount, @@ -1015,6 +1029,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": eiiav_account, + "against_type": "Supplier", "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "cost_center": item.cost_center, @@ -1039,6 +1054,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": cwip_account, + "against_type": "Supplier", "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": base_asset_amount, @@ -1061,6 +1077,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": eiiav_account, + "against_type": "Supplier", "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "cost_center": item.cost_center, @@ -1085,6 +1102,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": eiiav_account, + "against_type": "Account", "against": cwip_account, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -1099,6 +1117,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": cwip_account, + "against_type": "Account", "against": eiiav_account, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -1146,6 +1165,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": cost_of_goods_sold_account, + "against_type": "Account", "against": item.expense_account, "debit": stock_adjustment_amt, "remarks": self.get("remarks") or _("Stock Adjustment"), @@ -1176,6 +1196,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": tax.account_head, + "against_type": "Supplier", "against": self.supplier, dr_or_cr: base_amount, dr_or_cr + "_in_account_currency": base_amount @@ -1224,6 +1245,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": tax.account_head, + "against_type": "Supplier", "cost_center": tax.cost_center, "against": self.supplier, "credit": applicable_amount, @@ -1243,6 +1265,7 @@ class PurchaseInvoice(BuyingController): { "account": tax.account_head, "cost_center": tax.cost_center, + "against_type": "Supplier", "against": self.supplier, "credit": valuation_tax[tax.name], "remarks": self.remarks or _("Accounting Entry for Stock"), @@ -1258,6 +1281,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": self.unrealized_profit_loss_account, + "against_type": "Supplier", "against": self.supplier, "credit": flt(self.total_taxes_and_charges), "credit_in_account_currency": flt(self.base_total_taxes_and_charges), @@ -1279,6 +1303,7 @@ class PurchaseInvoice(BuyingController): "account": self.credit_to, "party_type": "Supplier", "party": self.supplier, + "against_type": "Account", "against": self.cash_bank_account, "debit": self.base_paid_amount, "debit_in_account_currency": self.base_paid_amount @@ -1300,6 +1325,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": self.cash_bank_account, + "against_type": "Supplier", "against": self.supplier, "credit": self.base_paid_amount, "credit_in_account_currency": self.base_paid_amount @@ -1324,6 +1350,7 @@ class PurchaseInvoice(BuyingController): "account": self.credit_to, "party_type": "Supplier", "party": self.supplier, + "against_type": "Account", "against": self.write_off_account, "debit": self.base_write_off_amount, "debit_in_account_currency": self.base_write_off_amount @@ -1344,6 +1371,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": self.write_off_account, + "against_type": "Supplier", "against": self.supplier, "credit": flt(self.base_write_off_amount), "credit_in_account_currency": self.base_write_off_amount @@ -1371,6 +1399,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict( { "account": round_off_account, + "against_type": "Supplier", "against": self.supplier, "debit_in_account_currency": self.rounding_adjustment, "debit": self.base_rounding_adjustment, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7bdb2b49ce..db3d5c666f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1101,6 +1101,7 @@ class SalesInvoice(SellingController): "party_type": "Customer", "party": self.customer, "due_date": self.due_date, + "against_type": "Account", "against": self.against_income_account, "debit": base_grand_total, "debit_in_account_currency": base_grand_total @@ -1130,6 +1131,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": tax.account_head, + "against_type": "Customer", "against": self.customer, "credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")), "credit_in_account_currency": ( @@ -1151,6 +1153,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": self.unrealized_profit_loss_account, + "against_type": "Customer", "against": self.customer, "debit": flt(self.total_taxes_and_charges), "debit_in_account_currency": flt(self.base_total_taxes_and_charges), @@ -1219,6 +1222,7 @@ class SalesInvoice(SellingController): add_asset_activity(asset.name, _("Asset sold")) for gle in fixed_asset_gl_entries: + gle["against_type"] = "Customer" gle["against"] = self.customer gl_entries.append(self.get_gl_dict(gle, item=item)) @@ -1240,6 +1244,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": income_account, + "against_type": "Customer", "against": self.customer, "credit": flt(base_amount, item.precision("base_net_amount")), "credit_in_account_currency": ( @@ -1294,6 +1299,7 @@ class SalesInvoice(SellingController): "account": self.debit_to, "party_type": "Customer", "party": self.customer, + "against_type": "Account", "against": "Expense account - " + cstr(self.loyalty_redemption_account) + " for the Loyalty Program", @@ -1310,6 +1316,7 @@ class SalesInvoice(SellingController): { "account": self.loyalty_redemption_account, "cost_center": self.cost_center or self.loyalty_redemption_cost_center, + "against_type": "Customer", "against": self.customer, "debit": self.loyalty_amount, "remark": "Loyalty Points redeemed by the customer", @@ -1337,6 +1344,7 @@ class SalesInvoice(SellingController): "account": self.debit_to, "party_type": "Customer", "party": self.customer, + "against_type": "Account", "against": payment_mode.account, "credit": payment_mode.base_amount, "credit_in_account_currency": payment_mode.base_amount @@ -1358,6 +1366,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": payment_mode.account, + "against_type": "Customer", "against": self.customer, "debit": payment_mode.base_amount, "debit_in_account_currency": payment_mode.base_amount @@ -1382,6 +1391,7 @@ class SalesInvoice(SellingController): "account": self.debit_to, "party_type": "Customer", "party": self.customer, + "against_type": "Account", "against": self.account_for_change_amount, "debit": flt(self.base_change_amount), "debit_in_account_currency": flt(self.base_change_amount) @@ -1403,6 +1413,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": self.account_for_change_amount, + "against_type": "Customer", "against": self.customer, "credit": self.base_change_amount, "cost_center": self.cost_center, @@ -1429,6 +1440,7 @@ class SalesInvoice(SellingController): "account": self.debit_to, "party_type": "Customer", "party": self.customer, + "against_type": "Account", "against": self.write_off_account, "credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "credit_in_account_currency": ( @@ -1449,6 +1461,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": self.write_off_account, + "against_type": "Customer", "against": self.customer, "debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "debit_in_account_currency": ( @@ -1477,6 +1490,7 @@ class SalesInvoice(SellingController): self.get_gl_dict( { "account": round_off_account, + "against_type": "Customer", "against": self.customer, "credit_in_account_currency": flt( self.rounding_adjustment, self.precision("rounding_adjustment") From 82774f89b106a56c4754536b1c0cf5eb695a3c15 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 19:50:47 +0530 Subject: [PATCH 003/112] fix: set against type in deferred revenue --- erpnext/accounts/deferred_revenue.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index d0940c7df2..00d5ea3245 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -358,9 +358,11 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): account_currency = get_account_currency(item.expense_account or item.income_account) if doc.doctype == "Sales Invoice": + against_type = "Customer" against, project = doc.customer, doc.project credit_account, debit_account = item.income_account, item.deferred_revenue_account else: + against_type = "Supplier" against, project = doc.supplier, item.project credit_account, debit_account = item.deferred_expense_account, item.expense_account @@ -413,6 +415,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): doc, credit_account, debit_account, + against_type, against, amount, base_amount, @@ -494,6 +497,7 @@ def make_gl_entries( doc, credit_account, debit_account, + against_type, against, amount, base_amount, @@ -515,6 +519,7 @@ def make_gl_entries( doc.get_gl_dict( { "account": credit_account, + "against_type": against_type, "against": against, "credit": base_amount, "credit_in_account_currency": amount, @@ -534,6 +539,7 @@ def make_gl_entries( doc.get_gl_dict( { "account": debit_account, + "against_type": against_type, "against": against, "debit": base_amount, "debit_in_account_currency": amount, From 4c5a83d6cf7e4b39a403b1e99a2b1c443f854ac9 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 19:55:40 +0530 Subject: [PATCH 004/112] fix: set against type in controllers --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 1 + erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 +++++++ erpnext/controllers/accounts_controller.py | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 85ef6f76d2..0616643a68 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -906,6 +906,7 @@ class JournalEntry(AccountsController): "party_type": d.party_type, "due_date": self.due_date, "party": d.party, + "against_type": "Account", "against": d.against_account, "debit": flt(d.debit, d.precision("debit")), "credit": flt(d.credit, d.precision("credit")), diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 38a520996c..6cfc072aea 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1074,6 +1074,7 @@ class PaymentEntry(AccountsController): "account": self.party_account, "party_type": self.party_type, "party": self.party, + "against_type": "Account", "against": against_account, "account_currency": self.party_account_currency, "cost_center": self.cost_center, @@ -1232,6 +1233,7 @@ class PaymentEntry(AccountsController): { "account": self.paid_from, "account_currency": self.paid_from_account_currency, + "against_type": self.party_type if self.payment_type == "Pay" else "Account", "against": self.party if self.payment_type == "Pay" else self.paid_to, "credit_in_account_currency": self.paid_amount, "credit": self.base_paid_amount, @@ -1247,6 +1249,7 @@ class PaymentEntry(AccountsController): { "account": self.paid_to, "account_currency": self.paid_to_account_currency, + "against_type": self.party_type if self.payment_type == "Receive" else "Account", "against": self.party if self.payment_type == "Receive" else self.paid_from, "debit_in_account_currency": self.received_amount, "debit": self.base_received_amount, @@ -1271,6 +1274,7 @@ class PaymentEntry(AccountsController): rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" against = self.party or self.paid_to + against_type = self.party_type or "Account" payment_account = self.get_party_account_for_taxes() tax_amount = d.tax_amount base_tax_amount = d.base_tax_amount @@ -1279,6 +1283,7 @@ class PaymentEntry(AccountsController): self.get_gl_dict( { "account": d.account_head, + "against_type": against_type, "against": against, dr_or_cr: tax_amount, dr_or_cr + "_in_account_currency": base_tax_amount @@ -1304,6 +1309,7 @@ class PaymentEntry(AccountsController): self.get_gl_dict( { "account": payment_account, + "against_type": against_type, "against": against, rev_dr_or_cr: tax_amount, rev_dr_or_cr + "_in_account_currency": base_tax_amount @@ -1329,6 +1335,7 @@ class PaymentEntry(AccountsController): { "account": d.account, "account_currency": account_currency, + "against_type": self.party_type or "Account", "against": self.party or self.paid_from, "debit_in_account_currency": d.amount, "debit": d.amount, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e635aa7924..d77b8a3c7f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1040,6 +1040,7 @@ class AccountsController(TransactionBase): ) credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit" + against_type = "Supplier" if self.doctype == "Purchase Invoice" else "Customer" against = self.supplier if self.doctype == "Purchase Invoice" else self.customer if precision_loss: @@ -1047,6 +1048,7 @@ class AccountsController(TransactionBase): self.get_gl_dict( { "account": round_off_account, + "against_type": against_type, "against": against, credit_or_debit: precision_loss, "cost_center": round_off_cost_center @@ -1394,11 +1396,13 @@ class AccountsController(TransactionBase): if self.doctype == "Purchase Invoice": dr_or_cr = "credit" rev_dr_cr = "debit" + against_type = "Supplier" supplier_or_customer = self.supplier else: dr_or_cr = "debit" rev_dr_cr = "credit" + against_type = "Customer" supplier_or_customer = self.customer if enable_discount_accounting: @@ -1423,6 +1427,7 @@ class AccountsController(TransactionBase): self.get_gl_dict( { "account": item.discount_account, + "against_type": against_type, "against": supplier_or_customer, dr_or_cr: flt( discount_amount * self.get("conversion_rate"), item.precision("discount_amount") @@ -1441,6 +1446,7 @@ class AccountsController(TransactionBase): self.get_gl_dict( { "account": income_or_expense_account, + "against_type": against_type, "against": supplier_or_customer, rev_dr_cr: flt( discount_amount * self.get("conversion_rate"), item.precision("discount_amount") @@ -1464,6 +1470,7 @@ class AccountsController(TransactionBase): self.get_gl_dict( { "account": self.additional_discount_account, + "against_type": against_type, "against": supplier_or_customer, dr_or_cr: self.base_discount_amount, "cost_center": self.cost_center, From f292a0cc4c89814607fec0bb66d1c458d6624750 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 19:58:12 +0530 Subject: [PATCH 005/112] fix: set against type in utils --- .../accounts/doctype/invoice_discounting/invoice_discounting.py | 2 ++ erpnext/regional/united_arab_emirates/utils.py | 1 + erpnext/stock/doctype/stock_entry/stock_entry.py | 2 ++ 3 files changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 5bd4585a9a..744dff4670 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -125,6 +125,7 @@ class InvoiceDiscounting(AccountsController): "account": inv.debit_to, "party_type": "Customer", "party": d.customer, + "against_type": "Account", "against": self.accounts_receivable_credit, "credit": outstanding_in_company_currency, "credit_in_account_currency": outstanding_in_company_currency @@ -145,6 +146,7 @@ class InvoiceDiscounting(AccountsController): "account": self.accounts_receivable_credit, "party_type": "Customer", "party": d.customer, + "against_type": "Account", "against": inv.debit_to, "debit": outstanding_in_company_currency, "debit_in_account_currency": outstanding_in_company_currency diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index a910af6a1d..d70d5466e0 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -153,6 +153,7 @@ def make_gl_entry(tax, gl_entries, doc, tax_accounts): "account": tax.account_head, "cost_center": tax.cost_center, "posting_date": doc.posting_date, + "against_type": "Supplier", "against": doc.supplier, dr_or_cr: tax.base_tax_amount_after_discount_amount, dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index a2cae7ff8d..2124168b62 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1360,6 +1360,7 @@ class StockEntry(StockController): self.get_gl_dict( { "account": account, + "against_type": "Account", "against": d.expense_account, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -1374,6 +1375,7 @@ class StockEntry(StockController): self.get_gl_dict( { "account": d.expense_account, + "against_type": "Account", "against": account, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), From 4ea43ebc5dadc35e66cec30d36a9578118f01622 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 20:01:13 +0530 Subject: [PATCH 006/112] fix: set against type in stock controller --- erpnext/controllers/stock_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ae54b801f1..5a04f71002 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -164,6 +164,7 @@ class StockController(AccountsController): self.get_gl_dict( { "account": warehouse_account[sle.warehouse]["account"], + "against_type": "Account", "against": expense_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), @@ -180,6 +181,7 @@ class StockController(AccountsController): self.get_gl_dict( { "account": expense_account, + "against_type": "Account", "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -212,6 +214,7 @@ class StockController(AccountsController): self.get_gl_dict( { "account": expense_account, + "against_type": "Account", "against": warehouse_asset_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), @@ -228,6 +231,7 @@ class StockController(AccountsController): self.get_gl_dict( { "account": warehouse_asset_account, + "against_type": "Account", "against": expense_account, "cost_center": item_row.cost_center, "remarks": _("Rounding gain/loss Entry for Stock Transfer"), @@ -834,6 +838,7 @@ class StockController(AccountsController): "cost_center": cost_center, "debit": debit, "credit": credit, + "against_type": "Account", "against": against_account, "remarks": remarks, } From 6e1565c32c41a5d8bb5cffc4a74c861b0592ece6 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 20:08:42 +0530 Subject: [PATCH 007/112] fix: set against type in asset --- erpnext/assets/doctype/asset/asset.py | 2 ++ erpnext/assets/doctype/asset_repair/asset_repair.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 0dbed87cf2..a732dec08b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -606,6 +606,7 @@ class Asset(AccountsController): self.get_gl_dict( { "account": cwip_account, + "against_type": "Account", "against": fixed_asset_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, @@ -621,6 +622,7 @@ class Asset(AccountsController): self.get_gl_dict( { "account": fixed_asset_account, + "against_type": "Account", "against": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 7e95cb2a1b..30c0371ae6 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -241,6 +241,7 @@ class AssetRepair(AccountsController): "account": fixed_asset_account, "debit": self.repair_cost, "debit_in_account_currency": self.repair_cost, + "against_type": "Account", "against": pi_expense_account, "voucher_type": self.doctype, "voucher_no": self.name, @@ -260,6 +261,7 @@ class AssetRepair(AccountsController): "account": pi_expense_account, "credit": self.repair_cost, "credit_in_account_currency": self.repair_cost, + "against_type": "Account", "against": fixed_asset_account, "voucher_type": self.doctype, "voucher_no": self.name, @@ -294,6 +296,7 @@ class AssetRepair(AccountsController): "account": item.expense_account or default_expense_account, "credit": item.amount, "credit_in_account_currency": item.amount, + "against_type": "Account", "against": fixed_asset_account, "voucher_type": self.doctype, "voucher_no": self.name, @@ -311,6 +314,7 @@ class AssetRepair(AccountsController): "account": fixed_asset_account, "debit": item.amount, "debit_in_account_currency": item.amount, + "against_type": "Account", "against": item.expense_account or default_expense_account, "voucher_type": self.doctype, "voucher_no": self.name, From f705bf2efe7bf079bb9b8af200f4c8cadfddd4ee Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 20:10:01 +0530 Subject: [PATCH 008/112] fix: remove multiple accounts from against in capitalization --- .../asset_capitalization.py | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 662e4b983b..ad91edc038 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -390,6 +390,7 @@ class AssetCapitalization(StockController): self.get_gl_dict( { "account": account, + "against_type": "Account", "against": target_account, "cost_center": item_row.cost_center, "project": item_row.get("project") or self.get("project"), @@ -431,6 +432,7 @@ class AssetCapitalization(StockController): self.set_consumed_asset_status(asset) for gle in fixed_asset_gl_entries: + gle["against_type"] = "Account" gle["against"] = target_account gl_entries.append(self.get_gl_dict(gle, item=item)) target_against.add(gle["account"]) @@ -447,6 +449,7 @@ class AssetCapitalization(StockController): self.get_gl_dict( { "account": item_row.expense_account, + "against_type": "Account", "against": target_account, "cost_center": item_row.cost_center, "project": item_row.get("project") or self.get("project"), @@ -458,41 +461,44 @@ class AssetCapitalization(StockController): ) def get_gl_entries_for_target_item(self, gl_entries, target_against, precision): - if self.target_is_fixed_asset: - # Capitalization - gl_entries.append( - self.get_gl_dict( - { - "account": self.target_fixed_asset_account, - "against": ", ".join(target_against), - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": flt(self.total_value, precision), - "cost_center": self.get("cost_center"), - }, - item=self, - ) - ) - else: - # Target Stock Item - sle_list = self.sle_map.get(self.name) - for sle in sle_list: - stock_value_difference = flt(sle.stock_value_difference, precision) - account = self.warehouse_account[sle.warehouse]["account"] - + for target_account in target_against: + if self.target_is_fixed_asset: + # Capitalization gl_entries.append( self.get_gl_dict( { - "account": account, - "against": ", ".join(target_against), - "cost_center": self.cost_center, - "project": self.get("project"), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "debit": stock_value_difference, + "account": self.target_fixed_asset_account, + "against_type": "Account", + "against": target_account, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": flt(self.total_value, precision) / len(target_against), + "cost_center": self.get("cost_center"), }, - self.warehouse_account[sle.warehouse]["account_currency"], item=self, ) ) + else: + # Target Stock Item + sle_list = self.sle_map.get(self.name) + for sle in sle_list: + stock_value_difference = flt(sle.stock_value_difference, precision) + account = self.warehouse_account[sle.warehouse]["account"] + + gl_entries.append( + self.get_gl_dict( + { + "account": account, + "against_type": "Account", + "against": target_account, + "cost_center": self.cost_center, + "project": self.get("project"), + "remarks": self.get("remarks") or "Accounting Entry for Stock", + "debit": stock_value_difference / len(target_against), + }, + self.warehouse_account[sle.warehouse]["account_currency"], + item=self, + ) + ) def create_target_asset(self): total_target_asset_value = flt(self.total_value, self.precision("total_value")) From fcfdb9b5667755b2846e834d70d3f9b444743d2b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 3 Oct 2023 15:19:33 +0530 Subject: [PATCH 009/112] fix: purchase receipt tests --- erpnext/controllers/stock_controller.py | 3 ++- .../purchase_receipt/purchase_receipt.py | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 5a04f71002..d816780053 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -824,6 +824,7 @@ class StockController(AccountsController): credit, remarks, against_account, + against_type="Account", debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None, @@ -838,7 +839,7 @@ class StockController(AccountsController): "cost_center": cost_center, "debit": debit, "credit": credit, - "against_type": "Account", + "against_type": against_type, "against": against_account, "remarks": remarks, } diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 04eff54c43..836dda7811 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -449,6 +449,7 @@ class PurchaseReceipt(BuyingController): debit=0.0, credit=discrepancy_caused_by_exchange_rate_difference, remarks=remarks, + against_type="Supplier", against_account=self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, account_currency=credit_currency, @@ -462,6 +463,7 @@ class PurchaseReceipt(BuyingController): debit=discrepancy_caused_by_exchange_rate_difference, credit=0.0, remarks=remarks, + against_type="Supplier", against_account=self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, account_currency=credit_currency, @@ -666,7 +668,7 @@ class PurchaseReceipt(BuyingController): (self.name, expenses_included_in_valuation), ) - against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) + against_accounts = [d.account for d in gl_entries if flt(d.debit) > 0] total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked stock_rbnb = self.get_company_default("stock_received_but_not_billed") @@ -687,16 +689,17 @@ class PurchaseReceipt(BuyingController): ) amount_including_divisional_loss -= applicable_amount - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=tax.cost_center, - debit=0.0, - credit=applicable_amount, - remarks=self.remarks or _("Accounting Entry for Stock"), - against_account=against_account, - item=tax, - ) + for against in against_accounts: + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=tax.cost_center, + debit=0.0, + credit=flt(applicable_amount) / len(against_accounts), + remarks=self.remarks or _("Accounting Entry for Stock"), + against_account=against, + item=tax, + ) i += 1 From 952e8cf60ca138ddb5941221421d87c2d00800c4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 3 Oct 2023 15:55:34 +0530 Subject: [PATCH 010/112] fix: asset capitalization tests --- .../asset_capitalization/test_asset_capitalization.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 6e0a6856f5..1c445da20d 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -97,12 +97,12 @@ class TestAssetCapitalization(unittest.TestCase): # Test General Ledger Entries expected_gle = { - "_Test Fixed Asset - TCP1": 3000, + "_Test Fixed Asset - TCP1": 2999.99, "Expenses Included In Asset Valuation - TCP1": -1000, "_Test Warehouse - TCP1": -2000, + "Round Off - TCP1": 0.01, } actual_gle = get_actual_gle_dict(asset_capitalization.name) - self.assertEqual(actual_gle, expected_gle) # Test Stock Ledger Entries @@ -187,9 +187,10 @@ class TestAssetCapitalization(unittest.TestCase): # Test General Ledger Entries default_expense_account = frappe.db.get_value("Company", company, "default_expense_account") expected_gle = { - "_Test Fixed Asset - _TC": 3000, + "_Test Fixed Asset - _TC": 2999.99, "Expenses Included In Asset Valuation - _TC": -1000, default_expense_account: -2000, + "Round Off - _TC": 0.01, } actual_gle = get_actual_gle_dict(asset_capitalization.name) @@ -303,9 +304,10 @@ class TestAssetCapitalization(unittest.TestCase): # Test General Ledger Entries expected_gle = { - "_Test Warehouse - TCP1": consumed_asset_value_before_disposal, "_Test Accumulated Depreciations - TCP1": accumulated_depreciation, "_Test Fixed Asset - TCP1": -consumed_asset_purchase_value, + "_Test Warehouse - TCP1": consumed_asset_value_before_disposal - 0.01, + "Round Off - TCP1": 0.01, } actual_gle = get_actual_gle_dict(asset_capitalization.name) self.assertEqual(actual_gle, expected_gle) From aab5737ff3b95cd38de9240666854805bbb272a4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 11 Oct 2023 15:55:02 +0530 Subject: [PATCH 011/112] fix: make JV account against field a dynamic link --- .../journal_entry_account.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 3ba8cea94b..0e5dde0e3a 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -37,6 +37,7 @@ "col_break3", "is_advance", "user_remark", + "against_type", "against_account" ], "fields": [ @@ -249,12 +250,13 @@ }, { "fieldname": "against_account", - "fieldtype": "Text", + "fieldtype": "Dynamic Link", "hidden": 1, "label": "Against Account", "no_copy": 1, "oldfieldname": "against_account", "oldfieldtype": "Text", + "options": "against_type", "print_hide": 1 }, { @@ -279,12 +281,20 @@ "hidden": 1, "label": "Reference Detail No", "no_copy": 1 + }, + { + "fieldname": "against_type", + "fieldtype": "Link", + "hidden": 1, + "label": "Against Type", + "options": "DocType", + "print_hide": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-06-16 14:11:13.507807", + "modified": "2023-10-11 13:05:21.489496", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From 28445058ef4a9c9a6b4831f5223ffda2f1a4bfaf Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 11 Oct 2023 15:56:13 +0530 Subject: [PATCH 012/112] fix: split exchange gain loss account entries --- .../exchange_rate_revaluation.py | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 3b5698b118..bace7bfd30 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -467,6 +467,16 @@ class ExchangeRateRevaluation(Document): else "credit_in_account_currency" ) + balance_in_account_currency = flt( + abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency") + ) + difference_amount = ( + flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")) * balance_in_account_currency + ) + difference_amount -= ( + flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")) + * balance_in_account_currency + ) journal_entry_accounts.append( { "account": d.get("account"), @@ -503,28 +513,26 @@ class ExchangeRateRevaluation(Document): "reference_name": self.name, } ) + journal_entry_accounts.append( + { + "party_type": d.get("party_type"), + "party": d.get("party"), + "account": unrealized_exchange_gain_loss_account, + "balance": get_balance_on(unrealized_exchange_gain_loss_account), + "debit_in_account_currency": abs(difference_amount) if difference_amount < 0 else 0, + "credit_in_account_currency": difference_amount if difference_amount > 0 else 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "exchange_rate": 1, + "reference_type": "Exchange Rate Revaluation", + "reference_name": self.name, + } + ) journal_entry.set("accounts", journal_entry_accounts) journal_entry.set_amounts_in_company_currency() journal_entry.set_total_debit_credit() self.gain_loss_unbooked += journal_entry.difference - self.gain_loss_unbooked - journal_entry.append( - "accounts", - { - "account": unrealized_exchange_gain_loss_account, - "balance": get_balance_on(unrealized_exchange_gain_loss_account), - "debit_in_account_currency": abs(self.gain_loss_unbooked) - if self.gain_loss_unbooked < 0 - else 0, - "credit_in_account_currency": self.gain_loss_unbooked if self.gain_loss_unbooked > 0 else 0, - "cost_center": erpnext.get_default_cost_center(self.company), - "exchange_rate": 1, - "reference_type": "Exchange Rate Revaluation", - "reference_name": self.name, - }, - ) - journal_entry.set_amounts_in_company_currency() journal_entry.set_total_debit_credit() journal_entry.save() From 35d92abe731b5d3d6b091297defaafc76ecc2af5 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 11 Oct 2023 15:56:59 +0530 Subject: [PATCH 013/112] fix: remove multiple against account values from gle --- .../doctype/journal_entry/journal_entry.py | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 0616643a68..e4a89395d0 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -680,23 +680,39 @@ class JournalEntry(AccountsController): if self.voucher_type in ("Deferred Revenue", "Deferred Expense"): for d in self.get("accounts"): if d.reference_type == "Sales Invoice": - field = "customer" + against_type = "Customer" else: - field = "supplier" + against_type = "Supplier" - d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field) + d.against_type = against_type + d.against_account = frappe.db.get_value( + d.reference_type, d.reference_name, against_type.lower() + ) else: for d in self.get("accounts"): if flt(d.debit) > 0: - accounts_debited.append(d.party or d.account) + accounts_debited.append(d) if flt(d.credit) > 0: - accounts_credited.append(d.party or d.account) + accounts_credited.append(d) 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))) + if d.exchange_rate != 1.0: + d.against_type = d.party_type + d.against_account = d.party + elif flt(d.debit) > 0: + for acc in accounts_credited: + if acc.party == d.party: + d.against_type = "Account" + d.against_account = d.account + d.party_type = "" + d.party = "" + elif flt(d.credit) > 0: + for acc in accounts_debited: + if acc.party == d.party: + d.against_type = "Account" + d.against_account = d.account + d.party_type = "" + d.party = "" def validate_debit_credit_amount(self): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): @@ -906,7 +922,7 @@ class JournalEntry(AccountsController): "party_type": d.party_type, "due_date": self.due_date, "party": d.party, - "against_type": "Account", + "against_type": d.against_type, "against": d.against_account, "debit": flt(d.debit, d.precision("debit")), "credit": flt(d.credit, d.precision("credit")), From 3d00d74fed4ce065118bedce886972bb6c43b614 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 16:45:56 +0530 Subject: [PATCH 014/112] fix: combine jv entries for rate revaluation --- .../exchange_rate_revaluation.py | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index bace7bfd30..3b5698b118 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -467,16 +467,6 @@ class ExchangeRateRevaluation(Document): else "credit_in_account_currency" ) - balance_in_account_currency = flt( - abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency") - ) - difference_amount = ( - flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")) * balance_in_account_currency - ) - difference_amount -= ( - flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")) - * balance_in_account_currency - ) journal_entry_accounts.append( { "account": d.get("account"), @@ -513,26 +503,28 @@ class ExchangeRateRevaluation(Document): "reference_name": self.name, } ) - journal_entry_accounts.append( - { - "party_type": d.get("party_type"), - "party": d.get("party"), - "account": unrealized_exchange_gain_loss_account, - "balance": get_balance_on(unrealized_exchange_gain_loss_account), - "debit_in_account_currency": abs(difference_amount) if difference_amount < 0 else 0, - "credit_in_account_currency": difference_amount if difference_amount > 0 else 0, - "cost_center": erpnext.get_default_cost_center(self.company), - "exchange_rate": 1, - "reference_type": "Exchange Rate Revaluation", - "reference_name": self.name, - } - ) journal_entry.set("accounts", journal_entry_accounts) journal_entry.set_amounts_in_company_currency() journal_entry.set_total_debit_credit() self.gain_loss_unbooked += journal_entry.difference - self.gain_loss_unbooked + journal_entry.append( + "accounts", + { + "account": unrealized_exchange_gain_loss_account, + "balance": get_balance_on(unrealized_exchange_gain_loss_account), + "debit_in_account_currency": abs(self.gain_loss_unbooked) + if self.gain_loss_unbooked < 0 + else 0, + "credit_in_account_currency": self.gain_loss_unbooked if self.gain_loss_unbooked > 0 else 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "exchange_rate": 1, + "reference_type": "Exchange Rate Revaluation", + "reference_name": self.name, + }, + ) + journal_entry.set_amounts_in_company_currency() journal_entry.set_total_debit_credit() journal_entry.save() From ff0343d2cc9e1818beaf4047b8232183858d09d6 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 16:46:43 +0530 Subject: [PATCH 015/112] fix: auto separate against accounts --- .../doctype/journal_entry/journal_entry.py | 147 +++++++++++------- 1 file changed, 95 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index e4a89395d0..1b7b82bafc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -676,7 +676,6 @@ class JournalEntry(AccountsController): ) def set_against_account(self): - accounts_debited, accounts_credited = [], [] if self.voucher_type in ("Deferred Revenue", "Deferred Expense"): for d in self.get("accounts"): if d.reference_type == "Sales Invoice": @@ -689,30 +688,40 @@ class JournalEntry(AccountsController): d.reference_type, d.reference_name, against_type.lower() ) else: - for d in self.get("accounts"): - if flt(d.debit) > 0: - accounts_debited.append(d) - if flt(d.credit) > 0: - accounts_credited.append(d) + self.get_against_accounts() - for d in self.get("accounts"): - if d.exchange_rate != 1.0: - d.against_type = d.party_type - d.against_account = d.party - elif flt(d.debit) > 0: - for acc in accounts_credited: - if acc.party == d.party: - d.against_type = "Account" - d.against_account = d.account - d.party_type = "" - d.party = "" - elif flt(d.credit) > 0: - for acc in accounts_debited: - if acc.party == d.party: - d.against_type = "Account" - d.against_account = d.account - d.party_type = "" - d.party = "" + def get_against_accounts(self): + accounts_debited, accounts_credited, against_accounts = [], [], [] + split_account = {} + separate_against_account_entries = 1 + for d in self.get("accounts"): + if flt(d.debit) > 0 or flt(d.debit_in_account_currency) > 0: + accounts_debited.append(d) + elif flt(d.credit) > 0 or flt(d.credit_in_account_currency) > 0: + accounts_credited.append(d) + + if d.against_account: + separate_against_account_entries = 0 + break + + if separate_against_account_entries: + if len(accounts_credited) > 1 and len(accounts_debited) > 1: + frappe.msgprint( + _( + "Unable to automatically determine {0} accounts. Set them up in the {1} table if needed.".format( + frappe.bold("against"), frappe.bold("Accounting Entries") + ) + ), + alert=True, + ) + elif len(accounts_credited) == 1: + against_accounts = accounts_debited + split_account = accounts_credited[0] + elif len(accounts_debited) == 1: + against_accounts = accounts_credited + split_account = accounts_debited[0] + + return separate_against_account_entries, against_accounts, split_account def validate_debit_credit_amount(self): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): @@ -909,41 +918,75 @@ class JournalEntry(AccountsController): def build_gl_map(self): gl_map = [] + separate_against_account_entries, against_accounts, split_account = self.get_against_accounts() for d in self.get("accounts"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): r = [d.user_remark, self.remark] r = [x for x in r if x] remarks = "\n".join(r) - gl_map.append( - self.get_gl_dict( - { - "account": d.account, - "party_type": d.party_type, - "due_date": self.due_date, - "party": d.party, - "against_type": d.against_type, - "against": d.against_account, - "debit": flt(d.debit, d.precision("debit")), - "credit": flt(d.credit, d.precision("credit")), - "account_currency": d.account_currency, - "debit_in_account_currency": flt( - d.debit_in_account_currency, d.precision("debit_in_account_currency") - ), - "credit_in_account_currency": flt( - d.credit_in_account_currency, d.precision("credit_in_account_currency") - ), - "against_voucher_type": d.reference_type, - "against_voucher": d.reference_name, - "remarks": remarks, - "voucher_detail_no": d.reference_detail_no, - "cost_center": d.cost_center, - "project": d.project, - "finance_book": self.finance_book, - }, - item=d, - ) + gl_dict = self.get_gl_dict( + { + "account": d.account, + "party_type": d.party_type, + "due_date": self.due_date, + "party": d.party, + "debit": flt(d.debit, d.precision("debit")), + "credit": flt(d.credit, d.precision("credit")), + "account_currency": d.account_currency, + "debit_in_account_currency": flt( + d.debit_in_account_currency, d.precision("debit_in_account_currency") + ), + "credit_in_account_currency": flt( + d.credit_in_account_currency, d.precision("credit_in_account_currency") + ), + "against_voucher_type": d.reference_type, + "against_voucher": d.reference_name, + "remarks": remarks, + "voucher_detail_no": d.reference_detail_no, + "cost_center": d.cost_center, + "project": d.project, + "finance_book": self.finance_book, + }, + item=d, ) + + if not separate_against_account_entries: + gl_dict.update({"against_type": d.against_type, "against_account": d.against_account}) + gl_map.append(gl_dict) + + elif d in against_accounts: + gl_dict.update( + { + "against_type": split_account.party_type or "Account", + "against_account": split_account.party or split_account.account, + } + ) + gl_map.append(gl_dict) + + else: + for against_account in against_accounts: + against_account = against_account.as_dict() + debit = against_account.credit or against_account.credit_in_account_currency + credit = against_account.debit or against_account.debit_in_account_currency + gl_dict = gl_dict.copy() + gl_dict.update( + { + "against_type": against_account.party_type or "Account", + "against": against_account.party or against_account.account, + "debit": flt(debit, d.precision("debit")), + "credit": flt(credit, d.precision("credit")), + "account_currency": d.account_currency, + "debit_in_account_currency": flt( + debit / d.exchange_rate, d.precision("debit_in_account_currency") + ), + "credit_in_account_currency": flt( + credit / d.exchange_rate, d.precision("credit_in_account_currency") + ), + } + ) + gl_map.append(gl_dict) + return gl_map def make_gl_entries(self, cancel=0, adv_adj=0): From 758ec720deb78507f4972b8b71cb3731f6ee43c4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 17:14:55 +0530 Subject: [PATCH 016/112] fix: remove join for against in purchase receipt --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6874044a6c..72a2465526 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -411,7 +411,7 @@ class PurchaseReceipt(BuyingController): account_currency=account_currency, item=item, ) - + self.add_gl_entry( gl_entries=gl_entries, account=self.get_company_default("exchange_gain_loss_account"), @@ -684,7 +684,7 @@ class PurchaseReceipt(BuyingController): # Backward compatibility: # and charges added via Landed Cost Voucher, # post valuation related charges on "Stock Received But Not Billed" - against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) + against_accounts = [d.account for d in gl_entries if flt(d.debit) > 0] total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked stock_rbnb = ( From a1f8595a6a78ef94d1c82fefc44f4294d38cb856 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 17:20:11 +0530 Subject: [PATCH 017/112] chore: linting issue --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 99b9c4ef44..cf89b333c6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -920,7 +920,7 @@ class PurchaseInvoice(BuyingController): item=item, ) ) - + # update gross amount of asset bought through this document assets = frappe.db.get_all( "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} From e845b63228baffb3165aee6c24f038477cfb71bf Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 20:37:05 +0530 Subject: [PATCH 018/112] fix: single dr cr entries --- .../doctype/journal_entry/journal_entry.py | 79 ++++++++++++------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1b7b82bafc..da6c8cf7cd 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -691,21 +691,13 @@ class JournalEntry(AccountsController): self.get_against_accounts() def get_against_accounts(self): - accounts_debited, accounts_credited, against_accounts = [], [], [] - split_account = {} - separate_against_account_entries = 1 - for d in self.get("accounts"): - if flt(d.debit) > 0 or flt(d.debit_in_account_currency) > 0: - accounts_debited.append(d) - elif flt(d.credit) > 0 or flt(d.credit_in_account_currency) > 0: - accounts_credited.append(d) + self.against_accounts = [] + self.split_account = {} + self.get_debited_credited_accounts() - if d.against_account: - separate_against_account_entries = 0 - break - - if separate_against_account_entries: - if len(accounts_credited) > 1 and len(accounts_debited) > 1: + if self.separate_against_account_entries: + no_of_credited_acc, no_of_debited_acc = len(self.accounts_credited), len(self.accounts_debited) + if no_of_credited_acc > 1 and no_of_debited_acc > 1: frappe.msgprint( _( "Unable to automatically determine {0} accounts. Set them up in the {1} table if needed.".format( @@ -714,14 +706,37 @@ class JournalEntry(AccountsController): ), alert=True, ) - elif len(accounts_credited) == 1: - against_accounts = accounts_debited - split_account = accounts_credited[0] - elif len(accounts_debited) == 1: - against_accounts = accounts_credited - split_account = accounts_debited[0] + elif no_of_credited_acc == 1 and no_of_debited_acc == 1: + self.set_against_accounts_for_single_dr_cr() + self.separate_against_account_entries = 0 + elif no_of_credited_acc == 1: + self.against_accounts = self.accounts_debited + self.split_account = self.accounts_credited[0] + elif no_of_debited_acc == 1: + self.against_accounts = self.accounts_credited + self.split_account = self.accounts_debited[0] - return separate_against_account_entries, against_accounts, split_account + def get_debited_credited_accounts(self): + self.accounts_debited, self.accounts_credited = [], [] + self.separate_against_account_entries = 1 + for d in self.get("accounts"): + if has_debit_amount(d): + self.accounts_debited.append(d) + elif has_credit_amount(d): + self.accounts_credited.append(d) + + if d.against_account: + self.separate_against_account_entries = 0 + break + + def set_against_accounts_for_single_dr_cr(self): + for d in self.accounts: + if has_debit_amount(d): + against_account = self.accounts_credited[0] + elif has_credit_amount(d): + against_account = self.accounts_debited[0] + d.against_type = against_account.party_type or "Account" + d.against_account = against_account.party or against_account.account def validate_debit_credit_amount(self): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): @@ -918,7 +933,7 @@ class JournalEntry(AccountsController): def build_gl_map(self): gl_map = [] - separate_against_account_entries, against_accounts, split_account = self.get_against_accounts() + self.get_against_accounts() for d in self.get("accounts"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): r = [d.user_remark, self.remark] @@ -951,21 +966,21 @@ class JournalEntry(AccountsController): item=d, ) - if not separate_against_account_entries: - gl_dict.update({"against_type": d.against_type, "against_account": d.against_account}) + if not self.separate_against_account_entries: + gl_dict.update({"against_type": d.against_type, "against": d.against_account}) gl_map.append(gl_dict) - elif d in against_accounts: + elif d in self.against_accounts: gl_dict.update( { - "against_type": split_account.party_type or "Account", - "against_account": split_account.party or split_account.account, + "against_type": self.split_account.get("party_type") or "Account", + "against": self.split_account.get("party") or self.split_account.get("account"), } ) gl_map.append(gl_dict) else: - for against_account in against_accounts: + for against_account in self.against_accounts: against_account = against_account.as_dict() debit = against_account.credit or against_account.credit_in_account_currency credit = against_account.debit or against_account.debit_in_account_currency @@ -1611,3 +1626,11 @@ def make_reverse_journal_entry(source_name, target_doc=None): ) return doclist + + +def has_credit_amount(account): + return flt(account.credit) > 0 or flt(account.credit_in_account_currency) > 0 + + +def has_debit_amount(account): + return flt(account.debit) > 0 or flt(account.debit_in_account_currency) > 0 From 5ce395a60a0ebace32382343110e0ceccac4880e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 7 Nov 2023 12:52:49 +0530 Subject: [PATCH 019/112] fix: purchase receipt test --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 72a2465526..1fcde44053 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -406,6 +406,7 @@ class PurchaseReceipt(BuyingController): debit=0.0, credit=discrepancy_caused_by_exchange_rate_difference, remarks=remarks, + against_type="Supplier", against_account=self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, account_currency=account_currency, @@ -419,6 +420,7 @@ class PurchaseReceipt(BuyingController): debit=discrepancy_caused_by_exchange_rate_difference, credit=0.0, remarks=remarks, + against_type="Supplier", against_account=self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, account_currency=account_currency, From ea4b6ff27bc9406c5c2abd4519938bb7942aa3f4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 7 Nov 2023 13:05:38 +0530 Subject: [PATCH 020/112] fix: remove string from against field in loyalty point entry --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 8033dbff09..a059c32dfa 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -7,7 +7,7 @@ from frappe import _, msgprint, throw from frappe.contacts.doctype.address.address import get_address_display from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values -from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate +from frappe.utils import add_days, cint, flt, formatdate, get_link_to_form, getdate, nowdate import erpnext from erpnext.accounts.deferred_revenue import validate_service_stop_date @@ -1239,9 +1239,7 @@ class SalesInvoice(SellingController): "party_type": "Customer", "party": self.customer, "against_type": "Account", - "against": "Expense account - " - + cstr(self.loyalty_redemption_account) - + " for the Loyalty Program", + "against": self.loyalty_redemption_account, "credit": self.loyalty_amount, "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, From 68c6ad6036ad9ce1f40ea4f20568988c5bbe5deb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 7 Nov 2023 15:31:52 +0530 Subject: [PATCH 021/112] fix: test for balance in forex account after revaluation --- .../doctype/journal_entry/journal_entry.py | 24 +++++++------------ erpnext/accounts/general_ledger.py | 4 ++-- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index da6c8cf7cd..27d14c2e3f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -706,7 +706,7 @@ class JournalEntry(AccountsController): ), alert=True, ) - elif no_of_credited_acc == 1 and no_of_debited_acc == 1: + elif no_of_credited_acc <= 1 and no_of_debited_acc <= 1: self.set_against_accounts_for_single_dr_cr() self.separate_against_account_entries = 0 elif no_of_credited_acc == 1: @@ -720,9 +720,9 @@ class JournalEntry(AccountsController): self.accounts_debited, self.accounts_credited = [], [] self.separate_against_account_entries = 1 for d in self.get("accounts"): - if has_debit_amount(d): + if flt(d.debit) > 0: self.accounts_debited.append(d) - elif has_credit_amount(d): + elif flt(d.credit) > 0: self.accounts_credited.append(d) if d.against_account: @@ -730,13 +730,15 @@ class JournalEntry(AccountsController): break def set_against_accounts_for_single_dr_cr(self): + against_account = None for d in self.accounts: - if has_debit_amount(d): + if flt(d.debit) > 0: against_account = self.accounts_credited[0] - elif has_credit_amount(d): + elif flt(d.credit) > 0: against_account = self.accounts_debited[0] - d.against_type = against_account.party_type or "Account" - d.against_account = against_account.party or against_account.account + if against_account: + d.against_type = against_account.party_type or "Account" + d.against_account = against_account.party or against_account.account def validate_debit_credit_amount(self): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): @@ -1626,11 +1628,3 @@ def make_reverse_journal_entry(source_name, target_doc=None): ) return doclist - - -def has_credit_amount(account): - return flt(account.credit) > 0 or flt(account.credit_in_account_currency) > 0 - - -def has_debit_amount(account): - return flt(account.debit) > 0 or flt(account.debit_in_account_currency) > 0 diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 70a8470614..2f721c8013 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -356,7 +356,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): process_debit_credit_difference(gl_map) if gl_map: - check_freezing_date(gl_map[0]["posting_date"], adv_adj) + # check_freezing_date(gl_map[0]["posting_date"], adv_adj) is_opening = any(d.get("is_opening") == "Yes" for d in gl_map) if gl_map[0]["voucher_type"] != "Period Closing Voucher": validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"]) @@ -593,7 +593,7 @@ def make_reverse_gl_entries( partial_cancel=partial_cancel, ) validate_accounting_period(gl_entries) - check_freezing_date(gl_entries[0]["posting_date"], adv_adj) + # check_freezing_date(gl_entries[0]["posting_date"], adv_adj) is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries) validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"]) From 291a4991246b5a53491066eb46efda0de2a8002f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 00:46:52 +0530 Subject: [PATCH 022/112] fix: split expected jv entries for scrap asset --- erpnext/accounts/general_ledger.py | 4 ++-- erpnext/assets/doctype/asset/test_asset.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 2f721c8013..70a8470614 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -356,7 +356,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): process_debit_credit_difference(gl_map) if gl_map: - # check_freezing_date(gl_map[0]["posting_date"], adv_adj) + check_freezing_date(gl_map[0]["posting_date"], adv_adj) is_opening = any(d.get("is_opening") == "Yes" for d in gl_map) if gl_map[0]["voucher_type"] != "Period Closing Voucher": validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"]) @@ -593,7 +593,7 @@ def make_reverse_gl_entries( partial_cancel=partial_cancel, ) validate_accounting_period(gl_entries) - # check_freezing_date(gl_entries[0]["posting_date"], adv_adj) + check_freezing_date(gl_entries[0]["posting_date"], adv_adj) is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries) validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"]) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 9e3ec6faa8..536845ed26 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -256,7 +256,16 @@ class TestAsset(AssetSetup): flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")), 0.0, ), - ("_Test Fixed Asset - _TC", 0.0, 100000.0), + ( + "_Test Fixed Asset - _TC", + 0.0, + flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")), + ), + ( + "_Test Fixed Asset - _TC", + 0.0, + flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")), + ), ( "_Test Gain/Loss on Asset Disposal - _TC", flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")), @@ -267,7 +276,7 @@ class TestAsset(AssetSetup): gle = frappe.db.sql( """select account, debit, credit from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no = %s - order by account""", + order by account, credit""", asset.journal_entry_for_scrap, ) self.assertSequenceEqual(gle, expected_gle) From 5f5d75a0bbce2e28411ca79e7eaeca669933692a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 11:19:45 +0530 Subject: [PATCH 023/112] chore: linting issues --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 27d14c2e3f..1c737fc543 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -700,10 +700,8 @@ class JournalEntry(AccountsController): if no_of_credited_acc > 1 and no_of_debited_acc > 1: frappe.msgprint( _( - "Unable to automatically determine {0} accounts. Set them up in the {1} table if needed.".format( - frappe.bold("against"), frappe.bold("Accounting Entries") - ) - ), + "Unable to automatically determine {0} accounts. Set them up in the {1} table if needed." + ).format(frappe.bold("against"), frappe.bold("Accounting Entries")), alert=True, ) elif no_of_credited_acc <= 1 and no_of_debited_acc <= 1: From f9c88ea7bc96837ece6c477e4dc9a324985d98ac Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 12:55:41 +0530 Subject: [PATCH 024/112] refactor: keep old against fields intact --- .../accounts/doctype/gl_entry/gl_entry.json | 23 ++++++++++++------- .../journal_entry_account.json | 23 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 1adeaa54d8..16df40f435 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -19,6 +19,7 @@ "credit_in_account_currency", "against_type", "against", + "against_link", "against_voucher_type", "against_voucher", "voucher_type", @@ -137,13 +138,19 @@ "options": "DocType" }, { - "fieldname": "against", - "fieldtype": "Dynamic Link", - "in_filter": 1, - "label": "Against", - "oldfieldname": "against", - "oldfieldtype": "Text", - "options": "against_type" + "fieldname": "against", + "fieldtype": "Text", + "in_filter": 1, + "label": "Against", + "oldfieldname": "against", + "oldfieldtype": "Text" + }, + { + "fieldname": "against_link", + "fieldtype": "Dynamic Link", + "in_filter": 1, + "label": "Against", + "options": "against_type" }, { "fieldname": "against_voucher_type", @@ -295,7 +302,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-09-25 12:03:23.031733", + "modified": "2023-11-08 12:20:23.031733", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 0e5dde0e3a..8d8c83751b 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -38,7 +38,8 @@ "is_advance", "user_remark", "against_type", - "against_account" + "against_account", + "against_account_link" ], "fields": [ { @@ -249,15 +250,21 @@ "print_hide": 1 }, { - "fieldname": "against_account", + "fieldname": "against_account", + "fieldtype": "Text", + "hidden": 1, + "label": "Against Account", + "no_copy": 1, + "oldfieldname": "against_account", + "oldfieldtype": "Text", + "print_hide": 1 + }, + { + "fieldname": "against_account_link", "fieldtype": "Dynamic Link", - "hidden": 1, "label": "Against Account", "no_copy": 1, - "oldfieldname": "against_account", - "oldfieldtype": "Text", - "options": "against_type", - "print_hide": 1 + "options": "against_type" }, { "collapsible": 1, @@ -294,7 +301,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-11 13:05:21.489496", + "modified": "2023-11-08 12:20:21.489496", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From 09439334cae6a7306dad8e54a9f81f9ffa483f8b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 12:57:00 +0530 Subject: [PATCH 025/112] refactor: use both fields to store against values --- erpnext/accounts/deferred_revenue.py | 2 ++ .../invoice_discounting.py | 2 ++ .../doctype/journal_entry/journal_entry.py | 17 +++++++++++---- .../doctype/payment_entry/payment_entry.py | 6 ++++++ .../purchase_invoice/purchase_invoice.py | 21 +++++++++++++++++++ .../doctype/sales_invoice/sales_invoice.py | 14 +++++++++++++ erpnext/assets/doctype/asset/asset.py | 2 ++ .../asset_capitalization.py | 5 +++++ .../doctype/asset_repair/asset_repair.py | 4 ++++ erpnext/controllers/accounts_controller.py | 4 ++++ erpnext/controllers/stock_controller.py | 5 +++++ .../regional/united_arab_emirates/utils.py | 1 + .../stock/doctype/stock_entry/stock_entry.py | 2 ++ 13 files changed, 81 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 00d5ea3245..c2188c0da1 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -521,6 +521,7 @@ def make_gl_entries( "account": credit_account, "against_type": against_type, "against": against, + "against_link": against, "credit": base_amount, "credit_in_account_currency": amount, "cost_center": cost_center, @@ -541,6 +542,7 @@ def make_gl_entries( "account": debit_account, "against_type": against_type, "against": against, + "against_link": against, "debit": base_amount, "debit_in_account_currency": amount, "cost_center": cost_center, diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 744dff4670..ebfa0de3ae 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -127,6 +127,7 @@ class InvoiceDiscounting(AccountsController): "party": d.customer, "against_type": "Account", "against": self.accounts_receivable_credit, + "against_link": self.accounts_receivable_credit, "credit": outstanding_in_company_currency, "credit_in_account_currency": outstanding_in_company_currency if inv.party_account_currency == company_currency @@ -148,6 +149,7 @@ class InvoiceDiscounting(AccountsController): "party": d.customer, "against_type": "Account", "against": inv.debit_to, + "against_link": inv.debit_to, "debit": outstanding_in_company_currency, "debit_in_account_currency": outstanding_in_company_currency if ar_credit_account_currency == company_currency diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1c737fc543..9f76d9df19 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -683,10 +683,10 @@ class JournalEntry(AccountsController): else: against_type = "Supplier" + against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower()) d.against_type = against_type - d.against_account = frappe.db.get_value( - d.reference_type, d.reference_name, against_type.lower() - ) + d.against_account = against_account + d.against_account_link = against_account else: self.get_against_accounts() @@ -737,6 +737,7 @@ class JournalEntry(AccountsController): if against_account: d.against_type = against_account.party_type or "Account" d.against_account = against_account.party or against_account.account + d.against_account_link = against_account.party or against_account.account def validate_debit_credit_amount(self): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): @@ -967,7 +968,13 @@ class JournalEntry(AccountsController): ) if not self.separate_against_account_entries: - gl_dict.update({"against_type": d.against_type, "against": d.against_account}) + gl_dict.update( + { + "against_type": d.against_type, + "against": d.against_account, + "against_link": d.against_account, + } + ) gl_map.append(gl_dict) elif d in self.against_accounts: @@ -975,6 +982,7 @@ class JournalEntry(AccountsController): { "against_type": self.split_account.get("party_type") or "Account", "against": self.split_account.get("party") or self.split_account.get("account"), + "against_link": self.split_account.get("party") or self.split_account.get("account"), } ) gl_map.append(gl_dict) @@ -989,6 +997,7 @@ class JournalEntry(AccountsController): { "against_type": against_account.party_type or "Account", "against": against_account.party or against_account.account, + "against_link": against_account.party or against_account.account, "debit": flt(debit, d.precision("debit")), "credit": flt(credit, d.precision("credit")), "account_currency": d.account_currency, diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ff558b2f48..ff8695f770 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1078,6 +1078,7 @@ class PaymentEntry(AccountsController): "party": self.party, "against_type": "Account", "against": against_account, + "against_link": against_account, "account_currency": self.party_account_currency, "cost_center": self.cost_center, }, @@ -1237,6 +1238,7 @@ class PaymentEntry(AccountsController): "account_currency": self.paid_from_account_currency, "against_type": self.party_type if self.payment_type == "Pay" else "Account", "against": self.party if self.payment_type == "Pay" else self.paid_to, + "against_link": self.party if self.payment_type == "Pay" else self.paid_to, "credit_in_account_currency": self.paid_amount, "credit": self.base_paid_amount, "cost_center": self.cost_center, @@ -1253,6 +1255,7 @@ class PaymentEntry(AccountsController): "account_currency": self.paid_to_account_currency, "against_type": self.party_type if self.payment_type == "Receive" else "Account", "against": self.party if self.payment_type == "Receive" else self.paid_from, + "against_link": self.party if self.payment_type == "Receive" else self.paid_from, "debit_in_account_currency": self.received_amount, "debit": self.base_received_amount, "cost_center": self.cost_center, @@ -1287,6 +1290,7 @@ class PaymentEntry(AccountsController): "account": d.account_head, "against_type": against_type, "against": against, + "against_link": against, dr_or_cr: tax_amount, dr_or_cr + "_in_account_currency": base_tax_amount if account_currency == self.company_currency @@ -1313,6 +1317,7 @@ class PaymentEntry(AccountsController): "account": payment_account, "against_type": against_type, "against": against, + "against_link": against, rev_dr_or_cr: tax_amount, rev_dr_or_cr + "_in_account_currency": base_tax_amount if account_currency == self.company_currency @@ -1339,6 +1344,7 @@ class PaymentEntry(AccountsController): "account_currency": account_currency, "against_type": self.party_type or "Account", "against": self.party or self.paid_from, + "against_link": self.party or self.paid_from, "debit_in_account_currency": d.amount, "debit": d.amount, "cost_center": d.cost_center, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index cf89b333c6..53c131a507 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -643,6 +643,7 @@ class PurchaseInvoice(BuyingController): "due_date": self.due_date, "against_type": "Account", "against": self.against_expense_account, + "against_link": self.against_expense_account, "credit": base_grand_total, "credit_in_account_currency": base_grand_total if self.party_account_currency == self.company_currency @@ -717,6 +718,7 @@ class PurchaseInvoice(BuyingController): "account": warehouse_account[item.warehouse]["account"], "against_type": "Account", "against": warehouse_account[item.from_warehouse]["account"], + "against_link": warehouse_account[item.from_warehouse]["account"], "cost_center": item.cost_center, "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -738,6 +740,7 @@ class PurchaseInvoice(BuyingController): "account": warehouse_account[item.from_warehouse]["account"], "against_type": "Account", "against": warehouse_account[item.warehouse]["account"], + "against_link": warehouse_account[item.warehouse]["account"], "cost_center": item.cost_center, "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -756,6 +759,7 @@ class PurchaseInvoice(BuyingController): "account": item.expense_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "debit": flt(item.base_net_amount, item.precision("base_net_amount")), "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "cost_center": item.cost_center, @@ -774,6 +778,7 @@ class PurchaseInvoice(BuyingController): "account": item.expense_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "debit": warehouse_debit_amount, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "cost_center": item.cost_center, @@ -794,6 +799,7 @@ class PurchaseInvoice(BuyingController): "account": account, "against_type": "Account", "against": item.expense_account, + "against_link": item.expense_account, "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(amount["base_amount"]), @@ -815,6 +821,7 @@ class PurchaseInvoice(BuyingController): "account": supplier_warehouse_account, "against_type": "Account", "against": item.expense_account, + "against_link": item.expense_account, "cost_center": item.cost_center, "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -871,6 +878,7 @@ class PurchaseInvoice(BuyingController): "account": expense_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "debit": amount, "cost_center": item.cost_center, "project": item.project or self.project, @@ -898,6 +906,7 @@ class PurchaseInvoice(BuyingController): "account": expense_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "debit": discrepancy_caused_by_exchange_rate_difference, "cost_center": item.cost_center, "project": item.project or self.project, @@ -912,6 +921,7 @@ class PurchaseInvoice(BuyingController): "account": self.get_company_default("exchange_gain_loss_account"), "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "credit": discrepancy_caused_by_exchange_rate_difference, "cost_center": item.cost_center, "project": item.project or self.project, @@ -958,6 +968,7 @@ class PurchaseInvoice(BuyingController): "account": stock_rbnb, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or _("Accounting Entry for Stock"), "cost_center": self.cost_center, @@ -1007,6 +1018,7 @@ class PurchaseInvoice(BuyingController): "account": cost_of_goods_sold_account, "against_type": "Account", "against": item.expense_account, + "against_link": item.expense_account, "debit": stock_adjustment_amt, "remarks": self.get("remarks") or _("Stock Adjustment"), "cost_center": item.cost_center, @@ -1038,6 +1050,7 @@ class PurchaseInvoice(BuyingController): "account": tax.account_head, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, dr_or_cr: base_amount, dr_or_cr + "_in_account_currency": base_amount if account_currency == self.company_currency @@ -1088,6 +1101,7 @@ class PurchaseInvoice(BuyingController): "against_type": "Supplier", "cost_center": tax.cost_center, "against": self.supplier, + "against_link": self.supplier, "credit": applicable_amount, "remarks": self.remarks or _("Accounting Entry for Stock"), }, @@ -1107,6 +1121,7 @@ class PurchaseInvoice(BuyingController): "cost_center": tax.cost_center, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "credit": valuation_tax[tax.name], "remarks": self.remarks or _("Accounting Entry for Stock"), }, @@ -1123,6 +1138,7 @@ class PurchaseInvoice(BuyingController): "account": self.unrealized_profit_loss_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "credit": flt(self.total_taxes_and_charges), "credit_in_account_currency": flt(self.base_total_taxes_and_charges), "cost_center": self.cost_center, @@ -1145,6 +1161,7 @@ class PurchaseInvoice(BuyingController): "party": self.supplier, "against_type": "Account", "against": self.cash_bank_account, + "against_link": self.cash_bank_account, "debit": self.base_paid_amount, "debit_in_account_currency": self.base_paid_amount if self.party_account_currency == self.company_currency @@ -1167,6 +1184,7 @@ class PurchaseInvoice(BuyingController): "account": self.cash_bank_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "credit": self.base_paid_amount, "credit_in_account_currency": self.base_paid_amount if bank_account_currency == self.company_currency @@ -1192,6 +1210,7 @@ class PurchaseInvoice(BuyingController): "party": self.supplier, "against_type": "Account", "against": self.write_off_account, + "against_link": self.write_off_account, "debit": self.base_write_off_amount, "debit_in_account_currency": self.base_write_off_amount if self.party_account_currency == self.company_currency @@ -1213,6 +1232,7 @@ class PurchaseInvoice(BuyingController): "account": self.write_off_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "credit": flt(self.base_write_off_amount), "credit_in_account_currency": self.base_write_off_amount if write_off_account_currency == self.company_currency @@ -1241,6 +1261,7 @@ class PurchaseInvoice(BuyingController): "account": round_off_account, "against_type": "Supplier", "against": self.supplier, + "against_link": self.supplier, "debit_in_account_currency": self.rounding_adjustment, "debit": self.base_rounding_adjustment, "cost_center": round_off_cost_center diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index a059c32dfa..87b40c09bd 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1042,6 +1042,7 @@ class SalesInvoice(SellingController): "due_date": self.due_date, "against_type": "Account", "against": self.against_income_account, + "against_link": self.against_income_account, "debit": base_grand_total, "debit_in_account_currency": base_grand_total if self.party_account_currency == self.company_currency @@ -1072,6 +1073,7 @@ class SalesInvoice(SellingController): "account": tax.account_head, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")), "credit_in_account_currency": ( flt(base_amount, tax.precision("base_tax_amount_after_discount_amount")) @@ -1094,6 +1096,7 @@ class SalesInvoice(SellingController): "account": self.unrealized_profit_loss_account, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "debit": flt(self.total_taxes_and_charges), "debit_in_account_currency": flt(self.base_total_taxes_and_charges), "cost_center": self.cost_center, @@ -1163,6 +1166,7 @@ class SalesInvoice(SellingController): for gle in fixed_asset_gl_entries: gle["against_type"] = "Customer" gle["against"] = self.customer + gle["against_link"] = self.customer gl_entries.append(self.get_gl_dict(gle, item=item)) self.set_asset_status(asset) @@ -1185,6 +1189,7 @@ class SalesInvoice(SellingController): "account": income_account, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "credit": flt(base_amount, item.precision("base_net_amount")), "credit_in_account_currency": ( flt(base_amount, item.precision("base_net_amount")) @@ -1240,6 +1245,7 @@ class SalesInvoice(SellingController): "party": self.customer, "against_type": "Account", "against": self.loyalty_redemption_account, + "against_link": self.loyalty_redemption_account, "credit": self.loyalty_amount, "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, @@ -1255,6 +1261,7 @@ class SalesInvoice(SellingController): "cost_center": self.cost_center or self.loyalty_redemption_cost_center, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "debit": self.loyalty_amount, "remark": "Loyalty Points redeemed by the customer", }, @@ -1283,6 +1290,7 @@ class SalesInvoice(SellingController): "party": self.customer, "against_type": "Account", "against": payment_mode.account, + "against_link": payment_mode.account, "credit": payment_mode.base_amount, "credit_in_account_currency": payment_mode.base_amount if self.party_account_currency == self.company_currency @@ -1305,6 +1313,7 @@ class SalesInvoice(SellingController): "account": payment_mode.account, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "debit": payment_mode.base_amount, "debit_in_account_currency": payment_mode.base_amount if payment_mode_account_currency == self.company_currency @@ -1330,6 +1339,7 @@ class SalesInvoice(SellingController): "party": self.customer, "against_type": "Account", "against": self.account_for_change_amount, + "against_link": self.account_for_change_amount, "debit": flt(self.base_change_amount), "debit_in_account_currency": flt(self.base_change_amount) if self.party_account_currency == self.company_currency @@ -1352,6 +1362,7 @@ class SalesInvoice(SellingController): "account": self.account_for_change_amount, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "credit": self.base_change_amount, "cost_center": self.cost_center, }, @@ -1379,6 +1390,7 @@ class SalesInvoice(SellingController): "party": self.customer, "against_type": "Account", "against": self.write_off_account, + "against_link": self.write_off_account, "credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "credit_in_account_currency": ( flt(self.base_write_off_amount, self.precision("base_write_off_amount")) @@ -1400,6 +1412,7 @@ class SalesInvoice(SellingController): "account": self.write_off_account, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "debit_in_account_currency": ( flt(self.base_write_off_amount, self.precision("base_write_off_amount")) @@ -1429,6 +1442,7 @@ class SalesInvoice(SellingController): "account": round_off_account, "against_type": "Customer", "against": self.customer, + "against_link": self.customer, "credit_in_account_currency": flt( self.rounding_adjustment, self.precision("rounding_adjustment") ), diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 32518a109a..8908d8e5d0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -610,6 +610,7 @@ class Asset(AccountsController): "account": cwip_account, "against_type": "Account", "against": fixed_asset_account, + "against_link": fixed_asset_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, "credit": self.purchase_receipt_amount, @@ -626,6 +627,7 @@ class Asset(AccountsController): "account": fixed_asset_account, "against_type": "Account", "against": cwip_account, + "against_link": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, "debit": self.purchase_receipt_amount, diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 229c16d18a..31aee3386d 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -432,6 +432,7 @@ class AssetCapitalization(StockController): "account": account, "against_type": "Account", "against": target_account, + "against_link": target_account, "cost_center": item_row.cost_center, "project": item_row.get("project") or self.get("project"), "remarks": self.get("remarks") or "Accounting Entry for Stock", @@ -474,6 +475,7 @@ class AssetCapitalization(StockController): for gle in fixed_asset_gl_entries: gle["against_type"] = "Account" gle["against"] = target_account + gle["against_link"] = target_account gl_entries.append(self.get_gl_dict(gle, item=item)) target_against.add(gle["account"]) @@ -491,6 +493,7 @@ class AssetCapitalization(StockController): "account": item_row.expense_account, "against_type": "Account", "against": target_account, + "against_link": target_account, "cost_center": item_row.cost_center, "project": item_row.get("project") or self.get("project"), "remarks": self.get("remarks") or "Accounting Entry for Stock", @@ -510,6 +513,7 @@ class AssetCapitalization(StockController): "account": self.target_fixed_asset_account, "against_type": "Account", "against": target_account, + "against_link": target_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": flt(self.total_value, precision) / len(target_against), "cost_center": self.get("cost_center"), @@ -530,6 +534,7 @@ class AssetCapitalization(StockController): "account": account, "against_type": "Account", "against": target_account, + "against_link": target_account, "cost_center": self.cost_center, "project": self.get("project"), "remarks": self.get("remarks") or "Accounting Entry for Stock", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 65d2f8ef18..0b072b2a69 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -243,6 +243,7 @@ class AssetRepair(AccountsController): "debit_in_account_currency": self.repair_cost, "against_type": "Account", "against": pi_expense_account, + "against_link": pi_expense_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, @@ -263,6 +264,7 @@ class AssetRepair(AccountsController): "credit_in_account_currency": self.repair_cost, "against_type": "Account", "against": fixed_asset_account, + "against_link": fixed_asset_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, @@ -298,6 +300,7 @@ class AssetRepair(AccountsController): "credit_in_account_currency": item.amount, "against_type": "Account", "against": fixed_asset_account, + "against_link": fixed_asset_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, @@ -316,6 +319,7 @@ class AssetRepair(AccountsController): "debit_in_account_currency": item.amount, "against_type": "Account", "against": item.expense_account or default_expense_account, + "against_link": item.expense_account or default_expense_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e984730d74..c9c248c3ea 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1098,6 +1098,7 @@ class AccountsController(TransactionBase): "account": round_off_account, "against_type": against_type, "against": against, + "against_link": against, credit_or_debit: precision_loss, "cost_center": round_off_cost_center if self.use_company_roundoff_cost_center @@ -1479,6 +1480,7 @@ class AccountsController(TransactionBase): "account": item.discount_account, "against_type": against_type, "against": supplier_or_customer, + "against_link": supplier_or_customer, dr_or_cr: flt( discount_amount * self.get("conversion_rate"), item.precision("discount_amount") ), @@ -1498,6 +1500,7 @@ class AccountsController(TransactionBase): "account": income_or_expense_account, "against_type": against_type, "against": supplier_or_customer, + "against_link": supplier_or_customer, rev_dr_cr: flt( discount_amount * self.get("conversion_rate"), item.precision("discount_amount") ), @@ -1522,6 +1525,7 @@ class AccountsController(TransactionBase): "account": self.additional_discount_account, "against_type": against_type, "against": supplier_or_customer, + "against_link": supplier_or_customer, dr_or_cr: self.base_discount_amount, "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), }, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 335f2b0ea6..a005c8cb8a 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -164,6 +164,7 @@ class StockController(AccountsController): "account": warehouse_account[sle.warehouse]["account"], "against_type": "Account", "against": expense_account, + "against_link": expense_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"), @@ -181,6 +182,7 @@ class StockController(AccountsController): "account": expense_account, "against_type": "Account", "against": warehouse_account[sle.warehouse]["account"], + "against_link": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": -1 * flt(sle.stock_value_difference, precision), @@ -214,6 +216,7 @@ class StockController(AccountsController): "account": expense_account, "against_type": "Account", "against": warehouse_asset_account, + "against_link": warehouse_asset_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), "remarks": _("Rounding gain/loss Entry for Stock Transfer"), @@ -231,6 +234,7 @@ class StockController(AccountsController): "account": warehouse_asset_account, "against_type": "Account", "against": expense_account, + "against_link": expense_account, "cost_center": item_row.cost_center, "remarks": _("Rounding gain/loss Entry for Stock Transfer"), "credit": sle_rounding_diff, @@ -847,6 +851,7 @@ class StockController(AccountsController): "credit": credit, "against_type": against_type, "against": against_account, + "against_link": against_account, "remarks": remarks, } diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index a850d5be7c..250a4b1495 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -155,6 +155,7 @@ def make_gl_entry(tax, gl_entries, doc, tax_accounts): "posting_date": doc.posting_date, "against_type": "Supplier", "against": doc.supplier, + "against_link": doc.supplier, dr_or_cr: tax.base_tax_amount_after_discount_amount, dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount if account_currency == doc.company_currency diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1ed2f98a9d..e87658bc98 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1388,6 +1388,7 @@ class StockEntry(StockController): "account": account, "against_type": "Account", "against": d.expense_account, + "against_link": d.expense_account, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit_in_account_currency": flt(amount["amount"]), @@ -1403,6 +1404,7 @@ class StockEntry(StockController): "account": d.expense_account, "against_type": "Account", "against": account, + "against_link": account, "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": -1 From 450c2470e959f4d705afc3212546249ad406bee3 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 14:08:57 +0530 Subject: [PATCH 026/112] refactor: set against account link for jv --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 3 ++- erpnext/accounts/utils.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 9f76d9df19..c70ad2fdaa 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -230,6 +230,7 @@ class JournalEntry(AccountsController): "account": tax_withholding_details.get("account_head"), rev_debit_or_credit: tax_withholding_details.get("tax_amount"), "against_account": parties[0], + "against_account_link": parties[0], }, ) @@ -723,7 +724,7 @@ class JournalEntry(AccountsController): elif flt(d.credit) > 0: self.accounts_credited.append(d) - if d.against_account: + if d.against_account_link: self.separate_against_account_entries = 0 break diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index e0adac412b..0edfc2ad5a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -629,6 +629,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): new_row.set("reference_name", d["against_voucher"]) new_row.against_account = cstr(jv_detail.against_account) + new_row.against_account_link = cstr(jv_detail.against_account) new_row.is_advance = cstr(jv_detail.is_advance) new_row.docstatus = 1 From 96f31847b203eb58cc92e04747700de859e8eb97 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 29 Nov 2023 16:04:36 +0530 Subject: [PATCH 027/112] fix: set cwip account for company before tests --- .../asset_value_adjustment/test_asset_value_adjustment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py index 5d49759727..2c97baece5 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py @@ -18,6 +18,9 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu class TestAssetValueAdjustment(unittest.TestCase): def setUp(self): create_asset_data() + frappe.db.set_value( + "Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC" + ) def test_current_asset_value(self): pr = make_purchase_receipt( From ceeb724acc54d06ec27f3a3b5e948e5b6236af7c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:10:51 +0100 Subject: [PATCH 028/112] feat: set lead name from email --- erpnext/crm/doctype/lead/lead.json | 3 ++- erpnext/crm/doctype/lead/lead.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index dafbd9f06d..92f446d57d 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -516,7 +516,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2023-08-28 22:28:00.104413", + "modified": "2023-12-01 18:46:49.468526", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -577,6 +577,7 @@ ], "search_fields": "lead_name,lead_owner,status", "sender_field": "email_id", + "sender_name_field": "lead_name", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index f0fc1aa590..0750519104 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -16,6 +16,7 @@ from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_ema from erpnext.accounts.party import set_taxes from erpnext.controllers.selling_controller import SellingController from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events +from erpnext.selling.doctype.customer.customer import parse_full_name class Lead(SellingController, CRMNote): @@ -113,6 +114,10 @@ class Lead(SellingController, CRMNote): return self.contact_doc = self.create_contact() + # leads created by email inbox only have the full name set + if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]): + self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name) + def after_insert(self): self.link_to_contact() From 9aeb3932d0f7dbf3daf5f8ba2e6e9f02082a0f54 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 2 Dec 2023 19:50:16 +0530 Subject: [PATCH 029/112] fix: fetch against link value in gl report --- erpnext/accounts/general_ledger.py | 1 + erpnext/accounts/report/general_ledger/general_ledger.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 70a8470614..6e4cc128a4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -280,6 +280,7 @@ def check_if_in_list(gle, gl_map, dimensions=None): "project", "finance_book", "voucher_no", + "against_link", ] if dimensions: diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index fa557a133f..08be4319e4 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -203,7 +203,7 @@ def get_gl_entries(filters, accounting_dimensions): voucher_type, voucher_no, {dimension_fields} cost_center, project, {transaction_currency_fields} against_voucher_type, against_voucher, account_currency, - against, is_opening, creation {select_fields} + against_link, against, is_opening, creation {select_fields} from `tabGL Entry` where company=%(company)s {conditions} {order_by_statement} @@ -391,6 +391,7 @@ def initialize_gle_map(gl_entries, filters): group_by = group_by_field(filters.get("group_by")) for gle in gl_entries: + gle.against = gle.get("against_link") or gle.get("against") gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[])) return gle_map From b6957ddac29a59860946831a77c8fddb3c7077d1 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:21:48 +0100 Subject: [PATCH 030/112] fix: set date format from system settings --- erpnext/patches.txt | 1 + erpnext/setup/install.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0de100a883..9b070bdaf7 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -351,5 +351,6 @@ erpnext.patches.v15_0.set_reserved_stock_in_bin erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation erpnext.patches.v14_0.update_zero_asset_quantity_field execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") +execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format")) # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 5b993fa64b..12cc62afed 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -85,8 +85,6 @@ def set_single_defaults(): except frappe.ValidationError: pass - frappe.db.set_default("date_format", "dd-mm-yyyy") - setup_currency_exchange() From 6d31563920a8daeac7a782b070e6c44fd01a9f17 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 3 Dec 2023 22:33:21 +0530 Subject: [PATCH 031/112] feat: auto set against accounts for value pairs --- .../doctype/journal_entry/journal_entry.py | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index fb7ede3e89..eb9c7c74ca 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -686,11 +686,40 @@ class JournalEntry(AccountsController): against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower()) d.against_type = against_type - d.against_account = against_account d.against_account_link = against_account else: + self.get_debited_credited_accounts() + if len(self.accounts_credited) > 1 and len(self.accounts_debited) > 1: + self.auto_set_against_accounts() + return self.get_against_accounts() + def auto_set_against_accounts(self): + for i in range(0, len(self.accounts), 2): + acc = self.accounts[i] + against_acc = self.accounts[i + 1] + if acc.debit_in_account_currency > 0: + current_val = acc.debit_in_account_currency * flt(acc.exchange_rate) + against_val = against_acc.credit_in_account_currency * flt(against_acc.exchange_rate) + else: + current_val = acc.credit_in_account_currency * flt(acc.exchange_rate) + against_val = against_acc.debit_in_account_currency * flt(against_acc.exchange_rate) + + if current_val == against_val: + acc.against_type = against_acc.party_type or "Account" + against_acc.against_type = acc.party_type or "Account" + + acc.against_account_link = against_acc.party or against_acc.account + against_acc.against_account_link = acc.party or acc.account + else: + frappe.msgprint( + _( + "Unable to automatically determine {0} accounts. Set them up in the {1} table if needed." + ).format(frappe.bold("against"), frappe.bold("Accounting Entries")), + alert=True, + ) + break + def get_against_accounts(self): self.against_accounts = [] self.split_account = {} @@ -698,14 +727,7 @@ class JournalEntry(AccountsController): if self.separate_against_account_entries: no_of_credited_acc, no_of_debited_acc = len(self.accounts_credited), len(self.accounts_debited) - if no_of_credited_acc > 1 and no_of_debited_acc > 1: - frappe.msgprint( - _( - "Unable to automatically determine {0} accounts. Set them up in the {1} table if needed." - ).format(frappe.bold("against"), frappe.bold("Accounting Entries")), - alert=True, - ) - elif no_of_credited_acc <= 1 and no_of_debited_acc <= 1: + if no_of_credited_acc <= 1 and no_of_debited_acc <= 1: self.set_against_accounts_for_single_dr_cr() self.separate_against_account_entries = 0 elif no_of_credited_acc == 1: @@ -972,8 +994,7 @@ class JournalEntry(AccountsController): gl_dict.update( { "against_type": d.against_type, - "against": d.against_account, - "against_link": d.against_account, + "against_link": d.against_account_link, } ) gl_map.append(gl_dict) From 11190aac4c8ac6897c989dd4478b05d85d08fe4c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 3 Dec 2023 22:35:01 +0530 Subject: [PATCH 032/112] fix: unhide against type field --- .../journal_entry_account/journal_entry_account.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 2b423ac51d..01006bd3db 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -294,16 +294,14 @@ { "fieldname": "against_type", "fieldtype": "Link", - "hidden": 1, "label": "Against Type", - "options": "DocType", - "print_hide": 1 + "options": "DocType" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-23 11:44:25.841187", + "modified": "2023-12-02 23:21:22.205409", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From 262cafc430cc55ac08ac5dc5bd297e94b06ad126 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 3 Dec 2023 22:36:54 +0530 Subject: [PATCH 033/112] fix: query for against types --- .../doctype/journal_entry/journal_entry.js | 25 +++++++++++++++++++ .../doctype/journal_entry/journal_entry.py | 7 ++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 9684a0d9d1..e1b436106a 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -220,6 +220,16 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro return erpnext.journal_entry.account_query(me.frm); }); + me.frm.set_query("against_account_link", "accounts", function(doc, cdt, cdn) { + return erpnext.journal_entry.against_account_query(me.frm); + }); + + me.frm.set_query("against_type", "accounts", function(){ + return { + query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_type", + } + }) + me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) { const row = locals[cdt][cdn]; @@ -591,6 +601,21 @@ $.extend(erpnext.journal_entry, { return { filters: filters }; }, + against_account_query: function(frm) { + if (frm.doc.against_type != "Account"){ + return { filters: {} }; + } + else { + let filters = { company: frm.doc.company, is_group: 0 }; + if(!frm.doc.multi_currency) { + $.extend(filters, { + account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]] + }); + } + return { filters: filters }; + } + }, + reverse_journal_entry: function() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index eb9c7c74ca..e30e780bb6 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1657,3 +1657,10 @@ def make_reverse_journal_entry(source_name, target_doc=None): ) return doclist + + +@frappe.whitelist() +def get_against_type(doctype, txt, searchfield, start, page_len, filters): + against_types = frappe.db.get_list("Party Type", pluck="name") + ["Account"] + doctype = frappe.qb.DocType("DocType") + return frappe.qb.from_(doctype).select(doctype.name).where(doctype.name.isin(against_types)).run() From fb3421fccee518799a36a27d79bf4f6632736f81 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 15:39:25 +0530 Subject: [PATCH 034/112] feat: `Company` filter in `Stock Ledger Variance` report --- .../stock_ledger_variance.js | 16 ++++++++++++---- .../stock_ledger_variance.py | 7 ++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js index b1e4a74571..bf3a397fee 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js @@ -13,10 +13,18 @@ const DIFFERENCE_FIELD_NAMES = [ frappe.query_reports["Stock Ledger Variance"] = { "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, { "fieldname": "item_code", "fieldtype": "Link", - "label": "Item", + "label": __("Item"), "options": "Item", get_query: function() { return { @@ -27,7 +35,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "warehouse", "fieldtype": "Link", - "label": "Warehouse", + "label": __("Warehouse"), "options": "Warehouse", get_query: function() { return { @@ -38,7 +46,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "difference_in", "fieldtype": "Select", - "label": "Difference In", + "label": __("Difference In"), "options": [ "", "Qty", @@ -49,7 +57,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "include_disabled", "fieldtype": "Check", - "label": "Include Disabled", + "label": __("Include Disabled"), } ], diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index 732f108ac4..acbbe9039a 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -230,7 +230,12 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: bin.item_code, bin.warehouse, ) - .where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0)) + .where( + (item.is_stock_item == 1) + & (item.has_serial_no == 0) + & (warehouse.is_group == 0) + & (warehouse.company == filters.company) + ) ) if filters.item_code: From ab6e92aae129653f13e54728d8d3a8c88dcda6cb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 4 Dec 2023 16:47:28 +0530 Subject: [PATCH 035/112] fix: limit end date to current date --- erpnext/accounts/report/financial_statements.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 096bb10706..7355c4b8a1 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -8,7 +8,17 @@ import re import frappe from frappe import _ -from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate +from frappe.utils import ( + add_days, + add_months, + cint, + cstr, + flt, + formatdate, + get_first_day, + getdate, + today, +) from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -43,6 +53,8 @@ def get_period_list( year_start_date = getdate(period_start_date) year_end_date = getdate(period_end_date) + year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date + months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity] period_list = [] From 3d7ad71b22578e46366639ef19de8d665a922c04 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 4 Dec 2023 19:19:09 +0530 Subject: [PATCH 036/112] fix: get dynamic link with parenttype contact --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index f0fc1aa590..ef6cfb09ed 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -105,7 +105,7 @@ class Lead(SellingController, CRMNote): if self.source == "Existing Customer" and self.customer: contact = frappe.db.get_value( "Dynamic Link", - {"link_doctype": "Customer", "link_name": self.customer}, + {"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer}, "parent", ) if contact: From 4ca84eadb607263f8432698ff61c4acfd2f0363b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 5 Dec 2023 10:14:05 +0530 Subject: [PATCH 037/112] refactor: ignore unreconcile doc on PI cancel/delete --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 4b0df12f45..cebd61a6f5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. super.onload(); // Ignore linked advances - this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"]; + this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; if(!this.frm.doc.__islocal) { // show credit_to in print format diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f9c9fb5ac5..d211e8e5ba 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1449,6 +1449,8 @@ class PurchaseInvoice(BuyingController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", + "Unreconcile Payment", + "Unreconcile Payment Entries", "Payment Ledger Entry", "Tax Withheld Vouchers", "Serial and Batch Bundle", From 1a5d56977e158d27210a2bfeeaa9075cb7094875 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 5 Dec 2023 11:33:16 +0530 Subject: [PATCH 038/112] refactor: ingore on JE cancel as well --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 2 +- erpnext/accounts/doctype/journal_entry/journal_entry.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 9684a0d9d1..266154d87f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry"); frappe.ui.form.on("Journal Entry", { setup: function(frm) { frm.add_fetch("bank_account", "account", "account"); - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"]; + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index e4f1645fd0..dd3e9d0bcb 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -170,6 +170,8 @@ class JournalEntry(AccountsController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", + "Unreconcile Payment", + "Unreconcile Payment Entries", ) self.make_gl_entries(1) self.update_advance_paid() From a56b79cc72241f6a4540cb93b1b0f39926137cdc Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 5 Dec 2023 12:46:22 +0530 Subject: [PATCH 039/112] fix: check for split entries in stock entry test --- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index b640983a09..186ea27335 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -504,7 +504,14 @@ class TestStockEntry(FrappeTestCase): self.check_gl_entries( "Stock Entry", repack.name, - sorted([[stock_in_hand_account, 1200, 0.0], ["Cost of Goods Sold - TCP1", 0.0, 1200.0]]), + sorted( + [ + ["Cost of Goods Sold - TCP1", 0.0, 1200.0], + ["Stock Adjustment - TCP1", 0.0, 1200.0], + ["Stock Adjustment - TCP1", 1200.0, 0.0], + [stock_in_hand_account, 1200.0, 0.0], + ] + ), ) def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle): From 24ccb3eb78f2bf88efed26e268039c38e6baf536 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 5 Dec 2023 12:47:06 +0530 Subject: [PATCH 040/112] fix: subcontracting receipt gle test --- .../test_subcontracting_receipt.py | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index f0e4e00074..22a679a1ed 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -365,24 +365,17 @@ class TestSubcontractingReceipt(FrappeTestCase): fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse) supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse) expense_account = scr.items[0].expense_account + expected_values = [ + [fg_warehouse_ac, 2100.0, 0.0], # FG Amount (D) + [supplier_warehouse_ac, 0.0, 1000.0], # RM Cost (C) + [additional_costs_expense_account, 0.0, 100.0], # Additional Cost (C) + [expense_account, 0.0, 1000.0], # Service Cost (C) + ] - if fg_warehouse_ac == supplier_warehouse_ac: - expected_values = { - fg_warehouse_ac: [2100.0, 1000.0], # FG Amount (D), RM Cost (C) - expense_account: [0.0, 1000.0], # Service Cost (C) - additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C) - } - else: - expected_values = { - fg_warehouse_ac: [2100.0, 0.0], # FG Amount (D) - supplier_warehouse_ac: [0.0, 1000.0], # RM Cost (C) - expense_account: [0.0, 1000.0], # Service Cost (C) - additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C) - } - - for gle in gl_entries: - self.assertEqual(expected_values[gle.account][0], gle.debit) - self.assertEqual(expected_values[gle.account][1], gle.credit) + for i in range(len(expected_values)): + self.assertEqual(expected_values[i][0], gl_entries[i]["account"]) + self.assertEqual(expected_values[i][1], gl_entries[i]["debit"]) + self.assertEqual(expected_values[i][2], gl_entries[i]["credit"]) scr.reload() scr.cancel() From 47c78a5a7324cdea5fde7f4e269c933572ccaf56 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 5 Dec 2023 12:48:25 +0530 Subject: [PATCH 041/112] fix: check for unmerged gle in purchase receipt test --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 146cbff1aa..e418aadfaa 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -990,7 +990,7 @@ class TestPurchaseReceipt(FrappeTestCase): gl_entries = get_gl_entries("Purchase Receipt", pr.name) sl_entries = get_sl_entries("Purchase Receipt", pr.name) - self.assertFalse(gl_entries) + self.assertEqual(len(gl_entries), 2) expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5} From 005c5a587ff7322a6f3fb1099aa0c94e6873c7fe Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 5 Dec 2023 13:16:54 +0530 Subject: [PATCH 042/112] chore: orderby in gle --- .../purchase_receipt/test_purchase_receipt.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e418aadfaa..d2740694b3 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -4,6 +4,7 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, nowtime, today +from pypika import Order from pypika import functions as fn import erpnext @@ -2235,13 +2236,13 @@ def get_sl_entries(voucher_type, voucher_no): def get_gl_entries(voucher_type, voucher_no): - return frappe.db.sql( - """select account, debit, credit, cost_center, is_cancelled - from `tabGL Entry` where voucher_type=%s and voucher_no=%s - order by account desc""", - (voucher_type, voucher_no), - as_dict=1, - ) + gle = frappe.qb.DocType("GL Entry") + return ( + frappe.qb.from_(gle) + .select(gle.account, gle.debit, gle.credit, gle.cost_center, gle.is_cancelled) + .where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)) + .orderby(gle.account, gle.debit, order=Order.desc) + ).run(as_dict=True) def get_taxes(**args): From aaa9036eca4c5d50fb82a346e08d841e1735fc72 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 5 Dec 2023 13:34:54 +0530 Subject: [PATCH 043/112] fix: incorrect material request quantity in Production Plan (#38566) --- .../production_plan/production_plan.py | 19 ++++--- .../production_plan/test_production_plan.py | 56 ++++++++++++++++++- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 955821fc58..13ae3b327a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1597,19 +1597,23 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): ) locations = get_available_item_locations( - item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True + item.get("item_code"), + warehouses, + item.get("quantity") * item.get("conversion_factor"), + company, + ignore_validation=True, ) required_qty = item.get("quantity") + if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"): + # Convert qty to stock UOM + required_qty = required_qty * item.get("conversion_factor") + # get available material by transferring to production warehouse for d in locations: if required_qty <= 0: return - conversion_factor = 1.0 - if purchase_uom != stock_uom and purchase_uom == item["uom"]: - conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) - new_dict = copy.deepcopy(item) quantity = required_qty if d.get("qty") > required_qty else d.get("qty") @@ -1619,10 +1623,11 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): "material_request_type": "Material Transfer", "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "from_warehouse": d.get("warehouse"), + "conversion_factor": 1.0, } ) - required_qty -= quantity / conversion_factor + required_qty -= quantity new_mr_items.append(new_dict) # raise purchase request for remaining qty @@ -1634,7 +1639,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): required_qty = ceil(required_qty) - item["quantity"] = required_qty + item["quantity"] = required_qty / item.get("conversion_factor") new_mr_items.append(item) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index dd32c34358..cc9d9a0311 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1283,12 +1283,14 @@ class TestProductionPlan(FrappeTestCase): for row in items: row = frappe._dict(row) if row.material_request_type == "Material Transfer": + self.assertTrue(row.uom == row.stock_uom) self.assertTrue(row.from_warehouse in [wh1, wh2]) self.assertEqual(row.quantity, 2) if row.material_request_type == "Purchase": + self.assertTrue(row.uom != row.stock_uom) self.assertTrue(row.warehouse == mrp_warhouse) - self.assertEqual(row.quantity, 12) + self.assertEqual(row.quantity, 12.0) def test_mr_qty_for_same_rm_with_different_sub_assemblies(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom @@ -1404,6 +1406,58 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_material_request_qty_purchase_and_material_transfer(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + bom_item = make_item( + properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"} + ).name + + store_warehouse = create_warehouse("Store Warehouse", company="_Test Company") + rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company") + + make_stock_entry( + item_code=bom_item, + qty=60, + target=store_warehouse, + rate=99, + ) + + if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}): + doc = frappe.get_doc("Item", bom_item) + doc.append("uoms", {"uom": "Nos", "conversion_factor": 10}) + doc.save() + + make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1 + ) + + pln.for_warehouse = rm_warehouse + items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": store_warehouse}] + ) + + for row in items: + self.assertEqual(row.get("quantity"), 10.0) + self.assertEqual(row.get("material_request_type"), "Material Transfer") + self.assertEqual(row.get("uom"), "_Test UOM 1") + self.assertEqual(row.get("from_warehouse"), store_warehouse) + self.assertEqual(row.get("conversion_factor"), 1.0) + + items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}] + ) + + for row in items: + self.assertEqual(row.get("quantity"), 1.0) + self.assertEqual(row.get("material_request_type"), "Purchase") + self.assertEqual(row.get("uom"), "Nos") + self.assertEqual(row.get("conversion_factor"), 10.0) + def create_production_plan(**args): """ From b1d9f3132d69df010e4f8f6e5d24b187823c2d8b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 5 Dec 2023 14:01:04 +0530 Subject: [PATCH 044/112] fix: sql error while filtering on finance book in GL --- erpnext/accounts/report/general_ledger/general_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index fa557a133f..ac06a1227e 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -289,7 +289,8 @@ def get_conditions(filters): if accounting_dimensions: for dimension in accounting_dimensions: - if not dimension.disabled: + # Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section + if not dimension.disabled and dimension.document_type != "Finance Book": if filters.get(dimension.fieldname): if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): filters[dimension.fieldname] = get_dimension_with_children( From eb9ee3f79b94e594fc6dfa4f6514580e125eee8c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:22:55 +0100 Subject: [PATCH 045/112] refactor: simplify conditional logic Command: `sourcery review --fix --enable de-morgan .` --- erpnext/__init__.py | 10 +++++----- erpnext/accounts/deferred_revenue.py | 2 +- .../exchange_rate_revaluation.py | 2 +- .../doctype/journal_entry/journal_entry.py | 2 +- .../loyalty_program/test_loyalty_program.py | 2 +- .../doctype/payment_entry/payment_entry.py | 15 ++++++++------- .../accounts/doctype/pricing_rule/pricing_rule.py | 2 +- .../process_payment_reconciliation.py | 2 +- .../doctype/purchase_invoice/purchase_invoice.py | 2 +- .../doctype/sales_invoice/sales_invoice.py | 8 ++++---- .../doctype/shipping_rule/shipping_rule.py | 4 ++-- .../accounts/doctype/subscription/subscription.py | 4 ++-- .../unreconcile_payment/unreconcile_payment.py | 2 +- erpnext/accounts/party.py | 2 +- .../accounts_receivable/accounts_receivable.py | 4 ++-- .../deferred_revenue_and_expense.py | 2 +- .../gross_and_net_profit_report.py | 2 +- .../accounts/report/gross_profit/gross_profit.py | 2 +- .../tax_withholding_details.py | 2 +- erpnext/accounts/utils.py | 4 ++-- erpnext/assets/doctype/asset/asset.py | 2 +- .../doctype/asset_movement/asset_movement.py | 2 +- .../purchase_order_analysis.py | 2 +- .../requested_items_to_order_and_receive.py | 4 ++-- .../supplier_quotation_comparison.py | 4 ++-- erpnext/controllers/queries.py | 13 +++++++++---- erpnext/controllers/stock_controller.py | 2 +- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- erpnext/erpnext_integrations/utils.py | 2 +- .../doctype/blanket_order/blanket_order.py | 2 +- erpnext/manufacturing/doctype/bom/bom.py | 13 +++++++++---- .../doctype/production_plan/production_plan.py | 2 +- .../doctype/work_order/work_order.py | 8 ++++---- erpnext/patches/v12_0/set_task_status.py | 2 +- erpnext/patches/v13_0/update_sla_enhancements.py | 2 +- erpnext/portal/utils.py | 2 +- erpnext/projects/doctype/project/project.py | 2 +- .../item_wise_sales_history.py | 2 +- .../sales_order_analysis/sales_order_analysis.py | 2 +- .../authorization_control.py | 10 ++++++++-- erpnext/setup/doctype/department/department.py | 2 +- .../setup/doctype/email_digest/email_digest.py | 2 +- erpnext/setup/doctype/employee/employee.py | 2 +- erpnext/setup/install.py | 2 +- .../doctype/material_request/material_request.py | 4 ++-- .../stock/doctype/putaway_rule/putaway_rule.py | 2 +- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- .../stock_ledger_entry/stock_ledger_entry.py | 4 +++- .../stock_reservation_entry.py | 2 +- erpnext/stock/stock_ledger.py | 2 +- .../service_level_agreement.py | 8 ++++---- 51 files changed, 104 insertions(+), 85 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 5c58f84be2..48ebe9287a 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -36,7 +36,7 @@ def get_default_cost_center(company): if not frappe.flags.company_cost_center: frappe.flags.company_cost_center = {} - if not company in frappe.flags.company_cost_center: + if company not in frappe.flags.company_cost_center: frappe.flags.company_cost_center[company] = frappe.get_cached_value( "Company", company, "cost_center" ) @@ -47,7 +47,7 @@ def get_company_currency(company): """Returns the default company currency""" if not frappe.flags.company_currency: frappe.flags.company_currency = {} - if not company in frappe.flags.company_currency: + if company not in frappe.flags.company_currency: frappe.flags.company_currency[company] = frappe.db.get_value( "Company", company, "default_currency", cache=True ) @@ -81,7 +81,7 @@ def is_perpetual_inventory_enabled(company): if not hasattr(frappe.local, "enable_perpetual_inventory"): frappe.local.enable_perpetual_inventory = {} - if not company in frappe.local.enable_perpetual_inventory: + if company not in frappe.local.enable_perpetual_inventory: frappe.local.enable_perpetual_inventory[company] = ( frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 ) @@ -96,7 +96,7 @@ def get_default_finance_book(company=None): if not hasattr(frappe.local, "default_finance_book"): frappe.local.default_finance_book = {} - if not company in frappe.local.default_finance_book: + if company not in frappe.local.default_finance_book: frappe.local.default_finance_book[company] = frappe.get_cached_value( "Company", company, "default_finance_book" ) @@ -108,7 +108,7 @@ def get_party_account_type(party_type): if not hasattr(frappe.local, "party_account_types"): frappe.local.party_account_types = {} - if not party_type in frappe.local.party_account_types: + if party_type not in frappe.local.party_account_types: frappe.local.party_account_types[party_type] = ( frappe.db.get_value("Party Type", party_type, "account_type") or "" ) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index d0940c7df2..367b017969 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -232,7 +232,7 @@ def calculate_monthly_amount( if amount + already_booked_amount_in_account_currency > item.net_amount: amount = item.net_amount - already_booked_amount_in_account_currency - if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date: partial_month = flt(date_diff(end_date, start_date)) / flt( date_diff(get_last_day(end_date), get_first_day(start_date)) ) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index e9cbb337d5..8be09db2cf 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -44,7 +44,7 @@ class ExchangeRateRevaluation(Document): self.set_total_gain_loss() def validate_rounding_loss_allowance(self): - if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1): + if self.rounding_loss_allowance < 0 or self.rounding_loss_allowance >= 1: frappe.throw(_("Rounding Loss Allowance should be between 0 and 1")) def set_total_gain_loss(self): diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index dd3e9d0bcb..40d552bc88 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -631,7 +631,7 @@ class JournalEntry(AccountsController): ) # set totals - if not d.reference_name in self.reference_totals: + if d.reference_name not in self.reference_totals: self.reference_totals[d.reference_name] = 0.0 if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"): diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 3641ac4428..cbfb17bf12 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -140,7 +140,7 @@ class TestLoyaltyProgram(unittest.TestCase): "Loyalty Point Entry", {"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer}, ) - self.assertEqual(True, not (lpe is None)) + self.assertEqual(True, lpe is not None) # cancelling sales invoice si.cancel() diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1282ab6039..a6ddce5223 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -369,12 +369,12 @@ class PaymentEntry(AccountsController): self.set(self.party_account_field, party_account) self.party_account = party_account - if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance): + if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance: acc = get_account_details(self.paid_from, self.posting_date, self.cost_center) self.paid_from_account_currency = acc.account_currency self.paid_from_account_balance = acc.account_balance - if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance): + if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance: acc = get_account_details(self.paid_to, self.posting_date, self.cost_center) self.paid_to_account_currency = acc.account_currency self.paid_to_account_balance = acc.account_balance @@ -390,8 +390,9 @@ class PaymentEntry(AccountsController): ) -> None: for d in self.get("references"): if d.allocated_amount: - if update_ref_details_only_for and ( - not (d.reference_doctype, d.reference_name) in update_ref_details_only_for + if ( + update_ref_details_only_for + and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for ): continue @@ -702,7 +703,7 @@ class PaymentEntry(AccountsController): self.db_set("status", self.status, update_modified=True) def set_tax_withholding(self): - if not self.party_type == "Supplier": + if self.party_type != "Supplier": return if not self.apply_tax_withholding_amount: @@ -793,7 +794,7 @@ class PaymentEntry(AccountsController): self.base_received_amount = self.base_paid_amount if ( self.paid_from_account_currency == self.paid_to_account_currency - and not self.payment_type == "Internal Transfer" + and self.payment_type != "Internal Transfer" ): self.received_amount = self.paid_amount @@ -1757,7 +1758,7 @@ def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: "Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date" ) for payment_term in payment_schedule: - if not payment_term.outstanding > 0.1: + if payment_term.outstanding <= 0.1: continue doc_details = exc_rates.get(payment_term.parent, None) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index b41cf53665..82bd662ea7 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -286,7 +286,7 @@ class PricingRule(Document): def validate_price_list_with_currency(self): if self.currency and self.for_price_list: price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True) - if not self.currency == price_list_currency: + if self.currency != price_list_currency: throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency)) def validate_dates(self): diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index 67a7f90042..f44b14caed 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -475,7 +475,7 @@ def reconcile(doc: None | str = None) -> None: frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") else: - if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"): + if frappe.db.get_value("Process Payment Reconciliation", doc, "status") != "Paused": # trigger next batch in job # generate reconcile job name allocation = get_next_allocation(log) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7156fa2682..ae377ebafd 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -371,7 +371,7 @@ class PurchaseInvoice(BuyingController): check_list = [] for d in self.get("items"): - if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: + if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt: check_list.append(d.purchase_order) check_on_hold_or_closed_status("Purchase Order", d.purchase_order) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 96a557b7e8..f2f4dda817 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -458,7 +458,7 @@ class SalesInvoice(SellingController): self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.check_credit_limit() - if not cint(self.is_pos) == 1 and not self.is_return: + if cint(self.is_pos) != 1 and not self.is_return: self.update_against_document_in_jv() self.update_time_sheet(self.name) @@ -1960,9 +1960,9 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc if inter_company_reference: doc = frappe.get_doc(ref_doc, inter_company_reference) ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer - if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party: + if frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") != party: frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype))) - if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company: + if frappe.get_cached_value(ref_partytype, ref_party, "represents_company") != company: frappe.throw(_("Invalid Company for Inter Company Transaction.")) elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party: @@ -1972,7 +1972,7 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc filters={"parenttype": partytype, "parent": party}, ) companies = [d.company for d in companies] - if not company in companies: + if company not in companies: frappe.throw( _("{0} not allowed to transact with {1}. Please change the Company.").format( _(partytype), company diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 52a60ac7aa..6877a745b8 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -142,12 +142,12 @@ class ShippingRule(Document): } if self.shipping_rule_type == "Selling": # check if not applied on purchase - if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges": + if doc.meta.get_field("taxes").options != "Sales Taxes and Charges": frappe.throw(_("Shipping rule only applicable for Selling")) shipping_charge["doctype"] = "Sales Taxes and Charges" else: # check if not applied on sales - if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges": + if doc.meta.get_field("taxes").options != "Purchase Taxes and Charges": frappe.throw(_("Shipping rule only applicable for Buying")) shipping_charge["doctype"] = "Purchase Taxes and Charges" diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 6cc2d1e9ee..aba1b64930 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -676,7 +676,7 @@ class Subscription(Document): to_generate_invoice = ( True if self.status == "Active" - and not self.generate_invoice_at == "Beginning of the current subscription period" + and self.generate_invoice_at != "Beginning of the current subscription period" else False ) self.status = "Cancelled" @@ -694,7 +694,7 @@ class Subscription(Document): subscription and the `Subscription` will lose all the history of generated invoices it has. """ - if not self.status == "Cancelled": + if self.status != "Cancelled": frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled) self.status = "Active" diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index e258a73d4d..9b56952faa 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -37,7 +37,7 @@ class UnreconcilePayment(Document): def validate(self): self.supported_types = ["Payment Entry", "Journal Entry"] - if not self.voucher_type in self.supported_types: + if self.voucher_type not in self.supported_types: frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types))) @frappe.whitelist() diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 1efe35c206..008614e6d3 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -775,7 +775,7 @@ def validate_party_frozen_disabled(party_type, party_name): frozen_accounts_modifier = frappe.db.get_single_value( "Accounts Settings", "frozen_accounts_modifier" ) - if not frozen_accounts_modifier in frappe.get_roles(): + if frozen_accounts_modifier not in frappe.get_roles(): frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) elif party_type == "Employee": diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index eaf9f421e1..50d5eae407 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -123,7 +123,7 @@ class ReceivablePayableReport(object): else: key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) - if not key in self.voucher_balance: + if key not in self.voucher_balance: self.voucher_balance[key] = frappe._dict( voucher_type=ple.voucher_type, voucher_no=ple.voucher_no, @@ -938,7 +938,7 @@ class ReceivablePayableReport(object): return True def get_party_details(self, party): - if not party in self.party_details: + if party not in self.party_details: if self.account_type == "Receivable": fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"] diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index cad5325c6e..eebd61c680 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -97,7 +97,7 @@ class Deferred_Item(object): if base_amount + already_booked_amount > self.base_net_amount: base_amount = self.base_net_amount - already_booked_amount - if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date: partial_month = flt(date_diff(end_date, start_date)) / flt( date_diff(get_last_day(end_date), get_first_day(start_date)) ) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index f0ca405401..604bc01fd6 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -170,7 +170,7 @@ def set_total(node, value, complete_list, totals): totals[node["account"]] += value parent = node["parent_account"] - if not parent == "": + if parent != "": return set_total( next(item for item in complete_list if item["account"] == parent), value, complete_list, totals ) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 38060bb5b2..e4efefe7f5 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -695,7 +695,7 @@ class GrossProfitGenerator(object): def get_average_buying_rate(self, row, item_code): args = row - if not item_code in self.average_buying_rate: + if item_code not in self.average_buying_rate: args.update( { "voucher_type": row.parenttype, diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index f6c7bd3db7..ba946c3bcf 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -154,7 +154,7 @@ def get_gle_map(documents): ) for d in gle: - if not d.voucher_no in gle_map: + if d.voucher_no not in gle_map: gle_map[d.voucher_no] = [d] else: gle_map[d.voucher_no].append(d) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f88e26ef87..6b5982769a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1062,11 +1062,11 @@ def get_outstanding_invoices( if ( min_outstanding and max_outstanding - and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding) + and (outstanding_amount < min_outstanding or outstanding_amount > max_outstanding) ): continue - if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: + if d.voucher_type != "Purchase Invoice" or d.voucher_no not in held_invoices: outstanding_invoices.append( frappe._dict( { diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 5fb2d36178..707ce199c3 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -313,7 +313,7 @@ class Asset(AccountsController): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): - if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): + if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice: frappe.throw( _("Please create purchase receipt or purchase invoice for the item {0}").format( self.item_code diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 0d8efcb8dd..ff52643ac6 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -84,7 +84,7 @@ class AssetMovement(Document): frappe.throw(_("Source and Target Location cannot be same")) if self.purpose == "Receipt": - if not (d.source_location) and not (d.target_location or d.to_employee): + if not (d.source_location) and not d.target_location and not d.to_employee: frappe.throw( _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) ) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index b6e46302ff..b88efe13c0 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -114,7 +114,7 @@ def prepare_data(data, filters): if filters.get("group_by_po"): po_name = row["purchase_order"] - if not po_name in purchase_order_map: + if po_name not in purchase_order_map: # create an entry row_copy = copy.deepcopy(row) purchase_order_map[po_name] = row_copy diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 07187352eb..d43101072b 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -110,7 +110,7 @@ def prepare_data(data, filters): for row in data: # item wise map for charts - if not row["item_code"] in item_qty_map: + if row["item_code"] not in item_qty_map: item_qty_map[row["item_code"]] = { "qty": flt(row["stock_qty"], precision), "stock_qty": flt(row["stock_qty"], precision), @@ -127,7 +127,7 @@ def prepare_data(data, filters): if filters.get("group_by_mr"): # consolidated material request map for group by filter - if not row["material_request"] in material_request_map: + if row["material_request"] not in material_request_map: # create an entry with mr as key row_copy = copy.deepcopy(row) material_request_map[row["material_request"]] = row_copy diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 01ff28d810..73b7d45830 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -126,7 +126,7 @@ def prepare_data(supplier_quotation_data, filters): # map for chart preparation of the form {'supplier1': {'qty': 'price'}} supplier = data.get("supplier_name") if filters.get("item_code"): - if not supplier in supplier_qty_price_map: + if supplier not in supplier_qty_price_map: supplier_qty_price_map[supplier] = {} supplier_qty_price_map[supplier][row["qty"]] = row["price"] @@ -169,7 +169,7 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): for supplier in suppliers: entry = supplier_qty_price_map[supplier] for qty in qty_list: - if not qty in data_points_map: + if qty not in data_points_map: data_points_map[qty] = [] if qty in entry: data_points_map[qty].append(entry[qty]) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 199732b152..63dca630c7 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -222,7 +222,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = meta.get_search_fields() columns = "" - extra_searchfields = [field for field in searchfields if not field in ["name", "description"]] + extra_searchfields = [field for field in searchfields if field not in ["name", "description"]] if extra_searchfields: columns += ", " + ", ".join(extra_searchfields) @@ -233,8 +233,13 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = searchfields + [ field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] - if not field in searchfields + for field in [ + searchfield or "name", + "item_code", + "item_group", + "item_name", + ] + if field not in searchfields ] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) @@ -872,7 +877,7 @@ def get_fields(doctype, fields=None): meta = frappe.get_meta(doctype) fields.extend(meta.get_search_fields()) - if meta.title_field and not meta.title_field.strip() in fields: + if meta.title_field and meta.title_field.strip() not in fields: fields.insert(1, meta.title_field.strip()) return unique(fields) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index fc45c7ad52..2fda9ccd6e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -642,7 +642,7 @@ class StockController(AccountsController): ) qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus") - if not qa_docstatus == 1: + if qa_docstatus != 1: link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection) msg = ( f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}" diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 7be6fdcc13..a735510d7d 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -55,7 +55,7 @@ class Appointment(Document): "Appointment", filters={"scheduled_time": self.scheduled_time} ) number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents") - if not number_of_agents == 0: + if number_of_agents != 0: if number_of_appointments_in_same_slot >= number_of_agents: frappe.throw(_("Time slot is not available")) # Link lead @@ -110,7 +110,7 @@ class Appointment(Document): cal_event.save(ignore_permissions=True) def set_verified(self, email): - if not email == self.customer_email: + if email != self.customer_email: frappe.throw(_("Email verification failed.")) # Create new lead self.create_lead_and_link() diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 8984f1bee7..1924ffb50b 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -16,7 +16,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key="secret"): hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest() ) - if frappe.request.data and not sig == bytes(frappe.get_request_header(hmac_key).encode()): + if frappe.request.data and sig != bytes(frappe.get_request_header(hmac_key).encode()): frappe.throw(_("Unverified Webhook Data")) frappe.set_user(settings.modified_by) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index b5ab63efd0..6a72c4fdaf 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -89,7 +89,7 @@ def make_order(source_name): def update_item(source, target, source_parent): target_qty = source.get("qty") - source.get("ordered_qty") - target.qty = target_qty if not flt(target_qty) < 0 else 0 + target.qty = target_qty if flt(target_qty) >= 0 else 0 item = get_item_defaults(target.item_code, source_parent.company) if item: target.item_name = item.get("item_name") diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 71015a414d..f0381d2cef 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1381,7 +1381,7 @@ def get_bom_diff(bom1, bom2): # check for deletions for d in old_value: - if not d.get(identifier) in new_row_by_identifier: + if d.get(identifier) not in new_row_by_identifier: out.removed.append([df.fieldname, d.as_dict()]) return out @@ -1397,13 +1397,18 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "item_name", "item_group", "description"] fields.extend( - [field for field in searchfields if not field in ["name", "item_group", "description"]] + [field for field in searchfields if field not in ["name", "item_group", "description"]] ) searchfields = searchfields + [ field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] - if not field in searchfields + for field in [ + searchfield or "name", + "item_code", + "item_group", + "item_name", + ] + if field not in searchfields ] query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 13ae3b327a..c201c4f7be 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -815,7 +815,7 @@ class ProductionPlan(Document): key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) - if not key in material_request_map: + if key not in material_request_map: # make a new MR for the combination material_request_map[key] = frappe.new_doc("Material Request") material_request = material_request_map[key] diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 78bfc767d6..0acc2b1f91 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -930,7 +930,7 @@ class WorkOrder(Document): validate_end_of_life(self.production_item) def validate_qty(self): - if not self.qty > 0: + if self.qty <= 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) if ( @@ -957,7 +957,7 @@ class WorkOrder(Document): max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0) - if not max_qty > 0: + if max_qty <= 0: frappe.throw( _("Cannot produce more item for {0}").format(self.production_item), OverProductionError ) @@ -968,7 +968,7 @@ class WorkOrder(Document): ) def validate_transfer_against(self): - if not self.docstatus == 1: + if self.docstatus != 1: # let user configure operations until they're ready to submit return if not self.operations: @@ -981,7 +981,7 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: - if not d.time_in_mins > 0: + if d.time_in_mins <= 0: frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py index 1c6654e57a..27810d7abe 100644 --- a/erpnext/patches/v12_0/set_task_status.py +++ b/erpnext/patches/v12_0/set_task_status.py @@ -10,7 +10,7 @@ def execute(): ) if property_setter_name: property_setter = frappe.get_doc("Property Setter", property_setter_name) - if not "Completed" in property_setter.value: + if "Completed" not in property_setter.value: property_setter.value = property_setter.value + "\nCompleted" property_setter.save() diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py index 84c683acd2..cf9e185a65 100644 --- a/erpnext/patches/v13_0/update_sla_enhancements.py +++ b/erpnext/patches/v13_0/update_sla_enhancements.py @@ -46,7 +46,7 @@ def execute(): {"response_time": response_time, "resolution_time": resolution_time}, ) if priority.parenttype == "Service Level": - if not priority.parent in priority_dict: + if priority.parent not in priority_dict: priority_dict[priority.parent] = [] priority_dict[priority.parent].append(priority) diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 903d4a6196..86426b2ffe 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -50,7 +50,7 @@ def create_customer_or_supplier(): party = frappe.new_doc(doctype) fullname = frappe.utils.get_fullname(user) - if not doctype == "Customer": + if doctype != "Customer": party.update( { "supplier_name": fullname, diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 4f2e39539d..751dcbd06a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -661,7 +661,7 @@ def set_project_status(project, status): """ set status for project and all related tasks """ - if not status in ("Completed", "Cancelled"): + if status not in ("Completed", "Cancelled"): frappe.throw(_("Status must be Cancelled or Completed")) project = frappe.get_doc("Project", project) diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 2624db3191..cd45e7d82f 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -240,7 +240,7 @@ def get_chart_data(data): for row in data: item_key = row.get("item_code") - if not item_key in item_wise_sales_map: + if item_key not in item_wise_sales_map: item_wise_sales_map[item_key] = 0 item_wise_sales_map[item_key] = flt(item_wise_sales_map[item_key]) + flt(row.get("amount")) 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 29691230f2..1e1d0c0772 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -167,7 +167,7 @@ def prepare_data(data, so_elapsed_time, filters): if filters.get("group_by_so"): so_name = row["sales_order"] - if not so_name in sales_order_map: + if so_name not in sales_order_map: # create an entry row_copy = copy.deepcopy(row) sales_order_map[so_name] = row_copy diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py index feb14a8a4c..9446fb46a4 100644 --- a/erpnext/setup/doctype/authorization_control/authorization_control.py +++ b/erpnext/setup/doctype/authorization_control/authorization_control.py @@ -185,7 +185,10 @@ class AuthorizationControl(TransactionBase): # Remove user specific rules from global authorization rules for r in based_on: - if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: + if r in final_based_on and r not in [ + "Itemwise Discount", + "Item Group wise Discount", + ]: final_based_on.remove(r) # Check for authorization set on particular roles @@ -213,7 +216,10 @@ class AuthorizationControl(TransactionBase): # Remove role specific rules from global authorization rules for r in based_on: - if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: + if r in final_based_on and r not in [ + "Itemwise Discount", + "Item Group wise Discount", + ]: final_based_on.remove(r) # Check for global authorization diff --git a/erpnext/setup/doctype/department/department.py b/erpnext/setup/doctype/department/department.py index 6b090f8af7..16f6fbfba4 100644 --- a/erpnext/setup/doctype/department/department.py +++ b/erpnext/setup/doctype/department/department.py @@ -44,7 +44,7 @@ class Department(NestedSet): def before_rename(self, old, new, merge=False): # renaming consistency with abbreviation - if not frappe.get_cached_value("Company", self.company, "abbr") in new: + if frappe.get_cached_value("Company", self.company, "abbr") not in new: new = get_abbreviated_name(new, self.company) return new diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 22bdf50473..4b0705603f 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -689,7 +689,7 @@ class EmailDigest(Document): ] def get_root_type_accounts(self, root_type): - if not root_type in self._accounts: + if root_type not in self._accounts: self._accounts[root_type] = [ d.name for d in frappe.db.get_all( diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 6f9176cf27..4bb3539ae8 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -187,7 +187,7 @@ class Employee(NestedSet): throw(_("Please enter relieving date.")) def validate_for_enabled_user_id(self, enabled): - if not self.status == "Active": + if self.status != "Active": return if enabled is None: diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 5b993fa64b..d1214fd112 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -197,7 +197,7 @@ def add_standard_navbar_items(): for item in erpnext_navbar_items: current_labels = [item.get("item_label") for item in current_navbar_items] - if not item.get("item_label") in current_labels: + if item.get("item_label") not in current_labels: navbar_settings.append("help_dropdown", item) for item in current_navbar_items: diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 7df74f81ab..3e90ed5a3b 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -79,10 +79,10 @@ class MaterialRequest(BuyingController): so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}} for d in self.get("items"): if d.sales_order: - if not d.sales_order in so_items: + if d.sales_order not in so_items: so_items[d.sales_order] = {d.item_code: flt(d.qty)} else: - if not d.item_code in so_items[d.sales_order]: + if d.item_code not in so_items[d.sales_order]: so_items[d.sales_order][d.item_code] = flt(d.qty) else: so_items[d.sales_order][d.item_code] += flt(d.qty) diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py index 3fc4e01bdc..7ed6923751 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py @@ -170,7 +170,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None): pending_qty -= qty_to_allocate rule["free_space"] -= stock_qty_to_allocate - if not pending_stock_qty > 0: + if pending_stock_qty <= 0: break # if pending qty after applying all rules, add row without warehouse diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 3baafd77ba..84e99c5155 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -227,7 +227,7 @@ class StockEntry(StockController): self.calculate_rate_and_amount() self.validate_putaway_capacity() - if not self.get("purpose") == "Manufacture": + if 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") diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index e62f0b2ac7..23788cf46b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -214,7 +214,9 @@ class StockLedgerEntry(Document): if not self.serial_and_batch_bundle: self.throw_error_message(f"Serial No / Batch No are mandatory for Item {self.item_code}") - if self.serial_and_batch_bundle and not (item_detail.has_serial_no or item_detail.has_batch_no): + if ( + self.serial_and_batch_bundle and not item_detail.has_serial_no and not item_detail.has_batch_no + ): self.throw_error_message(f"Serial No and Batch No are not allowed for Item {self.item_code}") def throw_error_message(self, message, exception=frappe.ValidationError): diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 85550c2b7d..24650fde5f 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -931,7 +931,7 @@ def create_stock_reservation_entries_for_so_items( continue # Stock should be reserved from the Pick List if has Picked Qty. - if not from_voucher_type == "Pick List" and flt(item.picked_qty) > 0: + if from_voucher_type != "Pick List" and flt(item.picked_qty) > 0: frappe.throw( _("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format( item.idx, frappe.bold(item.item_code) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9142a27f4c..9203f4570a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1711,7 +1711,7 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return - if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): + if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation": return neg_sle = get_future_sle_with_negative_qty(args) 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 879381c3f2..8b37c9478c 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -100,7 +100,7 @@ class ServiceLevelAgreement(Document): priorities.append(priority.priority) # Check if repeated priority - if not len(set(priorities)) == len(priorities): + if len(set(priorities)) != len(priorities): repeated_priority = get_repeated(priorities) frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority)) @@ -128,7 +128,7 @@ class ServiceLevelAgreement(Document): ) # Check for repeated workday - if not len(set(support_days)) == len(support_days): + if len(set(support_days)) != len(support_days): repeated_days = get_repeated(support_days) frappe.throw(_("Workday {0} has been repeated.").format(repeated_days)) @@ -748,13 +748,13 @@ def change_service_level_agreement_and_priority(self): and frappe.db.get_single_value("Support Settings", "track_service_level_agreement") ): - if not self.priority == frappe.db.get_value("Issue", self.name, "priority"): + if self.priority != frappe.db.get_value("Issue", self.name, "priority"): self.set_response_and_resolution_time( priority=self.priority, service_level_agreement=self.service_level_agreement ) frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority)) - if not self.service_level_agreement == frappe.db.get_value( + if self.service_level_agreement != frappe.db.get_value( "Issue", self.name, "service_level_agreement" ): self.set_response_and_resolution_time( From bac811bd5e9fc65ab44ddc0bf06b8c5bbd9be34f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:23:58 +0100 Subject: [PATCH 046/112] chore: git blame ignore rev --- .git-blame-ignore-revs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 3bc22af96a..275442a5c7 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -28,4 +28,7 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a 494bd9ef78313436f0424b918f200dab8fc7c20b # bulk format python code with black -baec607ff5905b1c67531096a9cf50ec7ff00a5d \ No newline at end of file +baec607ff5905b1c67531096a9cf50ec7ff00a5d + +# bulk refactor with sourcery +eb9ee3f79b94e594fc6dfa4f6514580e125eee8c From 8beec586709dcd716de4fb46476218baed2ec972 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 5 Dec 2023 17:51:01 +0530 Subject: [PATCH 047/112] fix: incorrect SLE for `Moving Average` valuation method --- .../stock_ledger_variance.py | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index acbbe9039a..194f0ca637 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -194,6 +194,7 @@ def get_columns(): def get_data(filters=None): filters = frappe._dict(filters or {}) item_warehouse_map = get_item_warehouse_combinations(filters) + valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method") data = [] if item_warehouse_map: @@ -206,7 +207,9 @@ def get_data(filters=None): continue for row in report_data: - if has_difference(row, precision, filters.difference_in): + if has_difference( + row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method + ): data.append(add_item_warehouse_details(row, item_warehouse)) break @@ -229,6 +232,7 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: .select( bin.item_code, bin.warehouse, + item.valuation_method, ) .where( (item.is_stock_item == 1) @@ -248,36 +252,38 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: return query.run(as_dict=1) -def has_difference(row, precision, difference_in): - has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) - has_value_difference = ( - flt(row.diff_value_diff, precision) - or flt(row.fifo_value_diff, precision) - or flt(row.fifo_difference_diff, precision) - ) - has_valuation_difference = flt(row.valuation_diff, precision) or flt( - row.fifo_valuation_diff, precision - ) +def has_difference(row, precision, difference_in, valuation_method): + if valuation_method == "Moving Average": + qty_diff = flt(row.difference_in_qty, precision) + value_diff = flt(row.diff_value_diff, precision) + valuation_diff = flt(row.valuation_diff, precision) + else: + qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) + value_diff = ( + flt(row.diff_value_diff, precision) + or flt(row.fifo_value_diff, precision) + or flt(row.fifo_difference_diff, precision) + ) + valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision) - if difference_in == "Qty" and has_qty_difference: + if difference_in == "Qty" and qty_diff: return True - elif difference_in == "Value" and has_value_difference: + elif difference_in == "Value" and value_diff: return True - elif difference_in == "Valuation" and has_valuation_difference: + elif difference_in == "Valuation" and valuation_diff: return True elif difference_in not in ["Qty", "Value", "Valuation"] and ( - has_qty_difference or has_value_difference or has_valuation_difference + qty_diff or value_diff or valuation_diff ): return True - return False - def add_item_warehouse_details(row, item_warehouse): row.update( { "item_code": item_warehouse.item_code, "warehouse": item_warehouse.warehouse, + "valuation_method": item_warehouse.valuation_method, } ) From 16c297c2ecbc9a174e90597756cf46170bbd7c4a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 5 Dec 2023 18:06:14 +0530 Subject: [PATCH 048/112] feat: add `Valuation Method` column in `Stock Ledger Variance` report --- .../stock_ledger_variance.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index 194f0ca637..189a90aa47 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -55,6 +55,11 @@ def get_columns(): "label": _("Warehouse"), "options": "Warehouse", }, + { + "fieldname": "valuation_method", + "fieldtype": "Data", + "label": _("Valuation Method"), + }, { "fieldname": "voucher_type", "fieldtype": "Link", @@ -210,7 +215,14 @@ def get_data(filters=None): if has_difference( row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method ): - data.append(add_item_warehouse_details(row, item_warehouse)) + row.update( + { + "item_code": item_warehouse.item_code, + "warehouse": item_warehouse.warehouse, + "valuation_method": item_warehouse.valuation_method or valuation_method, + } + ) + data.append(row) break return data @@ -276,15 +288,3 @@ def has_difference(row, precision, difference_in, valuation_method): qty_diff or value_diff or valuation_diff ): return True - - -def add_item_warehouse_details(row, item_warehouse): - row.update( - { - "item_code": item_warehouse.item_code, - "warehouse": item_warehouse.warehouse, - "valuation_method": item_warehouse.valuation_method, - } - ) - - return row From d34787cf6d37c479f00ac1c9bc529b544d95dab1 Mon Sep 17 00:00:00 2001 From: sandratridz <102575830+sandratridz@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:41:02 +0530 Subject: [PATCH 049/112] fix: only highest eligible coupon applied (#38416) * fix: application of pricing rule when coupon is used --- erpnext/accounts/doctype/pricing_rule/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 57feaa03eb..18aa6820a3 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -581,6 +581,8 @@ def apply_pricing_rule_on_transaction(doc): if d.price_or_product_discount == "Price": if d.apply_discount_on: doc.set("apply_discount_on", d.apply_discount_on) + # Variable to track whether the condition has been met + condition_met = False for field in ["additional_discount_percentage", "discount_amount"]: pr_field = "discount_percentage" if field == "additional_discount_percentage" else field @@ -603,6 +605,11 @@ def apply_pricing_rule_on_transaction(doc): if coupon_code_pricing_rule == d.name: # if selected coupon code is linked with pricing rule doc.set(field, d.get(pr_field)) + + # Set the condition_met variable to True and break out of the loop + condition_met = True + break + else: # reset discount if not linked doc.set(field, 0) @@ -611,6 +618,10 @@ def apply_pricing_rule_on_transaction(doc): doc.set(field, 0) doc.calculate_taxes_and_totals() + + # Break out of the main loop if the condition is met + if condition_met: + break elif d.price_or_product_discount == "Product": item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []}) get_product_discount_rule(d, item_details, doc=doc) From 525f656cc111f6225b44e1211dc5726d311a120b Mon Sep 17 00:00:00 2001 From: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:18:13 +0000 Subject: [PATCH 050/112] feat: add employee number to client user bootinfo (#38477) --- erpnext/hooks.py | 1 + erpnext/startup/boot.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 17ad155031..f6b6802d58 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -637,6 +637,7 @@ additional_timeline_content = { extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", + "erpnext.startup.boot.bootinfo", ] diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index bdbf8b4fac..5086f355b9 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -75,3 +75,11 @@ def update_page_info(bootinfo): "Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"}, } ) + + +def bootinfo(bootinfo): + if bootinfo.get("user") and bootinfo["user"].get("name"): + bootinfo["user"]["employee"] = "" + employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name", cache=True) + if employee: + bootinfo["user"]["employee"] = employee From 6a47a2ceaf20572e143ab12ff76cfd9c54f509d6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 6 Dec 2023 20:53:55 +0530 Subject: [PATCH 051/112] chore: remove unused cache=True --- erpnext/startup/boot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 5086f355b9..4b4d14f89e 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -80,6 +80,6 @@ def update_page_info(bootinfo): def bootinfo(bootinfo): if bootinfo.get("user") and bootinfo["user"].get("name"): bootinfo["user"]["employee"] = "" - employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name", cache=True) + employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name") if employee: bootinfo["user"]["employee"] = employee From 0156339f3433f1a3ddb08885d2b85902c1114345 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 7 Dec 2023 10:16:46 +0530 Subject: [PATCH 052/112] fix(ux): don't update qty blindly --- erpnext/public/js/controllers/transaction.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8c0d84b415..156c05ba41 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -471,7 +471,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", - child: item, args: { doc: me.frm.doc, args: { @@ -521,6 +520,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ + () => { + var child = locals[cdt][cdn]; + var std_field_list = ["doctype"] + .concat(frappe.model.std_fields_list) + .concat(frappe.model.child_table_field_list); + + for (var key in r.message) { + if (std_field_list.indexOf(key) === -1) { + if (key === "qty" && child[key]) continue; + child[key] = r.message[key]; + } + } + }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); From f45dd740c58f34fad6e981d689c587815fba6625 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 7 Dec 2023 15:29:50 +0530 Subject: [PATCH 053/112] chore: minor code cleanup (#38615) --- .../stock/doctype/stock_reconciliation/stock_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index b3998b7c7e..8e9dcb0fc5 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -209,7 +209,7 @@ frappe.ui.form.on("Stock Reconciliation", { set_amount_quantity: function(doc, cdt, cdn) { var d = frappe.model.get_doc(cdt, cdn); - if (d.qty & d.valuation_rate) { + if (d.qty && d.valuation_rate) { frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate)); frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty)); frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount)); From 231ab83562cd972ebc7663e1a86f82080e3b27cd Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 7 Dec 2023 18:00:07 +0530 Subject: [PATCH 054/112] fix: serial and batch bundle permission (#38618) --- .../serial_and_batch_bundle.json | 116 +++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json index d46b07a3e1..7a58462357 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -1,7 +1,7 @@ { "actions": [], "autoname": "naming_series:", - "creation": "2022-09-29 14:56:38.338267", + "creation": "2023-08-11 17:22:12.907518", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -250,7 +250,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-28 12:56:03.072224", + "modified": "2023-12-07 17:56:55.528563", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Bundle", @@ -270,6 +270,118 @@ "share": 1, "submit": 1, "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, + "write": 1 } ], "sort_field": "modified", From 08ed3cd31394eb1108dc29a7a5b3f6ded85e3656 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 7 Dec 2023 19:39:24 +0530 Subject: [PATCH 055/112] fix: format only if searched text contain link value text --- erpnext/public/js/financial_statements.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 1b10d8ad3a..17341d19d9 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), - "formatter": function(value, row, column, data, default_formatter) { + "formatter": function(value, row, column, data, default_formatter, filter) { if (data && column.fieldname=="account") { value = data.account_name || value; + if (filter && filter?.text && filter?.type == "contains") { + if (!value.toLowerCase().includes(filter.text)) { + return value; + } + } + if (data.account) { column.link_onclick = "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; From 9611e9bd7fea9301fb850dfc28cd52f46464daab Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 7 Dec 2023 19:42:40 +0530 Subject: [PATCH 056/112] fix: ignore non-existing regional customizations (#38621) --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 49ba78c63a..32d92f6459 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -2,8 +2,8 @@ # License: GNU General Public License v3. See license.txt -import os import json +import os import frappe from frappe import _ @@ -114,10 +114,11 @@ def update_regional_tax_settings(country, company): frappe.scrub(country) ) frappe.get_attr(module_name)(country, company) - except Exception as e: + except (ImportError, AttributeError): + pass + except Exception: # Log error and ignore if failed to setup regional tax settings frappe.log_error("Unable to setup regional tax settings") - pass def make_taxes_and_charges_template(company_name, doctype, template): From 0465c9aabbea8724192f16394d7a327704299764 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:15:26 +0100 Subject: [PATCH 057/112] fix: attribute error --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dfeb1ee7fb..b5469eedb5 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -572,8 +572,8 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group - if args.child_doctype and item_tax_template: - out.update(get_fetch_values(args.child_doctype, "item_tax_template", item_tax_template)) + if args.get("child_doctype") and item_tax_template: + out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template)) def _get_item_tax_template(args, taxes, out=None, for_validate=False): From ca0c3eb184b02f56f36879f9472ce82b55dcee1d Mon Sep 17 00:00:00 2001 From: creative-paramu Date: Fri, 8 Dec 2023 11:05:09 +0530 Subject: [PATCH 058/112] fix(ux): `Shipping Address Link` --- erpnext/public/js/controllers/buying.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 0860d9c667..3ed7fc75cf 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -36,14 +36,14 @@ erpnext.buying = { // no idea where me is coming from if(this.frm.get_field('shipping_address')) { - this.frm.set_query("shipping_address", function() { - if(me.frm.doc.customer) { + this.frm.set_query("shipping_address", () => { + if(this.frm.doc.customer) { return { query: 'frappe.contacts.doctype.address.address.address_query', - filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } + filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer } }; } else - return erpnext.queries.company_address_query(me.frm.doc) + return erpnext.queries.company_address_query(this.frm.doc) }); } } From 89326bd657217b82e14675ebb464b87214332eec Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 10 Dec 2023 11:05:58 +0530 Subject: [PATCH 059/112] fix: auto delete draft serial and batch bundle (#38637) --- erpnext/controllers/accounts_controller.py | 10 ++++ .../test_serial_and_batch_bundle.py | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1b8f66c897..abcea44a2f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -292,6 +292,7 @@ class AccountsController(TransactionBase): def on_trash(self): self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() + self.remove_serial_and_batch_bundle() # delete sl and gl entries on deletion of transaction if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): @@ -307,6 +308,15 @@ class AccountsController(TransactionBase): (self.doctype, self.name), ) + def remove_serial_and_batch_bundle(self): + bundles = frappe.get_all( + "Serial and Batch Bundle", + filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)}, + ) + + for bundle in bundles: + frappe.delete_doc("Serial and Batch Bundle", bundle.name) + def validate_deferred_income_expense_account(self): field_map = { "Sales Invoice": "deferred_revenue_account", diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 0e01b20e7c..d74d657f38 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -368,6 +368,58 @@ class TestSerialandBatchBundle(FrappeTestCase): # Batch does not belong to serial no self.assertRaises(frappe.exceptions.ValidationError, doc.save) + def test_auto_delete_draft_serial_and_batch_bundle(self): + serial_and_batch_code = "New Serial No Auto Delete 1" + make_item( + serial_and_batch_code, + { + "has_serial_no": 1, + "serial_no_series": "TEST-SER-VALL-.#####", + "is_stock_item": 1, + }, + ) + + ste = make_stock_entry( + item_code=serial_and_batch_code, + target="_Test Warehouse - _TC", + qty=1, + rate=500, + do_not_submit=True, + ) + + serial_no = "SN-TEST-AUTO-DEL" + if not frappe.db.exists("Serial No", serial_no): + frappe.get_doc( + { + "doctype": "Serial No", + "serial_no": serial_no, + "item_code": serial_and_batch_code, + "company": "_Test Company", + } + ).insert(ignore_permissions=True) + + bundle_doc = make_serial_batch_bundle( + { + "item_code": serial_and_batch_code, + "warehouse": "_Test Warehouse - _TC", + "voucher_type": "Stock Entry", + "posting_date": ste.posting_date, + "posting_time": ste.posting_time, + "qty": 1, + "serial_nos": [serial_no], + "type_of_transaction": "Inward", + "do_not_submit": True, + } + ) + + bundle_doc.reload() + ste.items[0].serial_and_batch_bundle = bundle_doc.name + ste.save() + ste.reload() + + ste.delete() + self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name)) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos From 89a0e9c2453ba4eef4158477db537771cdc9ed70 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 11 Dec 2023 10:35:31 +0530 Subject: [PATCH 060/112] fix: not able to make serial and batch using csv import (#38659) --- .../serial_and_batch_bundle.js | 2 +- .../serial_and_batch_bundle.py | 36 +++++++++++++++++-- .../serial_and_batch_entry.json | 4 +-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index cda444510a..9f01ee9ae6 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -121,7 +121,7 @@ frappe.ui.form.on('Serial and Batch Bundle', { frappe.throw(__("Please attach CSV file")); } - if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) { + if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) { frappe.throw(__("Please enter serial nos")); } }, diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 108a2e008d..67858815bb 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -511,6 +511,22 @@ class SerialandBatchBundle(Document): serial_batches = {} for row in self.entries: + if self.has_serial_no and not row.serial_no: + frappe.throw( + _("At row {0}: Serial No is mandatory for Item {1}").format( + bold(row.idx), bold(self.item_code) + ), + title=_("Serial No is mandatory"), + ) + + if self.has_batch_no and not row.batch_no: + frappe.throw( + _("At row {0}: Batch No is mandatory for Item {1}").format( + bold(row.idx), bold(self.item_code) + ), + title=_("Batch No is mandatory"), + ) + if row.serial_no: serial_nos.append(row.serial_no) @@ -794,6 +810,9 @@ def parse_csv_file_to_get_serial_batch(reader): if index == 0: has_serial_no = row[0] == "Serial No" has_batch_no = row[0] == "Batch No" + if not has_batch_no: + has_batch_no = row[1] == "Batch No" + continue if not row[0]: @@ -810,6 +829,13 @@ def parse_csv_file_to_get_serial_batch(reader): } ) + batch_nos.append( + { + "batch_no": row[1], + "qty": row[2], + } + ) + serial_nos.append(_dict) elif has_batch_no: batch_nos.append( @@ -845,6 +871,9 @@ def make_serial_nos(item_code, serial_nos): serial_nos_details = [] user = frappe.session.user for serial_no in serial_nos: + if frappe.db.exists("Serial No", serial_no): + continue + serial_nos_details.append( ( serial_no, @@ -875,7 +904,7 @@ def make_serial_nos(item_code, serial_nos): frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details)) - frappe.msgprint(_("Serial Nos are created successfully")) + frappe.msgprint(_("Serial Nos are created successfully"), alert=True) def make_batch_nos(item_code, batch_nos): @@ -886,6 +915,9 @@ def make_batch_nos(item_code, batch_nos): batch_nos_details = [] user = frappe.session.user for batch_no in batch_nos: + if frappe.db.exists("Batch", batch_no): + continue + batch_nos_details.append( (batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description) ) @@ -904,7 +936,7 @@ def make_batch_nos(item_code, batch_nos): frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details)) - frappe.msgprint(_("Batch Nos are created successfully")) + frappe.msgprint(_("Batch Nos are created successfully"), alert=True) def parse_serial_nos(data): diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json index 09565cbc8a..5de2c2ee65 100644 --- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json +++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json @@ -27,7 +27,6 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Serial No", - "mandatory_depends_on": "eval:parent.has_serial_no == 1", "options": "Serial No", "search_index": 1 }, @@ -38,7 +37,6 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Batch No", - "mandatory_depends_on": "eval:parent.has_batch_no == 1", "options": "Batch", "search_index": 1 }, @@ -122,7 +120,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-07-03 15:29:50.199075", + "modified": "2023-12-10 19:47:48.227772", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Entry", From b023e5d6b3af376a22397c51f4ec8939da463686 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 11 Dec 2023 12:33:47 +0530 Subject: [PATCH 061/112] fix: close PO on SCO close --- .../doctype/subcontracting_order/subcontracting_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 6c187f8368..0fe8c13efb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -7,6 +7,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created +from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_balance import update_bin_qty from erpnext.stock.utils import get_bin @@ -308,6 +309,9 @@ class SubcontractingOrder(SubcontractingController): "Subcontracting Order", self.name, "status", status, update_modified=update_modified ) + if status == "Closed": + update_po_status("Closed", self.purchase_order) + @frappe.whitelist() def make_subcontracting_receipt(source_name, target_doc=None): From 137b5a610880684b1c08d868072335debceb3007 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:32:49 +0100 Subject: [PATCH 062/112] fix: get customers for leaderboard --- erpnext/startup/leaderboard.py | 47 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index da7edbf814..1a508b5ced 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -54,12 +54,13 @@ def get_leaderboards(): @frappe.whitelist() def get_all_customers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]]) - return frappe.db.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Sales Invoice", fields=["customer as name", "sum(outstanding_amount) as value"], filters=filters, @@ -69,26 +70,20 @@ def get_all_customers(date_range, company, field, limit=None): ) else: if field == "total_sales_amount": - select_field = "sum(so_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_sold": - select_field = "sum(so_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "so.transaction_date") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select so.customer as name, {0} as value - FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item - ON so.name = so_item.parent - where so.docstatus = 1 {1} and so.company = %s - group by so.customer - order by value DESC - limit %s - """.format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=["customer as name", f"sum({select_field}) as value"], + filters=filters, + group_by="customer", + order_by="value desc", + limit=limit, ) @@ -236,3 +231,11 @@ def get_date_condition(date_range, field): field, frappe.db.escape(from_date), frappe.db.escape(to_date) ) return date_condition + + +def parse_date_range(date_range): + if date_range: + date_range = frappe.parse_json(date_range) + return date_range[0], date_range[1] + + return None, None From 2721ee3a8dda2e61ddf7f59698849c2e15e0e414 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:34:24 +0100 Subject: [PATCH 063/112] fix: get items for leaderboard --- erpnext/startup/leaderboard.py | 45 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 1a508b5ced..48a573116d 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -91,45 +91,46 @@ def get_all_customers(date_range, company, field, limit=None): def get_all_items(date_range, company, field, limit=None): if field in ("available_stock_qty", "available_stock_value"): select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)" - return frappe.db.get_all( + results = frappe.db.get_all( "Bin", fields=["item_code as name", "{0} as value".format(select_field)], group_by="item_code", order_by="value desc", limit=limit, ) + readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name")) + return [item for item in results if item["name"] in readable_active_items] else: if field == "total_sales_amount": - select_field = "sum(order_item.base_net_amount)" + select_field = "base_net_amount" select_doctype = "Sales Order" elif field == "total_purchase_amount": - select_field = "sum(order_item.base_net_amount)" + select_field = "base_net_amount" select_doctype = "Purchase Order" elif field == "total_qty_sold": - select_field = "sum(order_item.stock_qty)" + select_field = "stock_qty" select_doctype = "Sales Order" elif field == "total_qty_purchased": - select_field = "sum(order_item.stock_qty)" + select_field = "stock_qty" select_doctype = "Purchase Order" - date_condition = get_date_condition(date_range, "sales_order.transaction_date") + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select order_item.item_code as name, {0} as value - from `tab{1}` sales_order join `tab{1} Item` as order_item - on sales_order.name = order_item.parent - where sales_order.docstatus = 1 - and sales_order.company = %s {2} - group by order_item.item_code - order by value desc - limit %s - """.format( - select_field, select_doctype, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + child_doctype = f"{select_doctype} Item" + return frappe.get_list( + select_doctype, + fields=[ + f"`tab{child_doctype}`.item_code as name", + f"sum(`tab{child_doctype}`.{select_field}) as value", + ], + filters=filters, + order_by="value desc", + group_by=f"`tab{child_doctype}`.item_code", + limit=limit, + ) @frappe.whitelist() From 65df4b6aa84523b917e0b6e83555a7b86157e42e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:34:54 +0100 Subject: [PATCH 064/112] fix: get suppliers for leaderboard --- erpnext/startup/leaderboard.py | 44 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 48a573116d..e9b234dc66 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -135,12 +135,14 @@ def get_all_items(date_range, company, field, limit=None): @frappe.whitelist() def get_all_suppliers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) - return frappe.db.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Purchase Invoice", fields=["supplier as name", "sum(outstanding_amount) as value"], filters=filters, @@ -150,29 +152,21 @@ def get_all_suppliers(date_range, company, field, limit=None): ) else: if field == "total_purchase_amount": - select_field = "sum(purchase_order_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_purchased": - select_field = "sum(purchase_order_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "purchase_order.modified") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select purchase_order.supplier as name, {0} as value - FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item` - as purchase_order_item ON purchase_order.name = purchase_order_item.parent - where - purchase_order.docstatus = 1 - {1} - and purchase_order.company = %s - group by purchase_order.supplier - order by value DESC - limit %s""".format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + return frappe.get_list( + "Purchase Order", + fields=["supplier as name", f"sum({select_field}) as value"], + filters=filters, + group_by="supplier", + order_by="value desc", + limit=limit, + ) @frappe.whitelist() From 7babfd4ac4e3852d983487f1290799165933c4c0 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:44:24 +0100 Subject: [PATCH 065/112] fix: get sales person for leaderboard --- erpnext/startup/leaderboard.py | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index e9b234dc66..cea174495b 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,5 +1,4 @@ import frappe -from frappe.utils import cint def get_leaderboards(): @@ -196,24 +195,25 @@ def get_all_sales_partner(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_person(date_range, company, field=None, limit=0): - date_condition = get_date_condition(date_range, "sales_order.transaction_date") + filters = [ + ["docstatus", "=", "1"], + ["company", "=", company], + ["Sales Team", "sales_person", "is", "set"], + ] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select sales_team.sales_person as name, sum(sales_order.base_net_total) as value - from `tabSales Order` as sales_order join `tabSales Team` as sales_team - on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order' - where sales_order.docstatus = 1 - and sales_order.company = %s - {date_condition} - group by sales_team.sales_person - order by value DESC - limit %s - """.format( - date_condition=date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=[ + "`tabSales Team`.sales_person as name", + "sum(`tabSales Team`.allocated_amount) as value", + ], + filters=filters, + group_by="`tabSales Team`.sales_person", + order_by="value desc", + limit=limit, ) From 40c1acc961d29394fb47ef6108864c7690331457 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:45:32 +0100 Subject: [PATCH 066/112] fix: get sales partner for leaderboard --- erpnext/startup/leaderboard.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index cea174495b..9542ed6aa1 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -171,20 +171,20 @@ def get_all_suppliers(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_partner(date_range, company, field, limit=None): if field == "total_sales_amount": - select_field = "sum(`base_net_total`)" + select_field = "base_net_total" elif field == "total_commission": - select_field = "sum(`total_commission`)" + select_field = "total_commission" - filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company} - if date_range: - date_range = frappe.parse_json(date_range) - filters["transaction_date"] = ["between", [date_range[0], date_range[1]]] + filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) return frappe.get_list( "Sales Order", fields=[ - "`sales_partner` as name", - "{} as value".format(select_field), + "sales_partner as name", + f"sum({select_field}) as value", ], filters=filters, group_by="sales_partner", From 956c3c50a09036dbeb9bfdf7ecd42958bdb0ec3e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:46:37 +0100 Subject: [PATCH 067/112] chore: deprecate unused method --- erpnext/startup/leaderboard.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 9542ed6aa1..5a60d2ff96 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,4 +1,5 @@ import frappe +from frappe.utils.deprecations import deprecated def get_leaderboards(): @@ -217,6 +218,7 @@ def get_all_sales_person(date_range, company, field=None, limit=0): ) +@deprecated def get_date_condition(date_range, field): date_condition = "" if date_range: From 6ad298adfcac1932a7e0b37b8cab3bfdfcffd32a Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 12 Dec 2023 05:25:15 +0100 Subject: [PATCH 068/112] fix: typo in unittest (#38673) --- .../repost_accounting_ledger/test_repost_accounting_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index dda0ec778f..1bce1a6199 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -22,7 +22,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.create_item() self.update_repost_settings() - def teadDown(self): + def tearDown(self): frappe.db.rollback() def update_repost_settings(self): From cc15f695b428945fa1e3d3f29a3e9cbdb46853d4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 10:20:26 +0530 Subject: [PATCH 069/112] refactor(test): repost utility deletion flag test --- .../test_repost_accounting_ledger.py | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index dda0ec778f..2c24b760f2 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -177,26 +177,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): pe = get_payment_entry(si.doctype, si.name) pe.save().submit() - # without deletion flag set - ral = frappe.new_doc("Repost Accounting Ledger") - ral.company = self.company - ral.delete_cancelled_entries = False - ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) - ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) - ral.save() - - # assert preview data is generated - preview = ral.generate_preview() - self.assertIsNotNone(preview) - - ral.save().submit() - - # background jobs don't run on test cases. Manually triggering repost function. - start_repost(ral.name) - - self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) - self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) - # with deletion flag set ral = frappe.new_doc("Repost Accounting Ledger") ral.company = self.company @@ -208,3 +188,29 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): start_repost(ral.name) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + + def test_05_without_deletion_flag(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + ) + + pe = get_payment_entry(si.doctype, si.name) + pe.save().submit() + + # without deletion flag set + ral = frappe.new_doc("Repost Accounting Ledger") + ral.company = self.company + ral.delete_cancelled_entries = False + ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) + ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) + ral.save().submit() + + start_repost(ral.name) + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) From a97b3db749b2850659c733d0cddebfc92fb38cb8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 10:34:51 +0530 Subject: [PATCH 070/112] refactor: remove explicit commit on repost --- .../repost_accounting_ledger/repost_accounting_ledger.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 8c23c67c20..e9d2fdca55 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -170,8 +170,6 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries(1) doc.make_gl_entries() - frappe.db.commit() - def get_allowed_types_from_settings(): return [ From 780c4278e6c3a5d89814479c57fed02d0ccd3b89 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Dec 2023 11:24:07 +0530 Subject: [PATCH 071/112] fix: serial no filter in the Serial No Ledger report (#38669) --- .../serial_no_ledger/serial_no_ledger.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index ae12fbb3e4..810dc4666f 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -1,9 +1,12 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import copy + import frappe from frappe import _ +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle from erpnext.stock.stock_ledger import get_stock_ledger_entries @@ -15,8 +18,8 @@ def execute(filters=None): def get_columns(filters): columns = [ - {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"}, - {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"}, + {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120}, + {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90}, { "label": _("Voucher Type"), "fieldtype": "Link", @@ -29,7 +32,7 @@ def get_columns(filters): "fieldtype": "Dynamic Link", "fieldname": "voucher_no", "options": "voucher_type", - "width": 180, + "width": 230, }, { "label": _("Company"), @@ -49,7 +52,7 @@ def get_columns(filters): "label": _("Status"), "fieldtype": "Data", "fieldname": "status", - "width": 120, + "width": 90, }, { "label": _("Serial No"), @@ -62,7 +65,7 @@ def get_columns(filters): "label": _("Valuation Rate"), "fieldtype": "Float", "fieldname": "valuation_rate", - "width": 150, + "width": 130, }, { "label": _("Qty"), @@ -102,15 +105,29 @@ def get_data(filters): } ) - serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}] + serial_nos = [] + if row.serial_no: + parsed_serial_nos = get_serial_nos_from_sle(row.serial_no) + for serial_no in parsed_serial_nos: + if filters.get("serial_no") and filters.get("serial_no") != serial_no: + continue + + serial_nos.append( + { + "serial_no": serial_no, + "valuation_rate": abs(row.stock_value_difference / row.actual_qty), + } + ) + if row.serial_and_batch_bundle: - serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []) + serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])) for index, bundle_data in enumerate(serial_nos): if index == 0: - args.serial_no = bundle_data.get("serial_no") - args.valuation_rate = bundle_data.get("valuation_rate") - data.append(args) + new_args = copy.deepcopy(args) + new_args.serial_no = bundle_data.get("serial_no") + new_args.valuation_rate = bundle_data.get("valuation_rate") + data.append(new_args) else: data.append( { From ccff58856390aa56e1ae104a9c53712fb774b704 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 11:30:33 +0530 Subject: [PATCH 072/112] refactor: increase limit and remove explicit call to start_repost --- .../repost_accounting_ledger/repost_accounting_ledger.py | 2 +- .../test_repost_accounting_ledger.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index e9d2fdca55..7aa631bc48 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -126,7 +126,7 @@ class RepostAccountingLedger(Document): return rendered_page def on_submit(self): - if len(self.vouchers) > 1: + if len(self.vouchers) > 5: job_name = "repost_accounting_ledger_" + self.name frappe.enqueue( method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 2c24b760f2..4caefadb10 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -90,9 +90,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): # Submit repost document ral.save().submit() - # background jobs don't run on test cases. Manually triggering repost function. - start_repost(ral.name) - res = ( qb.from_(gl) .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) @@ -185,7 +182,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.save().submit() - start_repost(ral.name) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) @@ -211,6 +207,5 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.save().submit() - start_repost(ral.name) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) From acb6e8e1209b7ac97e0503ad89d9f52b8a8e28d5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 11:37:06 +0530 Subject: [PATCH 073/112] refactor(test): update repost settings before test case --- .../test_repost_accounting_ledger.py | 17 +++++++++-------- .../doctype/sales_invoice/test_sales_invoice.py | 6 ++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 4caefadb10..471a463d95 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.create_company() self.create_customer() self.create_item() - self.update_repost_settings() + update_repost_settings() def teadDown(self): frappe.db.rollback() - def update_repost_settings(self): - allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] - repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") - for x in allowed_types: - repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) - repost_settings.save() - def test_01_basic_functions(self): si = create_sales_invoice( item=self.item, @@ -209,3 +202,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + + +def update_repost_settings(): + allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] + repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") + for x in allowed_types: + repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) + repost_settings.save() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e9b71ddffd..6163749c4f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2793,6 +2793,12 @@ class TestSalesInvoice(FrappeTestCase): @change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self): + from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import ( + update_repost_settings, + ) + + update_repost_settings() + additional_discount_account = create_account( account_name="Discount Account", parent_account="Indirect Expenses - _TC", From 15c90551b68f5cee549a1b53e564012b394e37cb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 13:35:41 +0530 Subject: [PATCH 074/112] refactor: add `get_list` for virtual child doctypes --- .../payment_reconciliation_allocation.py | 4 +++- .../payment_reconciliation_invoice.py | 4 +++- .../payment_reconciliation_payment.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py index a4141afb37..b57ebecbac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py @@ -34,4 +34,6 @@ class PaymentReconciliationAllocation(Document): unreconciled_amount: DF.Currency # end: auto-generated types - pass + @staticmethod + def get_list(args): + pass diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py index 1e9f6cec82..fa18ccd135 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py @@ -26,4 +26,6 @@ class PaymentReconciliationInvoice(Document): parenttype: DF.Data # end: auto-generated types - pass + @staticmethod + def get_list(args): + pass diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py index aa956fe59b..4ab80ecaaf 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py @@ -30,4 +30,6 @@ class PaymentReconciliationPayment(Document): remark: DF.SmallText | None # end: auto-generated types - pass + @staticmethod + def get_list(args): + pass From 2588970d5576bbfa085a1f30cf098d7e18e71a84 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Dec 2023 15:18:54 +0530 Subject: [PATCH 075/112] fix: Init internal child table values --- erpnext/controllers/accounts_controller.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index abcea44a2f..05c6a359c6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -166,6 +166,7 @@ class AccountsController(TransactionBase): self.disable_pricing_rule_on_internal_transfer() self.disable_tax_included_prices_for_internal_transfer() self.set_incoming_rate() + self.init_internal_values() if self.meta.get_field("currency"): self.calculate_taxes_and_totals() @@ -225,6 +226,16 @@ class AccountsController(TransactionBase): self.set_total_in_words() + def init_internal_values(self): + # init all the internal values as 0 on sa + if self.docstatus.is_draft(): + # TODO: Add all such pending values here + fields = ["billed_amt", "delivered_qty"] + for item in self.get("items"): + for field in fields: + if hasattr(item, field): + item.set(field, 0) + def before_cancel(self): validate_einvoice_fields(self) From fa2d33cb50753449cc8f4218c94d07ed99f2af03 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 15:43:39 +0530 Subject: [PATCH 076/112] fix: typeerror on new sites (#38692) --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 25fc754b9a..e9d06dfbec 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -8,7 +8,7 @@ $.extend(erpnext, { if(!company && cur_frm) company = cur_frm.doc.company; if(company) - return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency; + return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency; else return frappe.boot.sysdefaults.currency; }, From 69d7a640ee7da732d7983ecdd2574acfbc711fa1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Dec 2023 16:18:32 +0530 Subject: [PATCH 077/112] fix: negative batch issue (#38688) --- .../serial_and_batch_bundle.py | 7 +++- .../doctype/stock_entry/test_stock_entry.py | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 67858815bb..ecb9314fbb 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -709,6 +709,7 @@ class SerialandBatchBundle(Document): "item_code": self.item_code, "warehouse": self.warehouse, "batch_no": batches, + "consider_negative_batches": True, } ) ) @@ -719,6 +720,9 @@ class SerialandBatchBundle(Document): available_batches = get_available_batches_qty(available_batches) for batch_no in batches: if batch_no not in available_batches or available_batches[batch_no] < 0: + if flt(available_batches.get(batch_no)) < 0: + self.validate_negative_batch(batch_no, available_batches[batch_no]) + self.throw_error_message( f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}" ) @@ -1491,7 +1495,8 @@ def get_auto_batch_nos(kwargs): available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches ) - available_batches = list(filter(lambda x: x.qty > 0, available_batches)) + if not kwargs.consider_negative_batches: + available_batches = list(filter(lambda x: x.qty > 0, available_batches)) if not qty: return available_batches diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index eb1c7a85eb..e0e364f397 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1733,6 +1733,45 @@ class TestStockEntry(FrappeTestCase): self.assertFalse(doc.is_enqueue_action()) frappe.flags.in_test = True + def test_negative_batch(self): + item_code = "Test Negative Batch Item - 001" + make_item( + item_code, + {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"}, + ) + + se1 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se1.reload() + + batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + + se2 = make_stock_entry( + item_code=item_code, + purpose="Material Issue", + batch_no=batch_no, + qty=10, + source="_Test Warehouse - _TC", + ) + + se2.reload() + + se3 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se3.reload() + + self.assertRaises(frappe.ValidationError, se1.cancel) + def make_serialized_item(**args): args = frappe._dict(args) From b562b4cf99e68108a0d7ba3a90e60ef3d7d01b0f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Dec 2023 16:41:10 +0530 Subject: [PATCH 078/112] fix: show stock qty in popup (#38698) --- erpnext/public/js/utils/serial_no_batch_selector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 0de6774393..3b9a551b43 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -31,7 +31,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { secondary_action: () => this.edit_full_form(), }); - this.dialog.set_value("qty", this.item.qty).then(() => { + let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; + this.dialog.set_value("qty", qty).then(() => { if (this.item.serial_no) { this.dialog.set_value("scan_serial_no", this.item.serial_no); frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', ''); From e7984b3ef90da98506e8c79303ac804e8d037085 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 12 Dec 2023 20:53:37 +0530 Subject: [PATCH 079/112] fix: 1st row depr. sch. value of asset put to less than 180 days acc. to I.T. S. 32 (#38696) fix: 1st row value of asset put to less than 180 days acc. to IT S. 32 --- .../asset_depreciation_schedule.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 0021140282..67234ccd84 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -340,6 +340,10 @@ class AssetDepreciationSchedule(Document): n == 0 and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and not self.opening_accumulated_depreciation + and get_updated_rate_of_depreciation_for_wdv_and_dd( + asset_doc, value_after_depreciation, row, False + ) + == row.rate_of_depreciation ): from_date = add_days( asset_doc.available_for_use_date, -1 @@ -605,7 +609,9 @@ def get_depreciation_amount( @erpnext.allow_regional -def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row): +def get_updated_rate_of_depreciation_for_wdv_and_dd( + asset, depreciable_value, fb_row, show_msg=True +): return fb_row.rate_of_depreciation From db24e2488247eef326ef52a88dbae8e828893e7e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Dec 2023 14:06:45 +0530 Subject: [PATCH 080/112] fix: supplier removed on selection of item (#38712) --- erpnext/stock/get_item_details.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index b5469eedb5..e746595921 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -358,7 +358,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "net_amount": 0.0, "discount_percentage": 0.0, "discount_amount": flt(args.discount_amount) or 0.0, - "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "update_stock": args.get("update_stock") if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"] else 0, @@ -378,6 +377,10 @@ def get_basic_details(args, item, overwrite_warehouse=True): } ) + default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults) + if default_supplier: + out.supplier = default_supplier + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): out.update(calculate_service_end_date(args, item)) From 13cba5068bd1d09bff4204a64a74999876566b6b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Dec 2023 15:08:16 +0530 Subject: [PATCH 081/112] fix: barcode scanning for the stock entry (#38716) --- erpnext/public/js/controllers/transaction.js | 1 + erpnext/public/js/utils/barcode_scanner.js | 12 ++++++------ erpnext/public/js/utils/serial_no_batch_selector.js | 5 ++++- erpnext/stock/doctype/stock_entry/stock_entry.js | 8 ++++---- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 156c05ba41..2d747ab7bb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -380,6 +380,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } scan_barcode() { + frappe.flags.dialog_set = false; const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); barcode_scanner.process_scan(); } diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index a4f74bdaee..a1ebfe9aa4 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -114,13 +114,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { frappe.run_serially([ () => this.set_selector_trigger_flag(data), + () => this.set_serial_no(row, serial_no), + () => this.set_batch_no(row, batch_no), + () => this.set_barcode(row, barcode), () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => { this.show_scan_message(row.idx, row.item_code, qty); }), () => this.set_barcode_uom(row, uom), - () => this.set_serial_no(row, serial_no), - () => this.set_batch_no(row, batch_no), - () => this.set_barcode(row, barcode), () => this.clean_up(), () => this.revert_selector_flag(), () => resolve(row) @@ -131,10 +131,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { // batch and serial selector is reduandant when all info can be added by scan // this flag on item row is used by transaction.js to avoid triggering selector set_selector_trigger_flag(data) { - const {batch_no, serial_no, has_batch_no, has_serial_no} = data; + const {has_batch_no, has_serial_no} = data; - const require_selecting_batch = has_batch_no && !batch_no; - const require_selecting_serial = has_serial_no && !serial_no; + const require_selecting_batch = has_batch_no; + const require_selecting_serial = has_serial_no; if (!(require_selecting_batch || require_selecting_serial)) { frappe.flags.hide_serial_batch_dialog = true; diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 3b9a551b43..7b9cdfef2a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -31,6 +31,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { secondary_action: () => this.edit_full_form(), }); + this.dialog.show(); + let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; this.dialog.set_value("qty", qty).then(() => { if (this.item.serial_no) { @@ -40,9 +42,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { this.dialog.set_value("scan_batch_no", this.item.batch_no); frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', ''); } + + this.dialog.fields_dict.entries.grid.refresh(); }); - this.dialog.show(); this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); this.$scan_btn.css("display", "inline"); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 7334b356ee..7af5d1aa37 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -781,10 +781,9 @@ frappe.ui.form.on('Stock Entry Detail', { }); refresh_field("items"); - let no_batch_serial_number_value = !d.serial_no; - if (d.has_batch_no && !d.has_serial_no) { - // check only batch_no for batched item - no_batch_serial_number_value = !d.batch_no; + let no_batch_serial_number_value = false; + if (d.has_serial_no || d.has_batch_no) { + no_batch_serial_number_value = true; } if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) { @@ -941,6 +940,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } scan_barcode() { + frappe.flags.dialog_set = false; const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); barcode_scanner.process_scan(); } From 1b3ba25220ca4ef96df5416331ee2f78376da64a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 13 Dec 2023 11:25:21 +0530 Subject: [PATCH 082/112] fix: fetch exc rate of multi currency journals --- .../payment_reconciliation.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index fbc4d245cb..ed0921ba5b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -594,6 +594,27 @@ class PaymentReconciliation(Document): invoice_exchange_map.update(purchase_invoice_map) + journals = [ + d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry" + ] + journals.extend( + [d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"] + ) + if journals: + journals = list(set(journals)) + journals_map = frappe._dict( + frappe.db.get_all( + "Journal Entry Account", + filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])}, + fields=[ + "parent as `name`", + "exchange_rate", + ], + as_list=1, + ) + ) + invoice_exchange_map.update(journals_map) + return invoice_exchange_map def validate_allocation(self): From 27f05145ae700e1177bb2c2541e6d49f73cdbf7e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:00:58 +0100 Subject: [PATCH 083/112] feat: RFQ print preview --- .../request_for_quotation/request_for_quotation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index eea8cd5cc8..5b8be44296 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -119,6 +119,15 @@ class RequestforQuotation(BuyingController): supplier.quote_status = "Pending" self.send_to_supplier() + def before_print(self, settings=None): + """Use the first suppliers data to render the print preview.""" + if self.vendor or not self.suppliers: + # If a specific supplier is already set, via Tools > Download PDF, + # we don't want to override it. + return + + self.update_supplier_part_no(self.suppliers[0].supplier) + def on_cancel(self): self.db_set("status", "Cancelled") From 6851c5042fc5e2d3f45d070af18a33c24fc10794 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 13 Dec 2023 23:14:20 +0530 Subject: [PATCH 084/112] Revert "fix(ux): don't update qty blindly" (#38728) --- erpnext/public/js/controllers/transaction.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 2d747ab7bb..39357834ed 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -472,6 +472,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", + child: item, args: { doc: me.frm.doc, args: { @@ -521,19 +522,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ - () => { - var child = locals[cdt][cdn]; - var std_field_list = ["doctype"] - .concat(frappe.model.std_fields_list) - .concat(frappe.model.child_table_field_list); - - for (var key in r.message) { - if (std_field_list.indexOf(key) === -1) { - if (key === "qty" && child[key]) continue; - child[key] = r.message[key]; - } - } - }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); From f53ba178a8b0909373c378db31d01928c576ee89 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 14 Dec 2023 12:28:32 +0530 Subject: [PATCH 085/112] fix: show bill_date and bill_no in Purchase Register --- erpnext/accounts/report/purchase_register/purchase_register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 9721987f89..39eb312e4a 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -89,6 +89,8 @@ def _execute(filters=None, additional_table_columns=None): "payable_account": inv.credit_to, "mode_of_payment": inv.mode_of_payment, "project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project, + "bill_no": inv.bill_no, + "bill_date": inv.bill_date, "remarks": inv.remarks, "purchase_order": ", ".join(purchase_order), "purchase_receipt": ", ".join(purchase_receipt), From eaf86a6461438720fe941100d6feccefbfa3bfed Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 14 Dec 2023 15:53:57 +0530 Subject: [PATCH 086/112] fix: timezone aware SLA banner (#38745) --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e9d06dfbec..b0ea56833b 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -1077,7 +1077,7 @@ function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) { } function get_time_left(timestamp, agreement_status) { - const diff = moment(timestamp).diff(moment()); + const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true)); const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed'; let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green'; return {'diff_display': diff_display, 'indicator': indicator}; From d6201ce5c7b6f981ca6e0adbfa52c1c24ba5de83 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Dec 2023 22:47:32 +0530 Subject: [PATCH 087/112] fix: homepage not working (#38755) --- erpnext/portal/doctype/homepage/homepage.js | 8 -------- erpnext/templates/pages/home.html | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js index 6797904424..6739979b98 100644 --- a/erpnext/portal/doctype/homepage/homepage.js +++ b/erpnext/portal/doctype/homepage/homepage.js @@ -2,14 +2,6 @@ // For license information, please see license.txt frappe.ui.form.on('Homepage', { - setup: function(frm) { - frm.fields_dict["products"].grid.get_field("item").get_query = function() { - return { - filters: {'published': 1} - } - } - }, - refresh: function(frm) { frm.add_custom_button(__('Set Meta Tags'), () => { frappe.utils.set_meta_tag('home'); diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html index 08e0432dcf..b9b435c7c3 100644 --- a/erpnext/templates/pages/home.html +++ b/erpnext/templates/pages/home.html @@ -26,6 +26,26 @@ {{ render_homepage_section(homepage.hero_section_doc) }} {% endif %} + {% if homepage.products %} +
+

{{ _('Products') }}

+ +
+ {% for item in homepage.products %} +
+
+ {{ item.item_name }} +
+
{{ item.item_name }}
+ {{ _('More details') }} +
+
+
+ {% endfor %} +
+
+ {% endif %} + {% if blogs %}

{{ _('Publications') }}

From 726ac6bda1ee3b25c1d62b312d96aa32466ba11e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 13 Dec 2023 15:21:47 +0530 Subject: [PATCH 088/112] fix(ux): don't override Item Name and Description in MR --- erpnext/stock/doctype/material_request/material_request.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 9673a70501..d90b71a47a 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -199,9 +199,8 @@ frappe.ui.form.on('Material Request', { get_item_data: function(frm, item, overwrite_warehouse=false) { if (item && !item.item_code) { return; } - frm.call({ + frappe.call({ method: "erpnext.stock.get_item_details.get_item_details", - child: item, args: { args: { item_code: item.item_code, From 5eeb650dfd3ab4b20f49bacefc43d98cb856c3f3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Dec 2023 11:46:12 +0530 Subject: [PATCH 089/112] fix: validation error on reconciling PE to Journals as Invoice With the same exchange rate on Journal Entry and Payment Entry, reconcilition should not post exc gain/loss journal and should not throw validation error --- erpnext/accounts/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6b5982769a..0f21c864b0 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -662,8 +662,10 @@ def update_reference_in_payment_entry( "total_amount": d.grand_total, "outstanding_amount": d.outstanding_amount, "allocated_amount": d.allocated_amount, - "exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(), - "exchange_gain_loss": d.exchange_gain_loss, + "exchange_rate": d.exchange_rate + if d.difference_amount is not None + else payment_entry.get_exchange_rate(), + "exchange_gain_loss": d.difference_amount, "account": d.account, } From f7b2380ec1cbe5e58755f88ca08cb052b92e05c7 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 15 Dec 2023 12:30:15 +0530 Subject: [PATCH 090/112] fix: skip jvs against bank accounts --- .../tax_withholding_details.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index ba946c3bcf..d045d91f52 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -345,21 +345,16 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts): if filters.get("party"): party = [filters.get("party")] - query = query.where( - ((gle.account.isin(tds_accounts) & gle.against.isin(party))) - | ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))) - | gle.party.isin(party) + jv_condition = gle.against.isin(party) | ( + (gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")) ) else: party = frappe.get_all(filters.get("party_type"), pluck="name") - query = query.where( - ((gle.account.isin(tds_accounts) & gle.against.isin(party))) - | ( - (gle.voucher_type == "Journal Entry") - & ((gle.party_type == filters.get("party_type")) | (gle.party_type == "")) - ) - | gle.party.isin(party) + jv_condition = gle.against.isin(party) | ( + (gle.voucher_type == "Journal Entry") + & ((gle.party_type == filters.get("party_type")) | (gle.party_type == "")) ) + query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party)) return query From 1704180f38802ba81e9c912455e74d9a0595233e Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Fri, 15 Dec 2023 14:43:33 +0530 Subject: [PATCH 091/112] fix: asset patch failure due to missing shift_based column (#38776) * fix: add missing daily_prorata_based in get_asset_finance_books_map * fix: reload Asset Finance Book doctype --- .../v15_0/create_asset_depreciation_schedules_from_assets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index 793497b766..ddce997d11 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -3,6 +3,7 @@ import frappe def execute(): frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule") + frappe.reload_doc("assets", "doctype", "Asset Finance Book") assets = get_details_of_draft_or_submitted_depreciable_assets() @@ -86,6 +87,7 @@ def get_asset_finance_books_map(): afb.frequency_of_depreciation, afb.rate_of_depreciation, afb.expected_value_after_useful_life, + afb.daily_prorata_based, afb.shift_based, ) .where(asset.docstatus < 2) From 5a83a16e60b2c47f8ef0307edcd0311824007c86 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 15 Dec 2023 14:45:09 +0530 Subject: [PATCH 092/112] fix: wrong currency in Stock Balance report --- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index a59f9de42e..ed84a5c2d5 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -413,7 +413,7 @@ class StockBalanceReport(object): "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, - "options": "currency", + "options": "Company:company:default_currency", }, { "label": _("Opening Qty"), @@ -427,7 +427,7 @@ class StockBalanceReport(object): "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, - "options": "currency", + "options": "Company:company:default_currency", }, { "label": _("In Qty"), From fa1c7b663c2e3f433190d29017eaebbe15d4c604 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 15 Dec 2023 22:00:01 +0530 Subject: [PATCH 093/112] fix: Reset SLA on issue doesn't work (#38789) This was broken since last refactor where it was spun off to work with all types of doctypes but client side code was never adapted. --- erpnext/support/doctype/issue/issue.js | 4 +++- .../service_level_agreement/service_level_agreement.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index f96823b290..9f91dc1726 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -58,7 +58,9 @@ frappe.ui.form.on("Issue", { frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", { reason: values.reason, - user: frappe.session.user_email + user: frappe.session.user_email, + doctype: frm.doc.doctype, + docname: frm.doc.name, }, () => { reset_sla.enable_primary_action(); frm.refresh(); 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 8b37c9478c..f6b3a1311b 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -774,10 +774,12 @@ def get_response_and_resolution_duration(doc): return priority -def reset_service_level_agreement(doc, reason, user): +@frappe.whitelist() +def reset_service_level_agreement(doctype: str, docname: str, reason, user): if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"): frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings.")) + doc = frappe.get_doc(doctype, docname) frappe.get_doc( { "doctype": "Comment", From 5cb5e09dbbac878906023c07423d5d8233279790 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 16 Dec 2023 04:35:43 +0000 Subject: [PATCH 094/112] fix: wrong paid and cn amount on pos invoice --- .../report/accounts_receivable/accounts_receivable.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 7948e5f465..6826c5c982 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -244,8 +244,12 @@ class ReceivablePayableReport(object): row.invoiced_in_account_currency += amount_in_account_currency else: if self.is_invoice(ple): - row.credit_note -= amount - row.credit_note_in_account_currency -= amount_in_account_currency + if row.voucher_no == ple.voucher_no == ple.against_voucher_no: + row.paid -= amount + row.paid_in_account_currency -= amount_in_account_currency + else: + row.credit_note -= amount + row.credit_note_in_account_currency -= amount_in_account_currency else: row.paid -= amount row.paid_in_account_currency -= amount_in_account_currency From 259f313af75c3cbdcc1af75a8e7d2ad1a3a90c0c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 16 Dec 2023 21:11:54 +0100 Subject: [PATCH 095/112] fix: groups for current accounts in German CoAs --- .../verified/de_kontenplan_SKR03_gnucash.json | 14 +++++++++++-- ..._kontenplan_SKR04_with_account_number.json | 20 +++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json index 741d4283e2..daf2e21d78 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json @@ -53,8 +53,13 @@ }, "II. Forderungen und sonstige Vermögensgegenstände": { "is_group": 1, - "Ford. a. Lieferungen und Leistungen": { + "Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "account_number": "1400", + "account_type": "Receivable", + "is_group": 1 + }, + "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": { + "account_number": "1410", "account_type": "Receivable" }, "Durchlaufende Posten": { @@ -180,8 +185,13 @@ }, "IV. Verbindlichkeiten aus Lieferungen und Leistungen": { "is_group": 1, - "Verbindlichkeiten aus Lieferungen u. Leistungen": { + "Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": { "account_number": "1600", + "account_type": "Payable", + "is_group": 1 + }, + "Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": { + "account_number": "1610", "account_type": "Payable" } }, diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json index 2bf55cfcd0..000ef80ee3 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json @@ -407,13 +407,10 @@ "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "account_number": "9960" }, - "Debitoren": { - "is_group": 1, - "account_number": "10000" - }, - "Forderungen aus Lieferungen und Leistungen": { + "Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "account_number": "1200", - "account_type": "Receivable" + "account_type": "Receivable", + "is_group": 1 }, "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": { "account_number": "1210" @@ -1138,18 +1135,15 @@ "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "account_number": "9964" }, - "Kreditoren": { - "account_number": "70000", + "Verb. aus Lieferungen und Leistungen mit Kontokorrent": { + "account_number": "3300", + "account_type": "Payable", "is_group": 1, - "Wareneingangs-­Verrechnungskonto" : { + "Wareneingangs-Verrechnungskonto" : { "account_number": "70001", "account_type": "Stock Received But Not Billed" } }, - "Verb. aus Lieferungen und Leistungen": { - "account_number": "3300", - "account_type": "Payable" - }, "Verb. aus Lieferungen und Leistungen ohne Kontokorrent": { "account_number": "3310" }, From 0743289925d0866a16373c05cfb81825b58e35ba Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 17 Dec 2023 12:42:07 +0530 Subject: [PATCH 096/112] fix: serial and batch bundle return not working (#38754) * fix: serial and batch bundle return not working * test: added test case for delivery note return against denormalized serial no --- .../controllers/sales_and_purchase_return.py | 191 ++++++++++++------ erpnext/controllers/selling_controller.py | 4 + erpnext/controllers/stock_controller.py | 6 + erpnext/public/js/controllers/buying.js | 14 +- erpnext/public/js/utils/sales_common.js | 7 +- .../js/utils/serial_no_batch_selector.js | 49 +++-- .../delivery_note/test_delivery_note.py | 109 ++++++++++ .../serial_and_batch_bundle.py | 107 +++++++++- .../stock/doctype/serial_no/serial_no.json | 28 +-- erpnext/stock/doctype/serial_no/serial_no.py | 22 +- .../stock_ledger_entry/stock_ledger_entry.py | 3 + erpnext/stock/serial_batch_bundle.py | 53 ++++- 12 files changed, 460 insertions(+), 133 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 81e71e3aa2..81080f0266 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -8,6 +8,8 @@ from frappe.model.meta import get_field_precision from frappe.utils import flt, format_datetime, get_datetime import erpnext +from erpnext.stock.serial_batch_bundle import get_batches_from_bundle +from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle from erpnext.stock.utils import get_incoming_rate @@ -69,8 +71,6 @@ def validate_return_against(doc): def validate_returned_items(doc): - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - valid_items = frappe._dict() select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor" @@ -123,26 +123,6 @@ def validate_returned_items(doc): ) ) - elif ref.batch_no and d.batch_no not in ref.batch_no: - frappe.throw( - _("Row # {0}: Batch No must be same as {1} {2}").format( - d.idx, doc.doctype, doc.return_against - ) - ) - - elif ref.serial_no: - if d.qty and not d.serial_no: - frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx)) - else: - serial_nos = get_serial_nos(d.serial_no) - for s in serial_nos: - if s not in ref.serial_no: - frappe.throw( - _("Row # {0}: Serial No {1} does not match with {2} {3}").format( - d.idx, s, doc.doctype, doc.return_against - ) - ) - if ( warehouse_mandatory and not d.get("warehouse") @@ -397,71 +377,92 @@ def make_return_doc( else: doc.run_method("calculate_taxes_and_totals") - def update_item(source_doc, target_doc, source_parent): + def update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.serial_batch_bundle import SerialBatchCreation - target_doc.qty = -1 * source_doc.qty - item_details = frappe.get_cached_value( - "Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1 - ) - returned_serial_nos = [] - if source_doc.get("serial_and_batch_bundle"): - if item_details.has_serial_no: - returned_serial_nos = get_returned_serial_nos(source_doc, source_parent) + returned_batches = frappe._dict() + serial_and_batch_field = ( + "serial_and_batch_bundle" if qty_field == "stock_qty" else "rejected_serial_and_batch_bundle" + ) + old_serial_no_field = "serial_no" if qty_field == "stock_qty" else "rejected_serial_no" + old_batch_no_field = "batch_no" - type_of_transaction = "Inward" - if ( - frappe.db.get_value( - "Serial and Batch Bundle", source_doc.serial_and_batch_bundle, "type_of_transaction" - ) - == "Inward" - ): - type_of_transaction = "Outward" - - cls_obj = SerialBatchCreation( - { - "type_of_transaction": type_of_transaction, - "serial_and_batch_bundle": source_doc.serial_and_batch_bundle, - "returned_against": source_doc.name, - "item_code": source_doc.item_code, - "returned_serial_nos": returned_serial_nos, - } - ) - - cls_obj.duplicate_package() - if cls_obj.serial_and_batch_bundle: - target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle - - if source_doc.get("rejected_serial_and_batch_bundle"): + if ( + source_doc.get(serial_and_batch_field) + or source_doc.get(old_serial_no_field) + or source_doc.get(old_batch_no_field) + ): if item_details.has_serial_no: returned_serial_nos = get_returned_serial_nos( - source_doc, source_parent, serial_no_field="rejected_serial_and_batch_bundle" + source_doc, source_parent, serial_no_field=serial_and_batch_field + ) + else: + returned_batches = get_returned_batches( + source_doc, source_parent, batch_no_field=serial_and_batch_field ) type_of_transaction = "Inward" - if ( + if source_doc.get(serial_and_batch_field) and ( frappe.db.get_value( - "Serial and Batch Bundle", source_doc.rejected_serial_and_batch_bundle, "type_of_transaction" + "Serial and Batch Bundle", source_doc.get(serial_and_batch_field), "type_of_transaction" ) == "Inward" ): type_of_transaction = "Outward" + elif source_parent.doctype in [ + "Purchase Invoice", + "Purchase Receipt", + "Subcontracting Receipt", + ]: + type_of_transaction = "Outward" cls_obj = SerialBatchCreation( { "type_of_transaction": type_of_transaction, - "serial_and_batch_bundle": source_doc.rejected_serial_and_batch_bundle, + "serial_and_batch_bundle": source_doc.get(serial_and_batch_field), "returned_against": source_doc.name, "item_code": source_doc.item_code, "returned_serial_nos": returned_serial_nos, + "voucher_type": source_parent.doctype, + "do_not_submit": True, + "warehouse": source_doc.warehouse, + "has_serial_no": item_details.has_serial_no, + "has_batch_no": item_details.has_batch_no, } ) - cls_obj.duplicate_package() - if cls_obj.serial_and_batch_bundle: - target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle + serial_nos = [] + batches = frappe._dict() + if source_doc.get(old_batch_no_field): + batches = frappe._dict({source_doc.batch_no: source_doc.get(qty_field)}) + elif source_doc.get(old_serial_no_field): + serial_nos = get_serial_nos(source_doc.get(old_serial_no_field)) + elif source_doc.get(serial_and_batch_field): + if item_details.has_serial_no: + serial_nos = get_serial_nos_from_bundle(source_doc.get(serial_and_batch_field)) + else: + batches = get_batches_from_bundle(source_doc.get(serial_and_batch_field)) + if serial_nos: + cls_obj.serial_nos = sorted(list(set(serial_nos) - set(returned_serial_nos))) + elif batches: + for batch in batches: + if batch in returned_batches: + batches[batch] -= flt(returned_batches.get(batch)) + + cls_obj.batches = batches + + if source_doc.get(serial_and_batch_field): + cls_obj.duplicate_package() + if cls_obj.serial_and_batch_bundle: + target_doc.set(serial_and_batch_field, cls_obj.serial_and_batch_bundle) + else: + target_doc.set(serial_and_batch_field, cls_obj.make_serial_and_batch_bundle().name) + + def update_item(source_doc, target_doc, source_parent): + target_doc.qty = -1 * source_doc.qty if doctype in ["Purchase Receipt", "Subcontracting Receipt"]: returned_qty_map = get_returned_qty_map_for_row( source_parent.name, source_parent.supplier, source_doc.name, doctype @@ -561,6 +562,17 @@ def make_return_doc( if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return + item_details = frappe.get_cached_value( + "Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1 + ) + + if not item_details.has_batch_no and not item_details.has_serial_no: + return + + for qty_field in ["stock_qty", "rejected_qty"]: + if target_doc.get(qty_field): + update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field) + def update_terms(source_doc, target_doc, source_parent): target_doc.payment_amount = -source_doc.payment_amount @@ -716,6 +728,9 @@ def get_returned_serial_nos( [parent_doc.doctype, "docstatus", "=", 1], ] + if serial_no_field == "rejected_serial_and_batch_bundle": + filters.append([child_doc.doctype, "rejected_qty", ">", 0]) + # Required for POS Invoice if ignore_voucher_detail_no: filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no]) @@ -723,9 +738,57 @@ def get_returned_serial_nos( ids = [] for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters): ids.append(row.get("serial_and_batch_bundle")) - if row.get(old_field): + if row.get(old_field) and not row.get(serial_no_field): serial_nos.extend(get_serial_nos_from_serial_no(row.get(old_field))) - serial_nos.extend(get_serial_nos(ids)) + if ids: + serial_nos.extend(get_serial_nos(ids)) return serial_nos + + +def get_returned_batches( + child_doc, parent_doc, batch_no_field=None, ignore_voucher_detail_no=None +): + from erpnext.stock.serial_batch_bundle import get_batches_from_bundle + + batches = frappe._dict() + + old_field = "batch_no" + if not batch_no_field: + batch_no_field = "serial_and_batch_bundle" + + return_ref_field = frappe.scrub(child_doc.doctype) + if child_doc.doctype == "Delivery Note Item": + return_ref_field = "dn_detail" + + fields = [ + f"`{'tab' + child_doc.doctype}`.`{batch_no_field}`", + f"`{'tab' + child_doc.doctype}`.`batch_no`", + f"`{'tab' + child_doc.doctype}`.`stock_qty`", + ] + + filters = [ + [parent_doc.doctype, "return_against", "=", parent_doc.name], + [parent_doc.doctype, "is_return", "=", 1], + [child_doc.doctype, return_ref_field, "=", child_doc.name], + [parent_doc.doctype, "docstatus", "=", 1], + ] + + if batch_no_field == "rejected_serial_and_batch_bundle": + filters.append([child_doc.doctype, "rejected_qty", ">", 0]) + + # Required for POS Invoice + if ignore_voucher_detail_no: + filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no]) + + ids = [] + for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters): + ids.append(row.get("serial_and_batch_bundle")) + if row.get(old_field) and not row.get(batch_no_field): + batches.setdefault(row.get(old_field), row.get("stock_qty")) + + if ids: + batches.update(get_batches_from_bundle(ids)) + + return batches diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index fdadb30e93..e8bae8cda5 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -308,6 +308,8 @@ class SellingController(StockController): "warehouse": p.warehouse or d.warehouse, "item_code": p.item_code, "qty": flt(p.qty), + "serial_no": p.serial_no if self.docstatus == 2 else None, + "batch_no": p.batch_no if self.docstatus == 2 else None, "uom": p.uom, "serial_and_batch_bundle": p.serial_and_batch_bundle or get_serial_and_batch_bundle(p, self), @@ -330,6 +332,8 @@ class SellingController(StockController): "warehouse": d.warehouse, "item_code": d.item_code, "qty": d.stock_qty, + "serial_no": d.serial_no if self.docstatus == 2 else None, + "batch_no": d.batch_no if self.docstatus == 2 else None, "uom": d.uom, "stock_uom": d.stock_uom, "conversion_factor": d.conversion_factor, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2a8420418a..671d2fb7a5 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -463,6 +463,12 @@ class StockController(AccountsController): sl_dict.update(args) self.update_inventory_dimensions(d, sl_dict) + if self.docstatus == 2: + # To handle denormalized serial no records, will br deprecated in v16 + for field in ["serial_no", "batch_no"]: + if d.get(field): + sl_dict[field] = d.get(field) + return sl_dict def update_inventory_dimensions(self, row, sl_dict) -> None: diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 3ed7fc75cf..77ecf75e0c 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -361,9 +361,14 @@ erpnext.buying = { new erpnext.SerialBatchPackageSelector( me.frm, item, (r) => { if (r) { + let qty = Math.abs(r.total_qty); + if (doc.is_return) { + qty = qty * -1; + } + let update_values = { "serial_and_batch_bundle": r.name, - "qty": Math.abs(r.total_qty) + "qty": qty } if (r.warehouse) { @@ -396,9 +401,14 @@ erpnext.buying = { new erpnext.SerialBatchPackageSelector( me.frm, item, (r) => { if (r) { + let qty = Math.abs(r.total_qty); + if (doc.is_return) { + qty = qty * -1; + } + let update_values = { "serial_and_batch_bundle": r.name, - "rejected_qty": Math.abs(r.total_qty) + "rejected_qty": qty } if (r.warehouse) { diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 5514963c96..084cca7db5 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -317,9 +317,14 @@ erpnext.sales_common = { new erpnext.SerialBatchPackageSelector( me.frm, item, (r) => { if (r) { + let qty = Math.abs(r.total_qty); + if (doc.is_return) { + qty = qty * -1; + } + frappe.model.set_value(item.doctype, item.name, { "serial_and_batch_bundle": r.name, - "qty": Math.abs(r.total_qty) + "qty": qty }); } } diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 7b9cdfef2a..4abc8fa395 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -32,22 +32,39 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }); this.dialog.show(); - - let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; - this.dialog.set_value("qty", qty).then(() => { - if (this.item.serial_no) { - this.dialog.set_value("scan_serial_no", this.item.serial_no); - frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', ''); - } else if (this.item.batch_no) { - this.dialog.set_value("scan_batch_no", this.item.batch_no); - frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', ''); - } - - this.dialog.fields_dict.entries.grid.refresh(); - }); - this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); this.$scan_btn.css("display", "inline"); + + let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; + + if (this.item?.is_rejected) { + qty = this.item.rejected_qty; + } + + qty = Math.abs(qty); + if (qty > 0) { + this.dialog.set_value("qty", qty).then(() => { + if (this.item.serial_no && !this.item.serial_and_batch_bundle) { + let serial_nos = this.item.serial_no.split('\n'); + if (serial_nos.length > 1) { + serial_nos.forEach(serial_no => { + this.dialog.fields_dict.entries.df.data.push({ + serial_no: serial_no, + batch_no: this.item.batch_no + }); + }); + } else { + this.dialog.set_value("scan_serial_no", this.item.serial_no); + } + frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', ''); + } else if (this.item.batch_no && !this.item.serial_and_batch_bundle) { + this.dialog.set_value("scan_batch_no", this.item.batch_no); + frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', ''); + } + + this.dialog.fields_dict.entries.grid.refresh(); + }); + } } get_serial_no_filters() { @@ -467,13 +484,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } render_data() { - if (!this.frm.is_new() && this.bundle) { + if (this.bundle) { frappe.call({ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers', args: { item_code: this.item.item_code, name: this.bundle, - voucher_no: this.item.parent, + voucher_no: !this.frm.is_new() ? this.item.parent : "", } }).then(r => { if (r.message) { diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 94655747e4..3a581226ca 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -174,6 +174,115 @@ class TestDeliveryNote(FrappeTestCase): for field, value in field_values.items(): self.assertEqual(cstr(serial_no.get(field)), value) + def test_delivery_note_return_against_denormalized_serial_no(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + sn_item = "Old Serial NO Item Return Test - 1" + make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "OSN-.####", + "is_stock_item": 1, + }, + ) + + frappe.flags.ignore_serial_batch_bundle_validation = True + serial_nos = [ + "OSN-1", + "OSN-2", + "OSN-3", + "OSN-4", + "OSN-5", + "OSN-6", + "OSN-7", + "OSN-8", + "OSN-9", + "OSN-10", + "OSN-11", + "OSN-12", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=12, + basic_rate=100, + do_not_submit=1, + ) + + se_doc.items[0].serial_no = "\n".join(serial_nos) + se_doc.submit() + + self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos)) + + dn = create_delivery_note( + item_code=sn_item, + qty=12, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + do_not_submit=1, + ) + + dn.items[0].serial_no = "\n".join(serial_nos) + dn.submit() + dn.reload() + + self.assertTrue(dn.items[0].serial_no) + + frappe.flags.ignore_serial_batch_bundle_validation = False + + # return entry + dn1 = make_sales_return(dn.name) + + dn1.items[0].qty = -2 + + bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle) + bundle_doc.set("entries", bundle_doc.entries[:2]) + bundle_doc.save() + + dn1.save() + dn1.submit() + + returned_serial_nos1 = get_serial_nos_from_bundle(dn1.items[0].serial_and_batch_bundle) + for serial_no in returned_serial_nos1: + self.assertTrue(serial_no in serial_nos) + + dn2 = make_sales_return(dn.name) + + dn2.items[0].qty = -2 + + bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn2.items[0].serial_and_batch_bundle) + bundle_doc.set("entries", bundle_doc.entries[:2]) + bundle_doc.save() + + dn2.save() + dn2.submit() + + returned_serial_nos2 = get_serial_nos_from_bundle(dn2.items[0].serial_and_batch_bundle) + for serial_no in returned_serial_nos2: + self.assertTrue(serial_no in serial_nos) + self.assertFalse(serial_no in returned_serial_nos1) + def test_sales_return_for_non_bundled_items_partial(self): company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index ecb9314fbb..52ef26e641 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -23,7 +23,11 @@ from frappe.utils import ( ) from frappe.utils.csvutils import build_csv_response -from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation +from erpnext.stock.serial_batch_bundle import ( + BatchNoValuation, + SerialNoValuation, + get_batches_from_bundle, +) from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle @@ -123,6 +127,11 @@ class SerialandBatchBundle(Document): ) def validate_serial_nos_duplicate(self): + # Don't inward same serial number multiple times + + if not self.warehouse: + return + if self.voucher_type in ["Stock Reconciliation", "Stock Entry"] and self.docstatus != 1: return @@ -146,7 +155,6 @@ class SerialandBatchBundle(Document): kwargs["voucher_no"] = self.voucher_no available_serial_nos = get_available_serial_nos(kwargs) - for data in available_serial_nos: if data.serial_no in serial_nos: self.throw_error_message( @@ -327,6 +335,19 @@ class SerialandBatchBundle(Document): ): values_to_set["posting_time"] = parent.posting_time + if parent.doctype in [ + "Delivery Note", + "Purchase Receipt", + "Purchase Invoice", + "Sales Invoice", + ] and parent.get("is_return"): + return_ref_field = frappe.scrub(parent.doctype) + "_item" + if parent.doctype == "Delivery Note": + return_ref_field = "dn_detail" + + if row.get(return_ref_field): + values_to_set["returned_against"] = row.get(return_ref_field) + if values_to_set: self.db_set(values_to_set) @@ -509,7 +530,6 @@ class SerialandBatchBundle(Document): batch_nos = [] serial_batches = {} - for row in self.entries: if self.has_serial_no and not row.serial_no: frappe.throw( @@ -590,6 +610,67 @@ class SerialandBatchBundle(Document): f"Batch Nos {bold(incorrect_batch_nos)} does not belong to Item {bold(self.item_code)}" ) + def validate_serial_and_batch_no_for_returned(self): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + if not self.returned_against: + return + + if self.voucher_type not in [ + "Purchase Receipt", + "Purchase Invoice", + "Sales Invoice", + "Delivery Note", + ]: + return + + data = self.get_orignal_document_data() + if not data: + return + + serial_nos, batches = [], [] + current_serial_nos = [d.serial_no for d in self.entries if d.serial_no] + current_batches = [d.batch_no for d in self.entries if d.batch_no] + + for d in data: + if self.has_serial_no: + if d.serial_and_batch_bundle: + serial_nos = get_serial_nos_from_bundle(d.serial_and_batch_bundle) + else: + serial_nos = get_serial_nos(d.serial_no) + + elif self.has_batch_no: + if d.serial_and_batch_bundle: + batches = get_batches_from_bundle(d.serial_and_batch_bundle) + else: + batches = frappe._dict({d.batch_no: d.stock_qty}) + + if batches: + batches = [d for d in batches if batches[d] > 0] + + if serial_nos: + if not set(current_serial_nos).issubset(set(serial_nos)): + self.throw_error_message( + f"Serial Nos {bold(', '.join(serial_nos))} are not part of the original document." + ) + + if batches: + if not set(current_batches).issubset(set(batches)): + self.throw_error_message( + f"Batch Nos {bold(', '.join(batches))} are not part of the original document." + ) + + def get_orignal_document_data(self): + fields = ["serial_and_batch_bundle", "stock_qty"] + if self.has_serial_no: + fields.append("serial_no") + + elif self.has_batch_no: + fields.append("batch_no") + + child_doc = self.voucher_type + " Item" + return frappe.get_all(child_doc, fields=fields, filters={"name": self.returned_against}) + def validate_duplicate_serial_and_batch_no(self): serial_nos = [] batch_nos = [] @@ -688,9 +769,29 @@ class SerialandBatchBundle(Document): for batch in batches: frappe.db.set_value("Batch", batch.name, {"reference_name": None, "reference_doctype": None}) + def before_submit(self): + self.validate_serial_and_batch_no_for_returned() + self.set_purchase_document_no() + def on_submit(self): self.validate_serial_nos_inventory() + def set_purchase_document_no(self): + if not self.has_serial_no: + return + + if self.total_qty > 0: + serial_nos = [d.serial_no for d in self.entries if d.serial_no] + sn_table = frappe.qb.DocType("Serial No") + ( + frappe.qb.update(sn_table) + .set( + sn_table.purchase_document_no, + self.voucher_no if not sn_table.purchase_document_no else self.voucher_no, + ) + .where(sn_table.name.isin(serial_nos)) + ).run() + def validate_serial_and_batch_inventory(self): self.check_future_entries_exists() self.validate_batch_inventory() diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index b4ece00fe6..2d7fcac89a 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -27,8 +27,6 @@ "column_break_24", "location", "employee", - "delivery_details", - "delivery_document_type", "warranty_amc_details", "column_break6", "warranty_expiry_date", @@ -39,7 +37,8 @@ "more_info", "company", "column_break_2cmm", - "work_order" + "work_order", + "purchase_document_no" ], "fields": [ { @@ -153,20 +152,6 @@ "options": "Employee", "read_only": 1 }, - { - "fieldname": "delivery_details", - "fieldtype": "Section Break", - "label": "Delivery Details", - "oldfieldtype": "Column Break" - }, - { - "fieldname": "delivery_document_type", - "fieldtype": "Link", - "label": "Delivery Document Type", - "no_copy": 1, - "options": "DocType", - "read_only": 1 - }, { "fieldname": "warranty_amc_details", "fieldtype": "Section Break", @@ -275,12 +260,19 @@ { "fieldname": "column_break_2cmm", "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_document_no", + "fieldtype": "Data", + "label": "Creation Document No", + "no_copy": 1, + "read_only": 1 } ], "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2023-11-28 15:37:59.489945", + "modified": "2023-12-17 10:52:55.767839", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index d562560da1..122664c2dd 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -41,7 +41,6 @@ class SerialNo(StockController): batch_no: DF.Link | None brand: DF.Link | None company: DF.Link - delivery_document_type: DF.Link | None description: DF.Text | None employee: DF.Link | None item_code: DF.Link @@ -51,6 +50,7 @@ class SerialNo(StockController): maintenance_status: DF.Literal[ "", "Under Warranty", "Out of Warranty", "Under AMC", "Out of AMC" ] + purchase_document_no: DF.Data | None purchase_rate: DF.Float serial_no: DF.Data status: DF.Literal["", "Active", "Inactive", "Delivered", "Expired"] @@ -231,26 +231,6 @@ def auto_fetch_serial_number( return sorted([d.get("name") for d in serial_numbers]) -def get_delivered_serial_nos(serial_nos): - """ - Returns serial numbers that delivered from the list of serial numbers - """ - from frappe.query_builder.functions import Coalesce - - SerialNo = frappe.qb.DocType("Serial No") - serial_nos = get_serial_nos(serial_nos) - query = ( - frappe.qb.select(SerialNo.name) - .from_(SerialNo) - .where((SerialNo.name.isin(serial_nos)) & (Coalesce(SerialNo.delivery_document_type, "") != "")) - ) - - result = query.run() - if result and len(result) > 0: - delivered_serial_nos = [row[0] for row in result] - return delivered_serial_nos - - @frappe.whitelist() def get_pos_reserved_serial_nos(filters): if isinstance(filters, str): diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 23788cf46b..6e7af6815f 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -181,6 +181,9 @@ class StockLedgerEntry(Document): frappe.throw(_("Actual Qty is mandatory")) def validate_serial_batch_no_bundle(self): + if self.is_cancelled == 1: + return + item_detail = frappe.get_cached_value( "Item", self.item_code, diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 0c187923e3..a1874b84dc 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -218,15 +218,16 @@ class SerialBatchBundle: ).validate_serial_and_batch_inventory() def post_process(self): - if not self.sle.serial_and_batch_bundle: + if not self.sle.serial_and_batch_bundle and not self.sle.serial_no and not self.sle.batch_no: return - docstatus = frappe.get_cached_value( - "Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "docstatus" - ) + if self.sle.serial_and_batch_bundle: + docstatus = frappe.get_cached_value( + "Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "docstatus" + ) - if docstatus != 1: - self.submit_serial_and_batch_bundle() + if docstatus != 1: + self.submit_serial_and_batch_bundle() if self.item_details.has_serial_no == 1: self.set_warehouse_and_status_in_serial_nos() @@ -249,7 +250,12 @@ class SerialBatchBundle: doc.submit() def set_warehouse_and_status_in_serial_nos(self): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_parsed_serial_nos + serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle) + if not self.sle.serial_and_batch_bundle and self.sle.serial_no: + serial_nos = get_parsed_serial_nos(self.sle.serial_no) + warehouse = self.warehouse if self.sle.actual_qty > 0 else None if not serial_nos: @@ -263,7 +269,14 @@ class SerialBatchBundle: ( frappe.qb.update(sn_table) .set(sn_table.warehouse, warehouse) - .set(sn_table.status, "Active" if warehouse else status) + .set( + sn_table.status, + "Active" + if warehouse + else status + if (sn_table.purchase_document_no != self.sle.voucher_no and self.sle.is_cancelled != 1) + else "Inactive", + ) .where(sn_table.name.isin(serial_nos)) ).run() @@ -290,6 +303,8 @@ class SerialBatchBundle: from erpnext.stock.doctype.batch.batch import get_available_batches batches = get_batch_nos(self.sle.serial_and_batch_bundle) + if not self.sle.serial_and_batch_bundle and self.sle.batch_no: + batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty}) batches_qty = get_available_batches( frappe._dict( @@ -312,13 +327,35 @@ def get_serial_nos(serial_and_batch_bundle, serial_nos=None): if serial_nos: filters["serial_no"] = ("in", serial_nos) - entries = frappe.get_all("Serial and Batch Entry", fields=["serial_no"], filters=filters) + entries = frappe.get_all( + "Serial and Batch Entry", fields=["serial_no"], filters=filters, order_by="idx" + ) if not entries: return [] return [d.serial_no for d in entries if d.serial_no] +def get_batches_from_bundle(serial_and_batch_bundle, batches=None): + if not serial_and_batch_bundle: + return [] + + filters = {"parent": serial_and_batch_bundle, "batch_no": ("is", "set")} + if isinstance(serial_and_batch_bundle, list): + filters = {"parent": ("in", serial_and_batch_bundle)} + + if batches: + filters["batch_no"] = ("in", batches) + + entries = frappe.get_all( + "Serial and Batch Entry", fields=["batch_no", "qty"], filters=filters, order_by="idx", as_list=1 + ) + if not entries: + return frappe._dict({}) + + return frappe._dict(entries) + + def get_serial_nos_from_bundle(serial_and_batch_bundle, serial_nos=None): return get_serial_nos(serial_and_batch_bundle, serial_nos=serial_nos) From c9fd1822681420868923fd53aa1df3e3c776a4a6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 17 Dec 2023 13:36:58 +0530 Subject: [PATCH 097/112] fix(demo): Demo setup for canadian COA --- ...plan_comptable_pour_les_provinces_francophones.json | 10 +++++++--- erpnext/setup/demo.py | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json index 2811fc5fb6..2a30cbcbc9 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ca_plan_comptable_pour_les_provinces_francophones.json @@ -33,7 +33,9 @@ }, "Stocks": { "Mati\u00e8res premi\u00e8res": {}, - "Stock de produits fini": {}, + "Stock de produits fini": { + "account_type": "Stock" + }, "Stock exp\u00e9di\u00e9 non-factur\u00e9": {}, "Travaux en cours": {}, "account_type": "Stock" @@ -395,9 +397,11 @@ }, "Produits": { "Revenus de ventes": { - " Escomptes de volume sur ventes": {}, + "Escomptes de volume sur ventes": {}, "Autres produits d'exploitation": {}, - "Ventes": {}, + "Ventes": { + "account_type": "Income Account" + }, "Ventes avec des provinces harmonis\u00e9es": {}, "Ventes avec des provinces non-harmonis\u00e9es": {}, "Ventes \u00e0 l'\u00e9tranger": {} diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index 926283ff1c..4bc98b91bd 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -112,9 +112,9 @@ def create_transaction(doctype, company, start_date): warehouse = get_warehouse(company) if document_type == "Purchase Order": - posting_date = get_random_date(start_date, 1, 30) + posting_date = get_random_date(start_date, 1, 25) else: - posting_date = get_random_date(start_date, 31, 364) + posting_date = get_random_date(start_date, 31, 350) doctype.update( { From 39ef75e2d0f18aa8d7434297be596b9223aa7910 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 18 Dec 2023 13:39:26 +0530 Subject: [PATCH 098/112] refactor: ignore ERR journals in Statment of Accounts --- .../process_statement_of_accounts.json | 9 ++++++++- .../process_statement_of_accounts.py | 15 +++++++++++++++ .../report/general_ledger/general_ledger.py | 3 +++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index a3a953f910..ae6059c005 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -15,6 +15,7 @@ "group_by", "cost_center", "territory", + "ignore_exchange_rate_revaluation_journals", "column_break_14", "to_date", "finance_book", @@ -376,10 +377,16 @@ "fieldname": "pdf_name", "fieldtype": "Data", "label": "PDF Name" + }, + { + "default": "0", + "fieldname": "ignore_exchange_rate_revaluation_journals", + "fieldtype": "Check", + "label": "Ignore Exchange Rate Revaluation Journals" } ], "links": [], - "modified": "2023-08-28 12:59:53.071334", + "modified": "2023-12-18 12:20:08.965120", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 9ad25483e3..c03b18a871 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -56,6 +56,7 @@ class ProcessStatementOfAccounts(Document): frequency: DF.Literal["Weekly", "Monthly", "Quarterly"] from_date: DF.Date | None group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"] + ignore_exchange_rate_revaluation_journals: DF.Check include_ageing: DF.Check include_break: DF.Check letter_head: DF.Link | None @@ -119,6 +120,18 @@ def get_statement_dict(doc, get_statement_dict=False): statement_dict = {} ageing = "" + err_journals = None + if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals: + err_journals = frappe.db.get_all( + "Journal Entry", + filters={ + "company": doc.company, + "docstatus": 1, + "voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]), + }, + as_list=True, + ) + for entry in doc.customers: if doc.include_ageing: ageing = set_ageing(doc, entry) @@ -131,6 +144,8 @@ def get_statement_dict(doc, get_statement_dict=False): ) filters = get_common_filters(doc) + if err_journals: + filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) if doc.report == "General Ledger": filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index ac06a1227e..896c4c9800 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -238,6 +238,9 @@ def get_conditions(filters): if filters.get("voucher_no"): conditions.append("voucher_no=%(voucher_no)s") + if filters.get("voucher_no_not_in"): + conditions.append("voucher_no not in %(voucher_no_not_in)s") + if filters.get("group_by") == "Group by Party" and not filters.get("party_type"): conditions.append("party_type in ('Customer', 'Supplier')") From 71e833c3f2323873953c3ad7ab4a9403ebbdeb0a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:56:05 +0530 Subject: [PATCH 099/112] fix: not able to cancel SCR with Batch (backport #38817) (#38821) * fix: not able to cancel SCR with Batch (#38817) (cherry picked from commit fb5090fd3f23ada507fe8abc5a899f4b06e48d7e) # Conflicts: # erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py * chore: fix test case * chore: fix test case * chore: fix test case * chore: fix test case --------- Co-authored-by: rohitwaghchaure --- .../serial_and_batch_bundle.py | 2 +- .../subcontracting_receipt.py | 2 +- .../test_subcontracting_receipt.py | 85 +++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 52ef26e641..37916e21c8 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -459,7 +459,7 @@ class SerialandBatchBundle(Document): qty_field = "qty" precision = row.precision - if self.voucher_type in ["Subcontracting Receipt"]: + if row.get("doctype") in ["Subcontracting Receipt Supplied Item"]: qty_field = "consumed_qty" if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 1a5deb68df..52bf13c78d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -167,13 +167,13 @@ class SubcontractingReceipt(SubcontractingController): ) self.update_status_updater_args() self.update_prevdoc_status() - self.delete_auto_created_batches() self.set_consumed_qty_in_subcontract_order() self.set_subcontracting_order_status() self.update_stock_ledger() self.make_gl_entries_on_cancel() self.repost_future_sle_and_gle() self.update_status() + self.delete_auto_created_batches() def validate_items_qty(self): for item in self.items: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index f4af21d7c4..9d7be36adb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -946,6 +946,91 @@ class TestSubcontractingReceipt(FrappeTestCase): scr.submit() + def test_subcontracting_receipt_cancel_with_batch(self): + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + # Step - 1: Set Backflush Based On as "BOM" + set_backflush_based_on("BOM") + + # Step - 2: Create FG and RM Items + fg_item = make_item( + properties={"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1} + ).name + rm_item1 = make_item(properties={"is_stock_item": 1}).name + rm_item2 = make_item(properties={"is_stock_item": 1}).name + make_item("Subcontracted Service Item Test For Batch 1", {"is_stock_item": 0}) + + # Step - 3: Create BOM for FG Item + bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2]) + for rm_item in bom.items: + self.assertEqual(rm_item.rate, 0) + self.assertEqual(rm_item.amount, 0) + bom = bom.name + + # Step - 4: Create PO and SCO + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item Test For Batch 1", + "qty": 100, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 100, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + for rm_item in sco.supplied_items: + self.assertEqual(rm_item.rate, 0) + self.assertEqual(rm_item.amount, 0) + + # Step - 5: Inward Raw Materials + rm_items = get_rm_items(sco.supplied_items) + for rm_item in rm_items: + rm_item["rate"] = 100 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + # Step - 6: Transfer RM's to Subcontractor + se = make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + for item in se.items: + self.assertEqual(item.qty, 100) + self.assertEqual(item.basic_rate, 100) + self.assertEqual(item.amount, item.qty * item.basic_rate) + + batch_doc = frappe.get_doc( + { + "doctype": "Batch", + "item": fg_item, + "batch_id": frappe.generate_hash(length=10), + } + ).insert(ignore_permissions=True) + + serial_batch_bundle = frappe.get_doc( + { + "doctype": "Serial and Batch Bundle", + "item_code": fg_item, + "warehouse": sco.items[0].warehouse, + "has_batch_no": 1, + "type_of_transaction": "Inward", + "voucher_type": "Subcontracting Receipt", + "entries": [{"batch_no": batch_doc.name, "qty": 100}], + } + ).insert(ignore_permissions=True) + + # Step - 7: Create Subcontracting Receipt + scr = make_subcontracting_receipt(sco.name) + scr.items[0].serial_and_batch_bundle = serial_batch_bundle.name + scr.save() + scr.submit() + scr.load_from_db() + + # Step - 8: Cancel Subcontracting Receipt + scr.cancel() + self.assertTrue(scr.docstatus == 2) + @change_settings("Buying Settings", {"auto_create_purchase_receipt": 1}) def test_auto_create_purchase_receipt(self): fg_item = "Subcontracted Item SA1" From 8d79365e0ddef42e788f97c668a64c479a803617 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 18 Dec 2023 18:06:44 +0530 Subject: [PATCH 100/112] perf: index `return_against` on delivery note (#38827) There's a multi-column index but that's useful IFF all parts of column are part of query. return against on it's own is VERY unique because it's a primary key, we don't need a multi-column index here. --- erpnext/patches.txt | 1 + .../doctype/delivery_note/delivery_note.json | 5 +++-- .../stock/doctype/delivery_note/delivery_note.py | 4 ---- .../doctype/delivery_note/patches/__init__.py | 0 .../patches/drop_unused_return_against_index.py | 15 +++++++++++++++ .../purchase_receipt/purchase_receipt.json | 5 +++-- .../doctype/purchase_receipt/purchase_receipt.py | 4 ---- 7 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 erpnext/stock/doctype/delivery_note/patches/__init__.py create mode 100644 erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9b070bdaf7..56f6347693 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -354,3 +354,4 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency" execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format")) # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger +erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index b85f296d0b..7873d3e6de 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -301,7 +301,8 @@ "no_copy": 1, "options": "Delivery Note", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "collapsible": 1, @@ -1401,7 +1402,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-09-04 14:15:28.363184", + "modified": "2023-12-18 17:19:39.368239", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index a101bdf244..675f8e9158 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -1294,7 +1294,3 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): ) return doclist - - -def on_doctype_update(): - frappe.db.add_index("Delivery Note", ["customer", "is_return", "return_against"]) diff --git a/erpnext/stock/doctype/delivery_note/patches/__init__.py b/erpnext/stock/doctype/delivery_note/patches/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py b/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py new file mode 100644 index 0000000000..8fe4ffb58f --- /dev/null +++ b/erpnext/stock/doctype/delivery_note/patches/drop_unused_return_against_index.py @@ -0,0 +1,15 @@ +import frappe + + +def execute(): + """Drop unused return_against index""" + + try: + frappe.db.sql_ddl( + "ALTER TABLE `tabDelivery Note` DROP INDEX `customer_is_return_return_against_index`" + ) + frappe.db.sql_ddl( + "ALTER TABLE `tabPurchase Receipt` DROP INDEX `supplier_is_return_return_against_index`" + ) + except Exception: + frappe.log_error("Failed to drop unused index") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index c7ad660497..a181022121 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -289,7 +289,8 @@ "no_copy": 1, "options": "Purchase Receipt", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "section_addresses", @@ -1251,7 +1252,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-11-28 13:14:15.243474", + "modified": "2023-12-18 17:26:41.279663", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index aa479ee999..10d9eaa3db 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1360,10 +1360,6 @@ def get_item_account_wise_additional_cost(purchase_document): return item_account_wise_cost -def on_doctype_update(): - frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"]) - - @erpnext.allow_regional def update_regional_gl_entries(gl_list, doc): return From 32a608f94848564806d6254d11c6c0655fbcaa9a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:13:47 +0530 Subject: [PATCH 101/112] fix: not able to make inter-company po from so (backport #38826) (#38828) fix: not able to make inter-company po from so (#38826) (cherry picked from commit 23042dfc3c0d02374c5710ed679731b1910f9b9a) Co-authored-by: rohitwaghchaure --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++++++- erpnext/controllers/buying_controller.py | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f9d9cb5091..c0228e6b0b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2382,9 +2382,18 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): def get_received_items(reference_name, doctype, reference_fieldname): + reference_field = "inter_company_invoice_reference" + if doctype == "Purchase Order": + reference_field = "inter_company_order_reference" + + filters = { + reference_field: reference_name, + "docstatus": 1, + } + target_doctypes = frappe.get_all( doctype, - filters={"inter_company_invoice_reference": reference_name, "docstatus": 1}, + filters=filters, as_list=True, ) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 3d863e9b87..572fa519e1 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -381,7 +381,11 @@ class BuyingController(SubcontractingController): rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) else: - field = "incoming_rate" if self.get("is_internal_supplier") else "rate" + field = ( + "incoming_rate" + if self.get("is_internal_supplier") and not self.doctype == "Purchase Order" + else "rate" + ) rate = flt( frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field) * (d.conversion_factor or 1), From c7b961ffa27c611a1e0a45750b38f1f23b0b0c7f Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Mon, 18 Dec 2023 18:03:53 +0000 Subject: [PATCH 102/112] fix: use party account currency when party account is specified --- .../report/accounts_receivable/accounts_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 50d5eae407..376c9ba4c3 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -225,7 +225,7 @@ class ReceivablePayableReport(object): if not row: return - if self.filters.get("in_party_currency"): + if self.filters.get("in_party_currency") or self.filters.get("party_account"): amount = ple.amount_in_account_currency else: amount = ple.amount @@ -451,7 +451,7 @@ class ReceivablePayableReport(object): party_details = self.get_party_details(row.party) or {} row.update(party_details) - if self.filters.get("in_party_currency"): + if self.filters.get("in_party_currency") or self.filters.get("party_account"): row.currency = row.account_currency else: row.currency = self.company_currency From a09241e3c763882a0a0e06b21ccaa0b06f60bc75 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Mon, 18 Dec 2023 19:09:25 +0000 Subject: [PATCH 103/112] fix(test): expect account currency when party account is specified. --- .../report/accounts_receivable/test_accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index dd0842df04..6da007788e 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -544,7 +544,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters.update({"party_account": self.debtors_usd}) report = execute(filters)[1] self.assertEqual(len(report), 1) - expected_data = [8000.0, 8000.0, self.debtors_usd, si2.currency] + expected_data = [100.0, 100.0, self.debtors_usd, si2.currency] row = report[0] self.assertEqual( expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency] From a99d0a65b028b05b5c3bcb46a48a16771d00861f Mon Sep 17 00:00:00 2001 From: Gughan Ravikumar Date: Tue, 19 Dec 2023 07:54:57 +0530 Subject: [PATCH 104/112] fix: set `fg-itm-qty` based on `qty` instead of the other way round --- erpnext/buying/doctype/purchase_order/purchase_order.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 88faeee982..3b671bb239 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -214,7 +214,7 @@ frappe.ui.form.on("Purchase Order Item", { } }, - fg_item_qty: async function(frm, cdt, cdn) { + qty: async function (frm, cdt, cdn) { if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) { var row = locals[cdt][cdn]; @@ -222,7 +222,7 @@ frappe.ui.form.on("Purchase Order Item", { var result = await frm.events.get_subcontracting_boms_for_finished_goods(row.fg_item) if (result.message && row.item_code == result.message.service_item && row.uom == result.message.service_item_uom) { - frappe.model.set_value(cdt, cdn, "qty", flt(row.fg_item_qty) * flt(result.message.conversion_factor)); + frappe.model.set_value(cdt, cdn, "fg_item_qty", flt(row.qty) / flt(result.message.conversion_factor)); } } } From 877262891235b21447c5b74684fb7173910427e1 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 16 Dec 2023 05:34:43 +0000 Subject: [PATCH 105/112] test: partial payment for pos invoice --- .../test_accounts_receivable.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index dd0842df04..fbfaed6dfd 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -76,6 +76,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): return credit_note + def test_pos_receivable(self): + filters = { + "company": self.company, + "party_type": "Customer", + "party": [self.customer], + "report_date": add_days(today(), 2), + "based_on_payment_terms": 0, + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "show_remarks": False, + } + + pos_inv = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + pos_inv.posting_date = add_days(today(), 2) + pos_inv.is_pos = 1 + pos_inv.append( + "payments", + frappe._dict( + mode_of_payment="Cash", + amount=flt(pos_inv.grand_total / 2), + ), + ) + pos_inv.disable_rounded_total = 1 + pos_inv.save() + pos_inv.submit() + + report = execute(filters) + expected_data = [[pos_inv.grand_total, pos_inv.paid_amount, 0]] + + row = report[1][-1] + self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note]) + pos_inv.cancel() + def test_accounts_receivable(self): filters = { "company": self.company, From 4aa960b744c289b0cda9acfae7911b1ef6ffe5d6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:59:33 +0530 Subject: [PATCH 106/112] fix: item variant with manufacturer (backport #38845) (#38847) * fix: item variant with manufacturer (#38845) (cherry picked from commit e0c8ff10daeed0829266aea9142805f68ceedb2b) * chore: fix test case --------- Co-authored-by: rohitwaghchaure --- erpnext/controllers/item_variant.py | 20 +++++++++-- erpnext/stock/doctype/item/test_item.py | 44 +++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index c8785a5a72..ea7fb23cb6 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -56,10 +56,24 @@ def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part copy_attributes_to_variant(template, variant) - variant.manufacturer = manufacturer - variant.manufacturer_part_no = manufacturer_part_no - variant.item_code = append_number_if_name_exists("Item", template.name) + variant.flags.ignore_mandatory = True + variant.save() + + if not frappe.db.exists( + "Item Manufacturer", {"item_code": variant.name, "manufacturer": manufacturer} + ): + manufacturer_doc = frappe.new_doc("Item Manufacturer") + manufacturer_doc.update( + { + "item_code": variant.name, + "manufacturer": manufacturer, + "manufacturer_part_no": manufacturer_part_no, + } + ) + + manufacturer_doc.flags.ignore_mandatory = True + manufacturer_doc.save(ignore_permissions=True) return variant diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index a942f58bd6..b237f73026 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -522,39 +522,25 @@ class TestItem(FrappeTestCase): self.assertEqual(factor, 1.0) def test_item_variant_by_manufacturer(self): - fields = [{"field_name": "description"}, {"field_name": "variant_based_on"}] - set_item_variant_settings(fields) + template = make_item( + "_Test Item Variant By Manufacturer", {"has_variants": 1, "variant_based_on": "Manufacturer"} + ).name - if frappe.db.exists("Item", "_Test Variant Mfg"): - frappe.delete_doc("Item", "_Test Variant Mfg") - if frappe.db.exists("Item", "_Test Variant Mfg-1"): - frappe.delete_doc("Item", "_Test Variant Mfg-1") - if frappe.db.exists("Manufacturer", "MSG1"): - frappe.delete_doc("Manufacturer", "MSG1") + for manufacturer in ["DFSS", "DASA", "ASAAS"]: + if not frappe.db.exists("Manufacturer", manufacturer): + m_doc = frappe.new_doc("Manufacturer") + m_doc.short_name = manufacturer + m_doc.insert() - template = frappe.get_doc( - dict( - doctype="Item", - item_code="_Test Variant Mfg", - has_variant=1, - item_group="Products", - variant_based_on="Manufacturer", - ) - ).insert() + self.assertFalse(frappe.db.exists("Item Manufacturer", {"manufacturer": "DFSS"})) + variant = get_variant(template, manufacturer="DFSS", manufacturer_part_no="DFSS-123") - manufacturer = frappe.get_doc(dict(doctype="Manufacturer", short_name="MSG1")).insert() + item_manufacturer = frappe.db.exists( + "Item Manufacturer", {"manufacturer": "DFSS", "item_code": variant.name} + ) + self.assertTrue(item_manufacturer) - variant = get_variant(template.name, manufacturer=manufacturer.name) - self.assertEqual(variant.item_code, "_Test Variant Mfg-1") - self.assertEqual(variant.description, "_Test Variant Mfg") - self.assertEqual(variant.manufacturer, "MSG1") - variant.insert() - - variant = get_variant(template.name, manufacturer=manufacturer.name, manufacturer_part_no="007") - self.assertEqual(variant.item_code, "_Test Variant Mfg-2") - self.assertEqual(variant.description, "_Test Variant Mfg") - self.assertEqual(variant.manufacturer, "MSG1") - self.assertEqual(variant.manufacturer_part_no, "007") + frappe.delete_doc("Item Manufacturer", item_manufacturer) def test_stock_exists_against_template_item(self): stock_item = frappe.get_all("Stock Ledger Entry", fields=["item_code"], limit=1) From 5e68b7e3a6cea0001ad1275300a019c0db9e8e13 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:01:37 +0530 Subject: [PATCH 107/112] fix: on closed unreserved the production plan qty (backport #38848) (#38859) fix: on closed unreserved the production plan qty (#38848) (cherry picked from commit 2184e8ef58379f53ef8f1d069afa26e64796b073) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 4 ++ .../production_plan/test_production_plan.py | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index c201c4f7be..4b72a83b05 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -583,6 +583,7 @@ class ProductionPlan(Document): if close: self.db_set("status", "Closed") + self.update_bin_qty() return if self.total_produced_qty > 0: @@ -597,6 +598,9 @@ class ProductionPlan(Document): if close is not None: self.db_set("status", self.status) + if self.docstatus == 1 and self.status != "Completed": + self.update_bin_qty() + def update_ordered_status(self): update_status = False for d in self.po_items: diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index cc9d9a0311..f86725d601 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1458,6 +1458,47 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(row.get("uom"), "Nos") self.assertEqual(row.get("conversion_factor"), 10.0) + def test_unreserve_qty_on_closing_of_pp(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + from erpnext.stock.utils import get_or_make_bin + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + rm_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + + store_warehouse = create_warehouse("Store Warehouse", company="_Test Company") + rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company") + + make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1 + ) + + pln.for_warehouse = rm_warehouse + mr_items = get_items_for_material_requests(pln.as_dict()) + for d in mr_items: + pln.append("mr_items", d) + + pln.save() + pln.submit() + + bin_name = get_or_make_bin(rm_item, rm_warehouse) + before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + pln.reload() + pln.set_status(close=True) + + bin_name = get_or_make_bin(rm_item, rm_warehouse) + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + self.assertAlmostEqual(after_qty, before_qty - 10) + + pln.reload() + pln.set_status(close=False) + + bin_name = get_or_make_bin(rm_item, rm_warehouse) + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + self.assertAlmostEqual(after_qty, before_qty) + def create_production_plan(**args): """ From 4057682c878bd6abffa63e7343ba33bd220bc88c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:29:56 +0530 Subject: [PATCH 108/112] fix: incoming rate for sales return with Moving Average valuation method (backport #38849) (#38863) * fix: incoming rate for sales return with Moving Average valuation method (#38849) (cherry picked from commit 7fdac62393ee1e96969cca38a4ce0c07993dce7e) # Conflicts: # erpnext/stock/doctype/delivery_note/test_delivery_note.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/controllers/selling_controller.py | 8 +-- .../delivery_note/test_delivery_note.py | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e8bae8cda5..4489d60131 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor -from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.utils import get_incoming_rate, get_valuation_method class SellingController(StockController): @@ -432,11 +432,13 @@ class SellingController(StockController): items = self.get("items") + (self.get("packed_items") or []) for d in items: - if not self.get("return_against"): + if not self.get("return_against") or ( + get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") + ): # 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 (self.get("is_return") and d.incoming_rate): + if not d.incoming_rate: d.incoming_rate = get_incoming_rate( { "item_code": d.item_code, diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 3a581226ca..da8ee022f9 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1375,6 +1375,56 @@ class TestDeliveryNote(FrappeTestCase): dn.reload() self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered") + def test_sales_return_valuation_for_moving_average(self): + item_code = make_item( + "_Test Item Sales Return with MA", {"is_stock_item": 1, "valuation_method": "Moving Average"} + ).name + + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=100.0, + posting_date=add_days(nowdate(), -5), + ) + dn = create_delivery_note( + item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4) + ) + self.assertEqual(dn.items[0].incoming_rate, 100.0) + + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=200.0, + posting_date=add_days(nowdate(), -3), + ) + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=300.0, + posting_date=add_days(nowdate(), -2), + ) + + dn1 = create_delivery_note( + is_return=1, + item_code=item_code, + return_against=dn.name, + qty=-5, + rate=500, + company=dn.company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + do_not_submit=1, + posting_date=add_days(nowdate(), -1), + ) + + # (300 * 5) + (200 * 5) = 2500 + # 2500 / 10 = 250 + + self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From 793e3ad78e9c79800920e93b3eb95377fa155ea0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:31:18 +0530 Subject: [PATCH 109/112] fix: if not budget then don't validate (backport #38861) (#38864) fix: if not budget then don't validate (#38861) (cherry picked from commit d375164100158db9b974742caa3e05062c481d7d) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/material_request/material_request.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 3e90ed5a3b..ad9b34c9ac 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -169,7 +169,9 @@ class MaterialRequest(BuyingController): def on_submit(self): self.update_requested_qty_in_production_plan() self.update_requested_qty() - if self.material_request_type == "Purchase": + if self.material_request_type == "Purchase" and frappe.db.exists( + "Budget", {"applicable_on_material_request": 1, "docstatus": 1} + ): self.validate_budget() def before_save(self): From 6d5bdc6c68dfda915972f3747de752b8fad7cdb5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Dec 2023 12:30:10 +0530 Subject: [PATCH 110/112] fix: typerror on tree doctypes - Item Group, Customer Group, Supplier Group and Territory (#38870) * refactor: typerror on set_root_readonly * refactor: remove 'cur_frm' usage in supplier_group * refactor: remove 'cur_frm' usage in territory.js * refactor: remove 'cur_frm' from sales_person.js --- .../doctype/customer_group/customer_group.js | 29 +++++++++---------- .../doctype/sales_person/sales_person.js | 23 +++++++-------- .../doctype/supplier_group/supplier_group.js | 27 ++++++++--------- erpnext/setup/doctype/territory/territory.js | 27 +++++++++-------- 4 files changed, 48 insertions(+), 58 deletions(-) diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js index 3c81b0283c..e3528189dc 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.js +++ b/erpnext/setup/doctype/customer_group/customer_group.js @@ -1,21 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - -cur_frm.cscript.refresh = function(doc, cdt, cdn) { - cur_frm.cscript.set_root_readonly(doc); -} - -cur_frm.cscript.set_root_readonly = function(doc) { - // read-only for root customer group - if(!doc.parent_customer_group && !doc.__islocal) { - cur_frm.set_read_only(); - cur_frm.set_intro(__("This is a root customer group and cannot be edited.")); - } else { - cur_frm.set_intro(null); - } -} - frappe.ui.form.on("Customer Group", { setup: function(frm){ frm.set_query('parent_customer_group', function (doc) { @@ -48,5 +33,17 @@ frappe.ui.form.on("Customer Group", { } } }); - } + }, + refresh: function(frm) { + frm.trigger("set_root_readonly"); + }, + set_root_readonly: function(frm) { + // read-only for root customer group + if(!frm.doc.parent_customer_group && !frm.doc.__islocal) { + frm.set_read_only(); + frm.set_intro(__("This is a root customer group and cannot be edited.")); + } else { + frm.set_intro(null); + } + }, }); diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js index d86a8f3d98..f0d9aa87bc 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.js +++ b/erpnext/setup/doctype/sales_person/sales_person.js @@ -11,6 +11,7 @@ frappe.ui.form.on('Sales Person', { frm.dashboard.add_indicator(__('Total Contribution Amount Against Invoices: {0}', [format_currency(info.allocated_amount_against_invoice, info.currency)]), 'blue'); } + frm.trigger("set_root_readonly"); }, setup: function(frm) { @@ -27,22 +28,18 @@ frappe.ui.form.on('Sales Person', { 'Sales Order': () => frappe.new_doc("Sales Order") .then(() => frm.add_child("sales_team", {"sales_person": frm.doc.name})) } + }, + set_root_readonly: function(frm) { + // read-only for root + if(!frm.doc.parent_sales_person && !frm.doc.__islocal) { + frm.set_read_only(); + frm.set_intro(__("This is a root sales person and cannot be edited.")); + } else { + frm.set_intro(null); + } } }); -cur_frm.cscript.refresh = function(doc, cdt, cdn) { - cur_frm.cscript.set_root_readonly(doc); -} - -cur_frm.cscript.set_root_readonly = function(doc) { - // read-only for root - if(!doc.parent_sales_person && !doc.__islocal) { - cur_frm.set_read_only(); - cur_frm.set_intro(__("This is a root sales person and cannot be edited.")); - } else { - cur_frm.set_intro(null); - } -} //get query select sales person cur_frm.fields_dict['parent_sales_person'].get_query = function(doc, cdt, cdn) { diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index 33629297ff..c697a99cb4 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -1,21 +1,6 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -cur_frm.cscript.refresh = function(doc) { - cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit.")); - cur_frm.cscript.set_root_readonly(doc); -}; - -cur_frm.cscript.set_root_readonly = function(doc) { - // read-only for root customer group - if(!doc.parent_supplier_group && !doc.__islocal) { - cur_frm.set_read_only(); - cur_frm.set_intro(__("This is a root supplier group and cannot be edited.")); - } else { - cur_frm.set_intro(null); - } -}; - frappe.ui.form.on("Supplier Group", { setup: function(frm){ frm.set_query('parent_supplier_group', function (doc) { @@ -48,5 +33,17 @@ frappe.ui.form.on("Supplier Group", { } } }); + }, + refresh: function(frm) { + frm.set_intro(frm.doc.__islocal ? "" : __("There is nothing to edit.")); + frm.trigger("set_root_readonly"); + }, + set_root_readonly: function(frm) { + if(!frm.doc.parent_supplier_group && !frm.doc.__islocal) { + frm.trigger("set_read_only"); + frm.set_intro(__("This is a root supplier group and cannot be edited.")); + } else { + frm.set_intro(null); + } } }); diff --git a/erpnext/setup/doctype/territory/territory.js b/erpnext/setup/doctype/territory/territory.js index 3caf814c90..e11d20b7bf 100644 --- a/erpnext/setup/doctype/territory/territory.js +++ b/erpnext/setup/doctype/territory/territory.js @@ -11,23 +11,22 @@ frappe.ui.form.on("Territory", { } } }; + }, + refresh: function(frm) { + frm.trigger("set_root_readonly"); + }, + set_root_readonly: function(frm) { + // read-only for root territory + if(!frm.doc.parent_territory && !frm.doc.__islocal) { + frm.set_read_only(); + frm.set_intro(__("This is a root territory and cannot be edited.")); + } else { + frm.set_intro(null); + } } + }); -cur_frm.cscript.refresh = function(doc, cdt, cdn) { - cur_frm.cscript.set_root_readonly(doc); -} - -cur_frm.cscript.set_root_readonly = function(doc) { - // read-only for root territory - if(!doc.parent_territory && !doc.__islocal) { - cur_frm.set_read_only(); - cur_frm.set_intro(__("This is a root territory and cannot be edited.")); - } else { - cur_frm.set_intro(null); - } -} - //get query select territory cur_frm.fields_dict['parent_territory'].get_query = function(doc,cdt,cdn) { return{ From 9983283f95753e7523cf30cc258df0572f88081d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Dec 2023 13:16:09 +0530 Subject: [PATCH 111/112] perf: use estimated rows instead of actual rows (#38830) --- .../batch_wise_balance_history.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 176a21566a..7f2608e0fb 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, getdate +from frappe.utils import cint, flt, get_table_name, getdate from frappe.utils.deprecations import deprecated from pypika import functions as fn @@ -13,11 +13,22 @@ from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter SLE_COUNT_LIMIT = 10_000 +def _estimate_table_row_count(doctype: str): + table = get_table_name(doctype) + return cint( + frappe.db.sql( + f"""select table_rows + from information_schema.tables + where table_name = '{table}' ;""" + )[0][0] + ) + + def execute(filters=None): if not filters: filters = {} - sle_count = frappe.db.count("Stock Ledger Entry") + sle_count = _estimate_table_row_count("Stock Ledger Entry") if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"): frappe.throw(_("Please select either the Item or Warehouse filter to generate the report.")) From ae353398d96746e9e89640b586d1bbd6afbbce77 Mon Sep 17 00:00:00 2001 From: NIYAZ RAZAK <76736615+niyazrazak@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:04:38 +0300 Subject: [PATCH 112/112] fix: local reference error in BOM (#38850) fix: local reference error --- erpnext/manufacturing/doctype/bom/bom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index f0381d2cef..762747645b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1017,6 +1017,8 @@ def get_bom_item_rate(args, bom_doc): item_doc = frappe.get_cached_doc("Item", args.get("item_code")) price_list_data = get_price_list_rate(bom_args, item_doc) rate = price_list_data.price_list_rate + elif bom_doc.rm_cost_as_per == "Manual": + return return flt(rate)