From 725a7f90e9a873335caaa588d73ddb3de7383c33 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 25 Sep 2023 19:45:19 +0530 Subject: [PATCH 01/79] 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 02/79] 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 03/79] 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 04/79] 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 05/79] 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 06/79] 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 07/79] 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 08/79] 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 09/79] 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 10/79] 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 11/79] 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 12/79] 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 13/79] 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 14/79] 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 15/79] 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 16/79] 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 17/79] 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 18/79] 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 19/79] 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 20/79] 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 21/79] 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 22/79] 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 23/79] 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 24/79] 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 25/79] 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 26/79] 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 27/79] 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 9aeb3932d0f7dbf3daf5f8ba2e6e9f02082a0f54 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 2 Dec 2023 19:50:16 +0530 Subject: [PATCH 28/79] 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 6d31563920a8daeac7a782b070e6c44fd01a9f17 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sun, 3 Dec 2023 22:33:21 +0530 Subject: [PATCH 29/79] 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 30/79] 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 31/79] 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 9471d8fff94f84a627957c5ff2c7f8f1ed53a098 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 17:56:32 +0530 Subject: [PATCH 32/79] feat: provision to update items in Stock Reservation dialog --- .../doctype/sales_order/sales_order.js | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 97b214e33e..be02877a1b 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -182,7 +182,7 @@ frappe.ui.form.on("Sales Order", { create_stock_reservation_entries(frm) { const dialog = new frappe.ui.Dialog({ title: __("Stock Reservation"), - size: "large", + size: "extra-large", fields: [ { fieldname: "set_warehouse", @@ -218,10 +218,33 @@ frappe.ui.form.on("Sales Order", { fields: [ { fieldname: "sales_order_item", - fieldtype: "Data", + fieldtype: "Link", label: __("Sales Order Item"), + options: "Sales Order Item", reqd: 1, - read_only: 1, + in_list_view: 1, + get_query: () => { + return { + filters: { + "parenttype": frm.doc.doctype, + "parent": frm.doc.name, + "reserve_stock": 1, + } + } + }, + onchange: (event) => { + if (event) { + let name = $(event.currentTarget).closest(".grid-row").attr("data-name"); + let item_row = dialog.fields_dict.items.grid.grid_rows_by_docname[name].doc; + + frm.doc.items.forEach(item => { + if (item.name === item_row.sales_order_item) { + item_row.item_code = item.item_code; + } + }); + dialog.fields_dict.items.grid.refresh(); + } + } }, { fieldname: "item_code", From 00261094c8ce0663f953b73ae17a5e7f3f494b59 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 18:00:06 +0530 Subject: [PATCH 33/79] fix(ux): show row index and field label while selecting the Sales Order Item --- erpnext/controllers/queries.py | 28 +++++++++++++++++++ .../doctype/sales_order/sales_order.js | 1 + 2 files changed, 29 insertions(+) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 199732b152..e858820965 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -891,3 +891,31 @@ def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, as_list=1, ) return terms + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_filtered_child_rows(doctype, txt, searchfield, start, page_len, filters) -> list: + table = frappe.qb.DocType(doctype) + query = ( + frappe.qb.from_(table) + .select( + table.name, + Concat("#", table.idx, ", ", table.item_code), + ) + .orderby(table.idx) + .offset(start) + .limit(page_len) + ) + + if filters: + for field, value in filters.items(): + query = query.where(table[field] == value) + + if txt: + txt += "%" + query = query.where( + ((table.idx.like(txt.replace("#", ""))) | (table.item_code.like(txt))) | (table.name.like(txt)) + ) + + return query.run(as_dict=False) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index be02877a1b..0d36f23695 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -225,6 +225,7 @@ frappe.ui.form.on("Sales Order", { in_list_view: 1, get_query: () => { return { + query: "erpnext.controllers.queries.get_filtered_child_rows", filters: { "parenttype": frm.doc.doctype, "parent": frm.doc.name, From 8d5045ef4caf695dbfdc087b78c8f68a79976813 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 18:03:22 +0530 Subject: [PATCH 34/79] feat: provision to add items in Stock Reservation dialog --- .../doctype/sales_order/sales_order.js | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 0d36f23695..b206e3fe33 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -207,6 +207,50 @@ frappe.ui.form.on("Sales Order", { }, }, {fieldtype: "Column Break"}, + { + fieldname: "add_item", + fieldtype: "Link", + label: __("Add Item"), + options: "Sales Order Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.get_filtered_child_rows", + filters: { + "parenttype": frm.doc.doctype, + "parent": frm.doc.name, + "reserve_stock": 1, + } + } + }, + onchange: () => { + let sales_order_item = dialog.get_value("add_item"); + + if (sales_order_item) { + frm.doc.items.forEach(item => { + if (item.name === sales_order_item) { + let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor); + + if (unreserved_qty > 0) { + dialog.fields_dict.items.df.data.forEach((row) => { + if (row.sales_order_item === sales_order_item) { + unreserved_qty -= row.qty_to_reserve; + } + }); + } + + dialog.fields_dict.items.df.data.push({ + 'sales_order_item': item.name, + 'item_code': item.item_code, + 'warehouse': dialog.get_value("set_warehouse") || item.warehouse, + 'qty_to_reserve': Math.max(unreserved_qty, 0) + }); + dialog.fields_dict.items.grid.refresh(); + dialog.set_value("add_item", undefined); + } + }); + } + }, + }, {fieldtype: "Section Break"}, { fieldname: "items", @@ -308,14 +352,14 @@ frappe.ui.form.on("Sales Order", { frm.doc.items.forEach(item => { if (item.reserve_stock) { - let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) + let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor); if (unreserved_qty > 0) { dialog.fields_dict.items.df.data.push({ 'sales_order_item': item.name, 'item_code': item.item_code, 'warehouse': item.warehouse, - 'qty_to_reserve': (unreserved_qty / flt(item.conversion_factor)) + 'qty_to_reserve': unreserved_qty }); } } From a56b79cc72241f6a4540cb93b1b0f39926137cdc Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 5 Dec 2023 12:46:22 +0530 Subject: [PATCH 35/79] 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 36/79] 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 37/79] 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 38/79] 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 fa1c7b663c2e3f433190d29017eaebbe15d4c604 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 15 Dec 2023 22:00:01 +0530 Subject: [PATCH 39/79] 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 40/79] 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 41/79] 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 42/79] 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 43/79] 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 44/79] 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 45/79] 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 46/79] 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 47/79] 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 48/79] 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 49/79] 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 50/79] 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 51/79] 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 52/79] 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 53/79] 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 54/79] 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 55/79] 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 56/79] 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 57/79] 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 58/79] 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) From 9a00edb03115b72afb4274e6fcf2dc7d5b431657 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 20 Dec 2023 17:22:30 +0530 Subject: [PATCH 59/79] fix: incorrect price list in customer-wise item price report --- .../customer_wise_item_price.py | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py index a58f40362b..40aa9acc3c 100644 --- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py @@ -3,11 +3,11 @@ import frappe -from frappe import _ +from frappe import _, qb +from frappe.query_builder import Criterion from erpnext import get_default_company from erpnext.accounts.party import get_party_details -from erpnext.stock.get_item_details import get_price_list_rate_for def execute(filters=None): @@ -50,6 +50,42 @@ def get_columns(filters=None): ] +def fetch_item_prices( + customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None +): + price_list_map = frappe._dict() + ip = qb.DocType("Item Price") + and_conditions = [] + or_conditions = [] + if items: + and_conditions.append(ip.item_code.isin([x.item_code for x in items])) + and_conditions.append(ip.selling == True) + + or_conditions.append(ip.customer == None) + or_conditions.append(ip.price_list == None) + + if customer: + or_conditions.append(ip.customer == customer) + + if price_list: + or_conditions.append(ip.price_list == price_list) + + if selling_price_list: + or_conditions.append(ip.price_list == selling_price_list) + + res = ( + qb.from_(ip) + .select(ip.item_code, ip.price_list, ip.price_list_rate) + .where(Criterion.all(and_conditions)) + .where(Criterion.any(or_conditions)) + .run(as_dict=True) + ) + for x in res: + price_list_map.update({(x.item_code, x.price_list): x.price_list_rate}) + + return price_list_map + + def get_data(filters=None): data = [] customer_details = get_customer_details(filters) @@ -59,9 +95,17 @@ def get_data(filters=None): "Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code" ) item_stock_map = {item.item_code: item.available for item in item_stock_map} + price_list_map = fetch_item_prices( + customer_details.customer, + customer_details.price_list, + customer_details.selling_price_list, + items, + ) for item in items: - price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0 + price_list_rate = price_list_map.get( + (item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0 + ) available_stock = item_stock_map.get(item.item_code) data.append( From a6ab53236e08cb57fc8a45496ee4c9ccfbc53e10 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 18:33:26 +0530 Subject: [PATCH 60/79] fix: allow to set rate manually for service item in BOM (backport #38880) (#38882) fix: allow to set rate manually for service item in BOM (#38880) (cherry picked from commit c2f692a4e4f3dd5089fe4949c6cd74574282fdb1) Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/doctype/bom/bom.py | 3 ++ erpnext/manufacturing/doctype/bom/test_bom.py | 29 +++++++++++++++++++ .../doctype/bom_item/bom_item.json | 13 +++++++-- erpnext/patches.txt | 1 + .../v14_0/set_maintain_stock_for_bom_item.py | 19 ++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 762747645b..d86b6d426e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -744,6 +744,9 @@ class BOM(WebsiteGenerator): base_total_rm_cost = 0 for d in self.get("items"): + if not d.is_stock_item and self.rm_cost_as_per == "Valuation Rate": + continue + old_rate = d.rate if self.rm_cost_as_per != "Manual": d.rate = self.get_rm_rate( diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 051b475bcc..2debf9191e 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -698,6 +698,35 @@ class TestBOM(FrappeTestCase): bom.update_cost() self.assertFalse(bom.flags.cost_updated) + def test_bom_with_service_item_cost(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 1000.0}).name + + service_item = make_item(properties={"is_stock_item": 0}).name + + fg_item = make_item(properties={"is_stock_item": 1}).name + + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + bom = make_bom(item=fg_item, raw_materials=[rm_item, service_item], do_not_save=True) + bom.rm_cost_as_per = "Valuation Rate" + + for row in bom.items: + if row.item_code == service_item: + row.rate = 566.00 + else: + row.rate = 800.00 + + bom.save() + + for row in bom.items: + if row.item_code == service_item: + self.assertEqual(row.is_stock_item, 0) + self.assertEqual(row.rate, 566.00) + else: + self.assertEqual(row.is_stock_item, 1) + def test_do_not_include_manufacturing_and_fixed_items(self): from erpnext.manufacturing.doctype.bom.bom import item_query diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index cb58af1f29..dfd6612098 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -14,6 +14,7 @@ "bom_no", "source_warehouse", "allow_alternative_item", + "is_stock_item", "section_break_5", "description", "col_break1", @@ -185,7 +186,7 @@ "in_list_view": 1, "label": "Rate", "options": "currency", - "read_only": 1, + "read_only_depends_on": "eval:doc.is_stock_item == 1", "reqd": 1 }, { @@ -284,13 +285,21 @@ "fieldname": "do_not_explode", "fieldtype": "Check", "label": "Do Not Explode" + }, + { + "default": "0", + "fetch_from": "item_code.is_stock_item", + "fieldname": "is_stock_item", + "fieldtype": "Check", + "label": "Is Stock Item", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:35:51.378513", + "modified": "2023-12-20 16:21:55.477883", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 56f6347693..d5cd4da77a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -355,3 +355,4 @@ execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System # 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 +erpnext.patches.v14_0.set_maintain_stock_for_bom_item \ No newline at end of file diff --git a/erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py b/erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py new file mode 100644 index 0000000000..f0b618f32d --- /dev/null +++ b/erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py @@ -0,0 +1,19 @@ +import frappe + + +def execute(): + if not frappe.db.exists("BOM", {"docstatus": 1}): + return + + # Added is_stock_item to handle Read Only based on condition for the rate field + frappe.db.sql( + """ + UPDATE + `tabBOM Item` boi, + `tabItem` i + SET + boi.is_stock_item = i.is_stock_item + WHERE + boi.item_code = i.name + """ + ) From d370c60a6c840be23ec4094593b9bbf1d1dca88b Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 20 Dec 2023 19:02:32 +0530 Subject: [PATCH 61/79] feat: total_asset_cost field (#38879) --- erpnext/assets/doctype/asset/asset.json | 11 ++++++++++- erpnext/assets/doctype/asset/asset.py | 1 + .../assets/doctype/asset_repair/asset_repair.py | 6 ++++++ erpnext/patches.txt | 1 + .../v14_0/update_total_asset_cost_field.py | 17 +++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v14_0/update_total_asset_cost_field.py diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 540a4f5549..ea72b3cf84 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -35,6 +35,7 @@ "purchase_receipt", "purchase_invoice", "available_for_use_date", + "total_asset_cost", "column_break_23", "gross_purchase_amount", "asset_quantity", @@ -529,6 +530,14 @@ "label": "Capitalized In", "options": "Asset Capitalization", "read_only": 1 + }, + { + "depends_on": "eval:doc.docstatus > 0", + "fieldname": "total_asset_cost", + "fieldtype": "Currency", + "label": "Total Asset Cost", + "options": "Company:company:default_currency", + "read_only": 1 } ], "idx": 72, @@ -572,7 +581,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-11-20 20:57:37.010467", + "modified": "2023-12-20 16:50:21.128595", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3b3ed0ac75..3ea6ec7c4d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -111,6 +111,7 @@ class Asset(AccountsController): "Decapitalized", ] supplier: DF.Link | None + total_asset_cost: DF.Currency total_number_of_depreciations: DF.Int value_after_depreciation: DF.Currency # end: auto-generated types diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 31dd63d2f8..b7fce91b1d 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -93,6 +93,9 @@ class AssetRepair(AccountsController): self.increase_asset_value() + if self.capitalize_repair_cost: + self.asset_doc.total_asset_cost += self.repair_cost + if self.get("stock_consumption"): self.check_for_stock_items_and_warehouse() self.decrease_stock_quantity() @@ -128,6 +131,9 @@ class AssetRepair(AccountsController): self.decrease_asset_value() + if self.capitalize_repair_cost: + self.asset_doc.total_asset_cost -= self.repair_cost + if self.get("stock_consumption"): self.increase_stock_quantity() if self.get("capitalize_repair_cost"): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d5cd4da77a..952875c293 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -352,6 +352,7 @@ 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")) +erpnext.patches.v14_0.update_total_asset_cost_field # 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/patches/v14_0/update_total_asset_cost_field.py b/erpnext/patches/v14_0/update_total_asset_cost_field.py new file mode 100644 index 0000000000..57cf71b613 --- /dev/null +++ b/erpnext/patches/v14_0/update_total_asset_cost_field.py @@ -0,0 +1,17 @@ +import frappe + + +def execute(): + asset = frappe.qb.DocType("Asset") + frappe.qb.update(asset).set(asset.total_asset_cost, asset.gross_purchase_amount).run() + + asset_repair_list = frappe.db.get_all( + "Asset Repair", + filters={"docstatus": 1, "repair_status": "Completed", "capitalize_repair_cost": 1}, + fields=["asset", "repair_cost"], + ) + + for asset_repair in asset_repair_list: + frappe.qb.update(asset).set( + asset.total_asset_cost, asset.total_asset_cost + asset_repair.repair_cost + ).where(asset.name == asset_repair.asset).run() From 6a0a08b59c5b1eadbfdc657a131531d0e7461b11 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 21 Dec 2023 12:13:33 +0530 Subject: [PATCH 62/79] fix: typeerror on pos order summary to new order screen --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 193048f676..bd8579203c 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -520,7 +520,7 @@ erpnext.PointOfSale.ItemCart = class { } render_taxes(taxes) { - if (taxes.length) { + if (taxes && taxes.length) { const currency = this.events.get_frm().doc.currency; const taxes_html = taxes.map(t => { if (t.tax_amount_after_discount_amount == 0.0) return; From 61219ca4ce069421d909f8e170672f791e9c478d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:23:08 +0530 Subject: [PATCH 63/79] fix: do not reset the basic rate for the material receipt stock entry (backport #38896) (#38898) fix: do not reset the basic rate for the material receipt stock entry (#38896) (cherry picked from commit 98bfcc4c758b61f714c53ea0f00246731a30fdaf) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/stock_entry/stock_entry.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 7af5d1aa37..8da3e8fdd0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -512,7 +512,12 @@ frappe.ui.form.on('Stock Entry', { }, callback: function(r) { if (!r.exc) { - ["actual_qty", "basic_rate"].forEach((field) => { + let fields = ["actual_qty", "basic_rate"]; + if (frm.doc.purpose == "Material Receipt") { + fields = ["actual_qty"]; + } + + fields.forEach((field) => { frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0)); }); frm.events.calculate_basic_amount(frm, child); From 07175367d8158355e5f699c7c66a2a6743a85c29 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:40:52 +0530 Subject: [PATCH 64/79] =?UTF-8?q?fix:=20reposting=20not=20fixing=20valuati?= =?UTF-8?q?on=20rate=20for=20sales=20return=20using=20movin=E2=80=A6=20(ba?= =?UTF-8?q?ckport=20#38895)=20(#38897)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: reposting not fixing valuation rate for sales return using movin… (#38895) fix: reposting not fixing valuation rate for sales return using moving average method (cherry picked from commit 3a668bbe9694fdd6e8265869c6943e42f889ac41) # Conflicts: # erpnext/stock/stock_ledger.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- erpnext/controllers/selling_controller.py | 4 +- .../delivery_note/test_delivery_note.py | 53 +++++++++++++++++++ erpnext/stock/stock_ledger.py | 34 +++++++++--- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 4489d60131..919e459c9e 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -438,7 +438,9 @@ class SellingController(StockController): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get("stock_qty") or d.get("actual_qty")) - if not d.incoming_rate: + if not d.incoming_rate or ( + get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") + ): 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 da8ee022f9..933be53b07 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1425,6 +1425,59 @@ class TestDeliveryNote(FrappeTestCase): self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0) + def test_sales_return_valuation_for_moving_average_case2(self): + # Make DN return + # Make Bakcdated Purchase Receipt and check DN return valuation rate + # The rate should be recalculate based on the backdated purchase receipt + frappe.flags.print_debug_messages = False + item_code = make_item( + "_Test Item Sales Return with MA Case2", + {"is_stock_item": 1, "valuation_method": "Moving Average", "stock_uom": "Nos"}, + ).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, + warehouse="_Test Warehouse - _TC", + qty=5, + rate=500, + posting_date=add_days(nowdate(), -4), + ) + + returned_dn = create_delivery_note( + is_return=1, + item_code=item_code, + return_against=dn.name, + qty=-5, + rate=500, + company=dn.company, + warehouse="_Test Warehouse - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + posting_date=add_days(nowdate(), -1), + ) + + self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 100.0) + + # Make backdated purchase receipt + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=5, + basic_rate=200.0, + posting_date=add_days(nowdate(), -3), + ) + + returned_dn.reload() + self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9203f4570a..a6206ac8dc 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -25,6 +25,7 @@ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry impor ) from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, + get_incoming_rate, get_or_make_bin, get_stock_balance, get_valuation_method, @@ -841,14 +842,33 @@ class update_entries_after(object): get_rate_for_return, # don't move this import to top ) - rate = get_rate_for_return( - sle.voucher_type, - sle.voucher_no, - sle.item_code, - voucher_detail_no=sle.voucher_detail_no, - sle=sle, - ) + if self.valuation_method == "Moving Average": + rate = get_incoming_rate( + { + "item_code": sle.item_code, + "warehouse": sle.warehouse, + "posting_date": sle.posting_date, + "posting_time": sle.posting_time, + "qty": sle.actual_qty, + "serial_no": sle.get("serial_no"), + "batch_no": sle.get("batch_no"), + "serial_and_batch_bundle": sle.get("serial_and_batch_bundle"), + "company": sle.company, + "voucher_type": sle.voucher_type, + "voucher_no": sle.voucher_no, + "allow_zero_valuation": self.allow_zero_rate, + "sle": sle.name, + } + ) + else: + rate = get_rate_for_return( + sle.voucher_type, + sle.voucher_no, + sle.item_code, + voucher_detail_no=sle.voucher_detail_no, + sle=sle, + ) elif ( sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and sle.voucher_detail_no From 283763dfb2affa6a0b7bb29e19123c3e1fb27f30 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 21 Dec 2023 16:52:25 +0530 Subject: [PATCH 65/79] chore: additional_asset_cost field (#38904) --- erpnext/assets/doctype/asset/asset.json | 11 ++++++++++- erpnext/assets/doctype/asset/asset.py | 2 ++ erpnext/assets/doctype/asset_repair/asset_repair.py | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index ea72b3cf84..ac712d4431 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -36,6 +36,7 @@ "purchase_invoice", "available_for_use_date", "total_asset_cost", + "additional_asset_cost", "column_break_23", "gross_purchase_amount", "asset_quantity", @@ -538,6 +539,14 @@ "label": "Total Asset Cost", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "depends_on": "eval:doc.docstatus > 0", + "fieldname": "additional_asset_cost", + "fieldtype": "Currency", + "label": "Additional Asset Cost", + "options": "Company:company:default_currency", + "read_only": 1 } ], "idx": 72, @@ -581,7 +590,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-12-20 16:50:21.128595", + "modified": "2023-12-21 16:46:20.732869", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3ea6ec7c4d..dd34189391 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -50,6 +50,7 @@ class Asset(AccountsController): from erpnext.assets.doctype.asset_finance_book.asset_finance_book import AssetFinanceBook + additional_asset_cost: DF.Currency amended_from: DF.Link | None asset_category: DF.Link | None asset_name: DF.Data @@ -145,6 +146,7 @@ class Asset(AccountsController): ).format(asset_depr_schedules_links) ) + self.total_asset_cost = self.gross_purchase_amount self.status = self.get_status() def on_submit(self): diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index b7fce91b1d..bb627d408c 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -95,6 +95,7 @@ class AssetRepair(AccountsController): if self.capitalize_repair_cost: self.asset_doc.total_asset_cost += self.repair_cost + self.asset_doc.additional_asset_cost += self.repair_cost if self.get("stock_consumption"): self.check_for_stock_items_and_warehouse() @@ -133,6 +134,7 @@ class AssetRepair(AccountsController): if self.capitalize_repair_cost: self.asset_doc.total_asset_cost -= self.repair_cost + self.asset_doc.additional_asset_cost -= self.repair_cost if self.get("stock_consumption"): self.increase_stock_quantity() From 787333896c3710d16b1fa72432db0fe59c74dfa8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 21 Dec 2023 20:09:10 +0530 Subject: [PATCH 66/79] perf: Drop unused/duplicate/sub-optimal indexes (#38884) * ci: enable more checks * perf: Drop unused/duplicate indexes --- .pre-commit-config.yaml | 6 +++- .../accounts/doctype/gl_entry/gl_entry.json | 8 ++---- .../purchase_invoice/purchase_invoice.py | 4 --- .../doctype/sales_invoice/sales_invoice.py | 4 --- .../purchase_order_item.json | 3 +- erpnext/patches.txt | 4 +-- erpnext/stock/doctype/bin/bin.json | 3 +- .../drop_unused_return_against_index.py | 28 +++++++++++++------ erpnext/tests/test_perf.py | 24 ++++++++++++++++ 9 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 erpnext/tests/test_perf.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30be903ae8..6ea121f298 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ fail_fast: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: trailing-whitespace files: "erpnext.*" @@ -15,6 +15,10 @@ repos: args: ['--branch', 'develop'] - id: check-merge-conflict - id: check-ast + - id: check-json + - id: check-toml + - id: check-yaml + - id: debug-statements - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.44.0 diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 16df40f435..c4492be367 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -158,8 +158,7 @@ "label": "Against Voucher Type", "oldfieldname": "against_voucher_type", "oldfieldtype": "Data", - "options": "DocType", - "search_index": 1 + "options": "DocType" }, { "fieldname": "against_voucher", @@ -178,8 +177,7 @@ "label": "Voucher Type", "oldfieldname": "voucher_type", "oldfieldtype": "Select", - "options": "DocType", - "search_index": 1 + "options": "DocType" }, { "fieldname": "voucher_no", @@ -337,4 +335,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 931b48d905..f40824dff4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1858,10 +1858,6 @@ def make_inter_company_sales_invoice(source_name, target_doc=None): return make_inter_company_transaction("Purchase Invoice", source_name, target_doc) -def on_doctype_update(): - frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"]) - - @frappe.whitelist() def make_purchase_receipt(source_name, target_doc=None): def update_item(obj, target, source_parent): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c0228e6b0b..2cddb863c6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2575,10 +2575,6 @@ def get_loyalty_programs(customer): return lp_details -def on_doctype_update(): - frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"]) - - @frappe.whitelist() def create_invoice_discounting(source_name, target_doc=None): invoice = frappe.get_doc("Sales Invoice", source_name) diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 98c1b388c1..5a24cc2e92 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -123,8 +123,7 @@ "oldfieldname": "item_code", "oldfieldtype": "Link", "options": "Item", - "reqd": 1, - "search_index": 1 + "reqd": 1 }, { "fieldname": "supplier_part_no", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 952875c293..7ade21df60 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -355,5 +355,5 @@ execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System erpnext.patches.v14_0.update_total_asset_cost_field # 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 -erpnext.patches.v14_0.set_maintain_stock_for_bom_item \ No newline at end of file +erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 +erpnext.patches.v14_0.set_maintain_stock_for_bom_item diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index 312470d50e..10d9511357 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -52,8 +52,7 @@ "oldfieldtype": "Link", "options": "Item", "read_only": 1, - "reqd": 1, - "search_index": 1 + "reqd": 1 }, { "default": "0.00", 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 index 8fe4ffb58f..cc29e67fa7 100644 --- 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 @@ -1,15 +1,27 @@ +import click import frappe +UNUSED_INDEXES = [ + ("Delivery Note", ["customer", "is_return", "return_against"]), + ("Sales Invoice", ["customer", "is_return", "return_against"]), + ("Purchase Invoice", ["supplier", "is_return", "return_against"]), + ("Purchase Receipt", ["supplier", "is_return", "return_against"]), +] + def execute(): - """Drop unused return_against index""" + for doctype, index_fields in UNUSED_INDEXES: + table = f"tab{doctype}" + index_name = frappe.db.get_index_name(index_fields) + drop_index_if_exists(table, index_name) + + +def drop_index_if_exists(table: str, index: str): + if not frappe.db.has_index(table, index): + return 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`" - ) + frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`") + click.echo(f"✓ dropped {index} index from {table}") except Exception: - frappe.log_error("Failed to drop unused index") + frappe.log_error("Failed to drop index") diff --git a/erpnext/tests/test_perf.py b/erpnext/tests/test_perf.py new file mode 100644 index 0000000000..fc17b1dcbd --- /dev/null +++ b/erpnext/tests/test_perf.py @@ -0,0 +1,24 @@ +import frappe +from frappe.tests.utils import FrappeTestCase + +INDEXED_FIELDS = { + "Bin": ["item_code"], + "GL Entry": ["voucher_type", "against_voucher_type"], + "Purchase Order Item": ["item_code"], + "Stock Ledger Entry": ["warehouse"], +} + + +class TestPerformance(FrappeTestCase): + def test_ensure_indexes(self): + # These fields are not explicitly indexed BUT they are prefix in some + # other composite index. If those are removed this test should be + # updated accordingly. + for doctype, fields in INDEXED_FIELDS.items(): + for field in fields: + self.assertTrue( + frappe.db.sql( + f"""SHOW INDEX FROM `tab{doctype}` + WHERE Column_name = "{field}" AND Seq_in_index = 1""" + ) + ) From 2dc49c834a0cab620465fafa555d239092107864 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Dec 2023 07:36:27 +0530 Subject: [PATCH 67/79] chore: fixup broken JSON files (#38915) --- .../unverified/at_austria_chart_template.json | 69 ++++--- .../doctype/journal_entry/test_records.json | 187 +++++++++--------- erpnext/setup/demo_data/journal_entry.json | 30 +-- 3 files changed, 141 insertions(+), 145 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json index 58d67beb67..bd7228ec41 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/at_austria_chart_template.json @@ -26,7 +26,7 @@ "0360 Bauliche Investitionen in fremden (gepachteten) Betriebs- und Geschäftsgebäuden": {"account_type": "Fixed Asset"}, "0370 Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgebäuden": {"account_type": "Fixed Asset"}, "0390 Kumulierte Abschreibungen zu Grundstücken ": {"account_type": "Fixed Asset"}, - "0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"}, + "0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"}, "0500 Maschinenwerkzeuge ": {"account_type": "Fixed Asset"}, "0510 Allgemeine Werkzeuge und Handwerkzeuge ": {"account_type": "Fixed Asset"}, "0520 Prototypen, Formen, Modelle ": {"account_type": "Fixed Asset"}, @@ -65,42 +65,41 @@ "0980 Geleistete Anzahlungen auf Finanzanlagen ": {"account_type": "Fixed Asset"}, "0990 Kumulierte Abschreibungen zu Finanzanlagen ": {"account_type": "Fixed Asset"}, "root_type": "Asset" - }, + }, "Klasse 1 Aktiva: Vorr\u00e4te": { "1000 Bezugsverrechnung": {"account_type": "Stock"}, "1100 Rohstoffe": {"account_type": "Stock"}, "1200 Bezogene Teile": {"account_type": "Stock"}, "1300 Hilfsstoffe": {"account_type": "Stock"}, "1350 Betriebsstoffe": {"account_type": "Stock"}, - "1360 Vorrat Energietraeger": {"account_type": "Stock"}, + "1360 Vorrat Energietraeger": {"account_type": "Stock"}, "1400 Unfertige Erzeugnisse": {"account_type": "Stock"}, "1500 Fertige Erzeugnisse": {"account_type": "Stock"}, "1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"}, "1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"}, - "1900 Wertberichtigungen": {"account_type": "Stock"}, "1800 Geleistete Anzahlungen": {"account_type": "Stock"}, "1900 Wertberichtigungen": {"account_type": "Stock"}, "root_type": "Asset" - }, + }, "Klasse 3 Passiva: Verbindlichkeiten": { "3000 Allgemeine Verbindlichkeiten (Schuld)": {"account_type": "Payable"}, "3010 R\u00fcckstellungen f\u00fcr Pensionen": {"account_type": "Payable"}, "3020 Steuerr\u00fcckstellungen": {"account_type": "Tax"}, - "3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"}, + "3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"}, "3110 Verbindlichkeiten gegen\u00fcber Bank": {"account_type": "Payable"}, "3150 Verbindlichkeiten Darlehen": {"account_type": "Payable"}, - "3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"}, + "3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"}, "3380 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": { "account_type": "Payable" }, "3400 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {}, "3460 Verbindlichkeiten gegenueber Gesellschaftern": {"account_type": "Payable"}, "3470 Einlagen stiller Gesellschafter": {"account_type": "Payable"}, - "3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"}, - "3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"}, - "3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"}, + "3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"}, + "3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"}, + "3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"}, "3600 Verbindlichkeiten Sozialversicherung": {"account_type": "Payable"}, - "3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"}, + "3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"}, "3700 Sonstige Verbindlichkeiten": {"account_type": "Payable"}, "3900 Passive Rechnungsabgrenzungsposten": {"account_type": "Payable"}, "3100 Anleihen (einschlie\u00dflich konvertibler)": {"account_type": "Payable"}, @@ -119,13 +118,13 @@ }, "3515 Umsatzsteuer Inland 10%": { "account_type": "Tax" - }, + }, "3520 Umsatzsteuer aus i.g. Erwerb 20%": { "account_type": "Tax" }, "3525 Umsatzsteuer aus i.g. Erwerb 10%": { "account_type": "Tax" - }, + }, "3560 Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {}, "3360 Verbindlichkeiten aus Lieferungen u. Leistungen EU": { "account_type": "Payable" @@ -141,7 +140,7 @@ "account_type": "Tax" }, "root_type": "Liability" - }, + }, "Klasse 2 Aktiva: Umlaufverm\u00f6gen, Rechnungsabgrenzungen": { "2030 Forderungen aus Lieferungen und Leistungen Inland (0% USt, umsatzsteuerfrei)": { "account_type": "Receivable" @@ -154,7 +153,7 @@ }, "2040 Forderungen aus Lieferungen und Leistungen Inland (sonstiger USt-Satz)": { "account_type": "Receivable" - }, + }, "2100 Forderungen aus Lieferungen und Leistungen EU": { "account_type": "Receivable" }, @@ -192,7 +191,7 @@ "account_type": "Receivable" }, "2570 Einfuhrumsatzsteuer (bezahlt)": {"account_type": "Tax"}, - + "2460 Eingeforderte aber noch nicht eingezahlte Einlagen": { "account_type": "Receivable" }, @@ -243,10 +242,10 @@ }, "2800 Guthaben bei Bank": { "account_type": "Bank" - }, + }, "2801 Guthaben bei Bank - Sparkonto": { "account_type": "Bank" - }, + }, "2810 Guthaben bei Paypal": { "account_type": "Bank" }, @@ -264,19 +263,19 @@ }, "2895 Schwebende Geldbewegugen": { "account_type": "Bank" - }, + }, "2513 Vorsteuer Inland 5%": { "account_type": "Tax" }, "2515 Vorsteuer Inland 20%": { "account_type": "Tax" - }, + }, "2520 Vorsteuer aus innergemeinschaftlichem Erwerb 10%": { "account_type": "Tax" }, "2525 Vorsteuer aus innergemeinschaftlichem Erwerb 20%": { "account_type": "Tax" - }, + }, "2530 Vorsteuer \u00a719/Art 19 ( reverse charge ) ": { "account_type": "Tax" }, @@ -286,16 +285,16 @@ "root_type": "Asset" }, "Klasse 4: Betriebliche Erträge": { - "4000 Erlöse 20 %": {"account_type": "Income Account"}, - "4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"}, + "4000 Erlöse 20 %": {"account_type": "Income Account"}, + "4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"}, "4010 Erl\u00f6se 10 %": {"account_type": "Income Account"}, - "4030 Erl\u00f6se 13 %": {"account_type": "Income Account"}, - "4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"}, - "4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"}, + "4030 Erl\u00f6se 13 %": {"account_type": "Income Account"}, + "4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"}, + "4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"}, "4410 Erl\u00f6sreduktion 10 %": {"account_type": "Expense Account"}, "4420 Erl\u00f6sreduktion 20 %": {"account_type": "Expense Account"}, - "4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"}, - "4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"}, + "4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"}, + "4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"}, "4500 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {"account_type": "Income Account"}, "4580 Aktivierte Eigenleistungen": {"account_type": "Income Account"}, "4600 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"}, @@ -304,15 +303,15 @@ "4700 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {"account_type": "Income Account"}, "4800 \u00dcbrige betriebliche Ertr\u00e4ge": {"account_type": "Income Account"}, "root_type": "Income" - }, + }, "Klasse 5: Aufwand f\u00fcr Material und Leistungen": { - "5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"}, + "5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"}, "5100 Verbrauch an Rohstoffen": {"account_type": "Cost of Goods Sold"}, "5200 Verbrauch von bezogenen Fertig- und Einzelteilen": {"account_type": "Cost of Goods Sold"}, "5300 Verbrauch von Hilfsstoffen": {"account_type": "Cost of Goods Sold"}, "5340 Verbrauch Verpackungsmaterial": {"account_type": "Cost of Goods Sold"}, "5470 Verbrauch von Kleinmaterial": {"account_type": "Cost of Goods Sold"}, - "5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"}, + "5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"}, "5400 Verbrauch von Betriebsstoffen": {"account_type": "Cost of Goods Sold"}, "5500 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {"account_type": "Cost of Goods Sold"}, "5600 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {"account_type": "Cost of Goods Sold"}, @@ -340,7 +339,7 @@ "6700 Sonstige Sozialaufwendungen": {"account_type": "Payable"}, "6900 Aufwandsstellenrechnung Personal": {"account_type": "Payable"}, "root_type": "Expense" - }, + }, "Klasse 7: Abschreibungen und sonstige betriebliche Aufwendungen": { "7010 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {"account_type": "Depreciation"}, "7100 Sonstige Steuern und Geb\u00fchren": {"account_type": "Tax"}, @@ -349,7 +348,7 @@ "7310 Fahrrad - Aufwand": {"account_type": "Expense Account"}, "7320 Kfz - Aufwand": {"account_type": "Expense Account"}, "7330 LKW - Aufwand": {"account_type": "Expense Account"}, - "7340 Lastenrad - Aufwand": {"account_type": "Expense Account"}, + "7340 Lastenrad - Aufwand": {"account_type": "Expense Account"}, "7350 Reise- und Fahraufwand": {"account_type": "Expense Account"}, "7360 Tag- und N\u00e4chtigungsgelder": {"account_type": "Expense Account"}, "7380 Nachrichtenaufwand": {"account_type": "Expense Account"}, @@ -409,7 +408,7 @@ "8990 Gewinnabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {"account_type": "Expense Account"}, "8350 nicht ausgenutzte Lieferantenskonti": {"account_type": "Expense Account"}, "root_type": "Income" - }, + }, "Klasse 9 Passiva: Eigenkapital, R\u00fccklagen, stille Einlagen, Abschlusskonten": { "9000 Gezeichnetes bzw. gewidmetes Kapital": { "account_type": "Equity" @@ -435,5 +434,5 @@ }, "root_type": "Equity" } - } + } } diff --git a/erpnext/accounts/doctype/journal_entry/test_records.json b/erpnext/accounts/doctype/journal_entry/test_records.json index dafcf56abd..717c579c7a 100644 --- a/erpnext/accounts/doctype/journal_entry/test_records.json +++ b/erpnext/accounts/doctype/journal_entry/test_records.json @@ -1,97 +1,94 @@ [ - { - "cheque_date": "2013-03-14", - "cheque_no": "33", - "company": "_Test Company", - "doctype": "Journal Entry", - "accounts": [ - { - "account": "Debtors - _TC", - "party_type": "Customer", - "party": "_Test Customer", - "credit_in_account_currency": 400.0, - "debit_in_account_currency": 0.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "_Test Cost Center - _TC" - }, - { - "account": "_Test Bank - _TC", - "credit_in_account_currency": 0.0, - "debit_in_account_currency": 400.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "_Test Cost Center - _TC" - } - ], - "naming_series": "_T-Journal Entry-", - "posting_date": "2013-02-14", - "user_remark": "test", - "voucher_type": "Bank Entry" - }, - - - { - "cheque_date": "2013-02-14", - "cheque_no": "33", - "company": "_Test Company", - "doctype": "Journal Entry", - "accounts": [ - { - "account": "_Test Payable - _TC", - "party_type": "Supplier", - "party": "_Test Supplier", - "credit_in_account_currency": 0.0, - "debit_in_account_currency": 400.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "_Test Cost Center - _TC" - }, - { - "account": "_Test Bank - _TC", - "credit_in_account_currency": 400.0, - "debit_in_account_currency": 0.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "_Test Cost Center - _TC" - } - ], - "naming_series": "_T-Journal Entry-", - "posting_date": "2013-02-14", - "user_remark": "test", - "voucher_type": "Bank Entry" - }, - - - { - "cheque_date": "2013-02-14", - "cheque_no": "33", - "company": "_Test Company", - "doctype": "Journal Entry", - "accounts": [ - { - "account": "Debtors - _TC", - "party_type": "Customer", - "party": "_Test Customer", - "credit_in_account_currency": 0.0, - "debit_in_account_currency": 400.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "_Test Cost Center - _TC" - }, - { - "account": "Sales - _TC", - "cost_center": "_Test Cost Center - _TC", - "credit_in_account_currency": 400.0, - "debit_in_account_currency": 0.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - "cost_center": "_Test Cost Center - _TC" - } - ], - "naming_series": "_T-Journal Entry-", - "posting_date": "2013-02-14", - "user_remark": "test", - "voucher_type": "Bank Entry" - } + { + "cheque_date": "2013-03-14", + "cheque_no": "33", + "company": "_Test Company", + "doctype": "Journal Entry", + "accounts": [ + { + "account": "Debtors - _TC", + "party_type": "Customer", + "party": "_Test Customer", + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "_Test Cost Center - _TC" + }, + { + "account": "_Test Bank - _TC", + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "_Test Cost Center - _TC" + } + ], + "naming_series": "_T-Journal Entry-", + "posting_date": "2013-02-14", + "user_remark": "test", + "voucher_type": "Bank Entry" + }, + + { + "cheque_date": "2013-02-14", + "cheque_no": "33", + "company": "_Test Company", + "doctype": "Journal Entry", + "accounts": [ + { + "account": "_Test Payable - _TC", + "party_type": "Supplier", + "party": "_Test Supplier", + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "_Test Cost Center - _TC" + }, + { + "account": "_Test Bank - _TC", + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "_Test Cost Center - _TC" + } + ], + "naming_series": "_T-Journal Entry-", + "posting_date": "2013-02-14", + "user_remark": "test", + "voucher_type": "Bank Entry" + }, + + { + "cheque_date": "2013-02-14", + "cheque_no": "33", + "company": "_Test Company", + "doctype": "Journal Entry", + "accounts": [ + { + "account": "Debtors - _TC", + "party_type": "Customer", + "party": "_Test Customer", + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "_Test Cost Center - _TC" + }, + { + "account": "Sales - _TC", + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "_Test Cost Center - _TC" + } + ], + "naming_series": "_T-Journal Entry-", + "posting_date": "2013-02-14", + "user_remark": "test", + "voucher_type": "Bank Entry" + } ] diff --git a/erpnext/setup/demo_data/journal_entry.json b/erpnext/setup/demo_data/journal_entry.json index b751c7cf24..a681be4f5b 100644 --- a/erpnext/setup/demo_data/journal_entry.json +++ b/erpnext/setup/demo_data/journal_entry.json @@ -4,22 +4,22 @@ "cheque_no": "33", "doctype": "Journal Entry", "accounts": [ - { - "party_type": "Customer", - "party": "ABC Enterprises", - "credit_in_account_currency": 40000.0, - "debit_in_account_currency": 0.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - }, - { - "credit_in_account_currency": 0.0, - "debit_in_account_currency": 40000.0, - "doctype": "Journal Entry Account", - "parentfield": "accounts", - } + { + "party_type": "Customer", + "party": "ABC Enterprises", + "credit_in_account_currency": 40000.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts" + }, + { + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 40000.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts" + } ], "user_remark": "test", "voucher_type": "Bank Entry" } -] \ No newline at end of file +] From 0773f66febb7547873607f6e6be0a7669b8dd00a Mon Sep 17 00:00:00 2001 From: hyaray Date: Fri, 22 Dec 2023 11:11:51 +0800 Subject: [PATCH 68/79] chore: Update company.py (#38660) chore: Update company.py --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 0e8ee2dda4..9897847896 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -274,7 +274,7 @@ class Company(NestedSet): "parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) if not wh_detail["is_group"] else "", - "warehouse_type": wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None, + "warehouse_type": wh_detail.get("warehouse_type"), } ) warehouse.flags.ignore_permissions = True From 1a1629196d0daedef3db4f73191e21321689306d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 22 Dec 2023 15:29:56 +0530 Subject: [PATCH 69/79] chore: improve `Allowed Qty` error msg --- .../stock_reservation_entry.py | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) 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 24650fde5f..7e03ac3357 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -9,7 +9,7 @@ from frappe.model.document import Document from frappe.query_builder.functions import Sum from frappe.utils import cint, flt -from erpnext.stock.utils import get_or_make_bin +from erpnext.stock.utils import get_or_make_bin, get_stock_balance class StockReservationEntry(Document): @@ -151,7 +151,7 @@ class StockReservationEntry(Document): """Validates `Reserved Qty` when `Reservation Based On` is `Qty`.""" if self.reservation_based_on == "Qty": - self.validate_with_max_reserved_qty(self.reserved_qty) + self.validate_with_allowed_qty(self.reserved_qty) def auto_reserve_serial_and_batch(self, based_on: str = None) -> None: """Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`.""" @@ -324,7 +324,7 @@ class StockReservationEntry(Document): frappe.throw(msg) # Should be called after validating Serial and Batch Nos. - self.validate_with_max_reserved_qty(qty_to_be_reserved) + self.validate_with_allowed_qty(qty_to_be_reserved) self.db_set("reserved_qty", qty_to_be_reserved) def update_reserved_qty_in_voucher( @@ -429,7 +429,7 @@ class StockReservationEntry(Document): msg = _("Stock Reservation Entry cannot be updated as it has been delivered.") frappe.throw(msg) - def validate_with_max_reserved_qty(self, qty_to_be_reserved: float) -> None: + def validate_with_allowed_qty(self, qty_to_be_reserved: float) -> None: """Validates `Reserved Qty` with `Max Reserved Qty`.""" self.db_set( @@ -448,12 +448,12 @@ class StockReservationEntry(Document): ) voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor) - max_reserved_qty = min( + allowed_qty = min( self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty) ) - if max_reserved_qty <= 0 and self.voucher_type == "Sales Order": - msg = _("Item {0} is already delivered for Sales Order {1}.").format( + if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0: + msg = _("Item {0} is already reserved/delivered against Sales Order {1}.").format( frappe.bold(self.item_code), frappe.bold(self.voucher_no) ) @@ -463,19 +463,33 @@ class StockReservationEntry(Document): else: frappe.throw(msg) - if qty_to_be_reserved > max_reserved_qty: + if qty_to_be_reserved > allowed_qty: + actual_qty = get_stock_balance(self.item_code, self.warehouse) msg = """ - Cannot reserve more than Max Reserved Qty {0} {1}.

- The Max Reserved Qty is calculated as follows:
+ Cannot reserve more than Allowed Qty {0} {1} for Item {2} against {3} {4}.

+ The Allowed Qty is calculated as follows:
    -
  • Available Qty To Reserve = (Actual Stock Qty - Reserved Stock Qty)
  • -
  • Voucher Qty = Voucher Item Qty
  • -
  • Delivered Qty = Qty delivered against the Voucher Item
  • -
  • Total Reserved Qty = Qty reserved against the Voucher Item
  • -
  • Max Reserved Qty = Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))
  • +
  • Actual Qty [Available Qty at Warehouse] = {5}
  • +
  • Reserved Stock [Ignore current SRE] = {6}
  • +
  • Available Qty To Reserve [Actual Qty - Reserved Stock] = {7}
  • +
  • Voucher Qty [Voucher Item Qty] = {8}
  • +
  • Delivered Qty [Qty delivered against the Voucher Item] = {9}
  • +
  • Total Reserved Qty [Qty reserved against the Voucher Item] = {10}
  • +
  • Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {11}
""".format( - frappe.bold(max_reserved_qty), self.stock_uom + frappe.bold(allowed_qty), + self.stock_uom, + frappe.bold(self.item_code), + self.voucher_type, + frappe.bold(self.voucher_no), + actual_qty, + actual_qty - self.available_qty, + self.available_qty, + self.voucher_qty, + voucher_delivered_qty, + total_reserved_qty, + allowed_qty, ) frappe.throw(msg) @@ -509,7 +523,6 @@ def get_available_qty_to_reserve( """Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination.""" from erpnext.stock.doctype.batch.batch import get_batch_qty - from erpnext.stock.utils import get_stock_balance if batch_no: return get_batch_qty( From a5d5223c0e7f85a64f70288ec6e0048864b2ffd7 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 22 Dec 2023 15:46:06 +0530 Subject: [PATCH 70/79] fix: `Reserved Stock` report --- .../report/reserved_stock/reserved_stock.js | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js index 68727411d5..2b075e2276 100644 --- a/erpnext/stock/report/reserved_stock/reserved_stock.js +++ b/erpnext/stock/report/reserved_stock/reserved_stock.js @@ -149,34 +149,36 @@ frappe.query_reports["Reserved Stock"] = { formatter: (value, row, column, data, default_formatter) => { value = default_formatter(value, row, column, data); - if (column.fieldname == "status") { - switch (data.status) { - case "Partially Reserved": - value = "" + value + ""; - break; - case "Reserved": - value = "" + value + ""; - break; - case "Partially Delivered": - value = "" + value + ""; - break; - case "Delivered": - value = "" + value + ""; - break; + if (data) { + if (column.fieldname == "status") { + switch (data.status) { + case "Partially Reserved": + value = "" + value + ""; + break; + case "Reserved": + value = "" + value + ""; + break; + case "Partially Delivered": + value = "" + value + ""; + break; + case "Delivered": + value = "" + value + ""; + break; + } } - } - else if (column.fieldname == "delivered_qty") { - if (data.delivered_qty > 0) { - if (data.reserved_qty > data.delivered_qty) { - value = "" + value + ""; + else if (column.fieldname == "delivered_qty") { + if (data.delivered_qty > 0) { + if (data.reserved_qty > data.delivered_qty) { + value = "" + value + ""; + } + else { + value = "" + value + ""; + } } else { - value = "" + value + ""; + value = "" + value + ""; } } - else { - value = "" + value + ""; - } } return value; From 161ae1edd1ebcafd14d7a302ad1adde238e43426 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 22 Dec 2023 17:37:17 +0530 Subject: [PATCH 71/79] fix: reset the incoming rate on changing of the warehouse (#38925) --- erpnext/public/js/utils/sales_common.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 084cca7db5..b92b02e826 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -184,6 +184,12 @@ erpnext.sales_common = { refresh_field("incentives",row.name,row.parentfield); } + warehouse(doc, cdt, cdn) { + if (doc.docstatus === 0 && doc.is_return && !doc.return_against) { + frappe.model.set_value(cdt, cdn, "incoming_rate", 0.0); + } + } + toggle_editable_price_list_rate() { var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name); var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate")); From 47f7b65058e860e91c58dd12da6e48bd70ae60f6 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Sun, 24 Dec 2023 16:13:31 +0530 Subject: [PATCH 72/79] feat: voucher subtype for general ledger (#38822) * feat: add voucher subtype column to gle * feat: add logic to set voucher subtypes * feat: fetch voucher subtype in ledger report * fix: order of conditions --- .../accounts/doctype/gl_entry/gl_entry.json | 30 +++++++++++-------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 ++ .../report/general_ledger/general_ledger.py | 8 ++++- erpnext/controllers/accounts_controller.py | 20 +++++++++++++ 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index c4492be367..09912e9896 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -23,6 +23,7 @@ "against_voucher_type", "against_voucher", "voucher_type", + "voucher_subtype", "voucher_no", "voucher_detail_no", "project", @@ -138,19 +139,19 @@ "options": "DocType" }, { - "fieldname": "against", - "fieldtype": "Text", - "in_filter": 1, - "label": "Against", - "oldfieldname": "against", - "oldfieldtype": "Text" + "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_link", + "fieldtype": "Dynamic Link", + "in_filter": 1, + "label": "Against", + "options": "against_type" }, { "fieldname": "against_voucher_type", @@ -294,13 +295,18 @@ "fieldtype": "Currency", "label": "Credit Amount in Transaction Currency", "options": "transaction_currency" + }, + { + "fieldname": "voucher_subtype", + "fieldtype": "Small Text", + "label": "Voucher Subtype" } ], "icon": "fa fa-list", "idx": 1, "in_create": 1, "links": [], - "modified": "2023-11-08 12:20:23.031733", + "modified": "2023-12-18 15:38:14.006208", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index f7dd29ab1c..139f52696b 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -39,6 +39,8 @@ class GLEntry(Document): account: DF.Link | None account_currency: DF.Link | None against: DF.Text | None + against_link: DF.DynamicLink | None + against_type: DF.Link | None against_voucher: DF.DynamicLink | None against_voucher_type: DF.Link | None company: DF.Link | None @@ -66,6 +68,7 @@ class GLEntry(Document): transaction_exchange_rate: DF.Float voucher_detail_no: DF.Data | None voucher_no: DF.DynamicLink | None + voucher_subtype: DF.SmallText | None voucher_type: DF.Link | None # end: auto-generated types diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index b45ff60f1b..4054dca360 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -200,7 +200,7 @@ def get_gl_entries(filters, accounting_dimensions): """ select name as gl_entry, posting_date, account, party_type, party, - voucher_type, voucher_no, {dimension_fields} + voucher_type, voucher_subtype, voucher_no, {dimension_fields} cost_center, project, {transaction_currency_fields} against_voucher_type, against_voucher, account_currency, against_link, against, is_opening, creation {select_fields} @@ -609,6 +609,12 @@ def get_columns(filters): columns += [ {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120}, + { + "label": _("Voucher Subtype"), + "fieldname": "voucher_subtype", + "fieldtype": "Data", + "width": 180, + }, { "label": _("Voucher No"), "fieldname": "voucher_no", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d88424b7e7..febad18058 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -874,6 +874,7 @@ class AccountsController(TransactionBase): "project": self.get("project"), "post_net_value": args.get("post_net_value"), "voucher_detail_no": args.get("voucher_detail_no"), + "voucher_subtype": self.get_voucher_subtype(), } ) @@ -929,6 +930,25 @@ class AccountsController(TransactionBase): return gl_dict + def get_voucher_subtype(self): + voucher_subtypes = { + "Journal Entry": "voucher_type", + "Payment Entry": "payment_type", + "Stock Entry": "stock_entry_type", + "Asset Capitalization": "entry_type", + } + if self.doctype in voucher_subtypes: + return self.get(voucher_subtypes[self.doctype]) + elif self.doctype == "Purchase Receipt" and self.is_return: + return "Purchase Return" + elif self.doctype == "Delivery Note" and self.is_return: + return "Sales Return" + elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice": + return "Credit Note" + elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice": + return "Debit Note" + return self.doctype + def get_value_in_transaction_currency(self, account_currency, args, field): if account_currency == self.get("currency"): return args.get(field + "_in_account_currency") From 87ba3b64f78989d9807c2a03cf7c715171b4499e Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Sun, 24 Dec 2023 11:52:26 +0100 Subject: [PATCH 73/79] chore: german translation for actual time (#38837) chore: german translation for actual time --- erpnext/translations/de.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 2745d4da12..d05d0d96c3 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -103,6 +103,7 @@ Actual Qty is mandatory,Die tatsächliche Menge ist zwingend erforderlich, Actual Qty {0} / Waiting Qty {1},Tatsächliche Menge {0} / Wartezeit {1}, Actual Qty: Quantity available in the warehouse.,Tatsächliche Menge: Menge verfügbar im Lager., Actual qty in stock,Tatsächliche Menge auf Lager, +Actual Time (in Hours via Time Sheet), IST Zeit (in Stunden aus Zeiterfassung), Actual type tax cannot be included in Item rate in row {0},Tatsächliche Steuerart kann nicht im Artikelpreis in Zeile {0} beinhaltet sein, Add,Hinzufügen, Add / Edit Prices,Preise hinzufügen / bearbeiten, From eb5bb9f9a99e54be71c7ae5e24235b5b44692444 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 25 Dec 2023 22:05:02 +0530 Subject: [PATCH 74/79] fix(ux): make PI Item rate field editable --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 7cad3ae1c0..9cf4e4fd7c 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -288,7 +288,6 @@ "oldfieldname": "import_rate", "oldfieldtype": "Currency", "options": "currency", - "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)", "reqd": 1 }, { @@ -919,7 +918,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-30 16:26:05.629780", + "modified": "2023-12-25 22:00:28.043555", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", From cb9114442b936f03c9c975900282b3b9db62e453 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 25 Dec 2023 22:16:41 +0530 Subject: [PATCH 75/79] fix(ux): make PI Item rate field readonly based on `Maintain Same Rate` --- .../doctype/purchase_invoice/purchase_invoice.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index cebd61a6f5..215d8ec215 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -163,6 +163,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. } }) }, __("Get Items From")); + + if (!this.frm.doc.is_return) { + frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => { + if (value) { + this.frm.doc.items.forEach((item) => { + this.frm.fields_dict.items.grid.update_docfield_property( + "rate", "read_only", (item.purchase_receipt && item.pr_detail) + ); + }); + } + }); + } } this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted); From b1ba2103323c8bfe066c288b4c285cf1f3c5b20b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 25 Dec 2023 22:41:18 +0530 Subject: [PATCH 76/79] fix(ux): make PR Item rate field readonly based on `Maintain Same Rate` --- .../doctype/purchase_receipt/purchase_receipt.js | 14 ++++++++++++++ .../purchase_receipt_item.json | 3 +-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 6c9d3392e3..2cbccb0774 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -88,6 +88,20 @@ frappe.ui.form.on("Purchase Receipt", { }, __('Create')); } + if (frm.doc.docstatus === 0) { + if (!frm.doc.is_return) { + frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => { + if (value) { + frm.doc.items.forEach((item) => { + frm.fields_dict.items.grid.update_docfield_property( + "rate", "read_only", (item.purchase_order && item.purchase_order_item) + ); + }); + } + }); + } + } + frm.events.add_custom_buttons(frm); }, diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 7344d2a599..9bd692ad61 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -359,7 +359,6 @@ "oldfieldtype": "Currency", "options": "currency", "print_width": "100px", - "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)", "width": "100px" }, { @@ -1104,7 +1103,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-30 16:12:02.364608", + "modified": "2023-12-25 22:32:09.801965", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 06d6220a2aa2f7855db2c2f985d046280e26f13a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 26 Dec 2023 15:23:32 +0530 Subject: [PATCH 77/79] fix: do not make serial batch bundle for zero qty (#38949) --- .../serial_and_batch_bundle.py | 21 +++++++ .../stock_reconciliation.py | 5 +- .../test_stock_reconciliation.py | 60 +++++++++++++++++++ 3 files changed, 85 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 37916e21c8..9a3f7e5b46 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 @@ -85,6 +85,7 @@ class SerialandBatchBundle(Document): # end: auto-generated types def validate(self): + self.set_batch_no() self.validate_serial_and_batch_no() self.validate_duplicate_serial_and_batch_no() self.validate_voucher_no() @@ -99,6 +100,26 @@ class SerialandBatchBundle(Document): self.set_incoming_rate() self.calculate_qty_and_amount() + def set_batch_no(self): + if self.has_serial_no and self.has_batch_no: + serial_nos = [d.serial_no for d in self.entries if d.serial_no] + has_no_batch = any(not d.batch_no for d in self.entries) + if not has_no_batch: + return + + serial_no_batch = frappe._dict( + frappe.get_all( + "Serial No", + filters={"name": ("in", serial_nos)}, + fields=["name", "batch_no"], + as_list=True, + ) + ) + + for row in self.entries: + if not row.batch_no: + row.batch_no = serial_no_batch.get(row.serial_no) + def validate_serial_nos_inventory(self): if not (self.has_serial_no and self.type_of_transaction == "Outward"): return diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e8d652e2b2..6819968394 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -171,7 +171,7 @@ class StockReconciliation(StockController): }, ) - if item_details.has_batch_no: + elif item_details.has_batch_no: batch_nos_details = get_available_batches( frappe._dict( { @@ -228,6 +228,9 @@ class StockReconciliation(StockController): def set_new_serial_and_batch_bundle(self): for item in self.items: + if not item.qty: + continue + if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle: current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 1ec99bf9a5..70e9fb2205 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -865,6 +865,66 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): sr1.load_from_db() self.assertEqual(sr1.difference_amount, 10000) + def test_make_stock_zero_for_serial_batch_item(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + serial_item = self.make_item( + properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DJJ.####"} + ).name + batch_item = self.make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "BDJJ.####", + "create_new_batch": 1, + } + ).name + + serial_batch_item = self.make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "ADJJ.####", + "create_new_batch": 1, + "has_serial_no": 1, + "serial_no_series": "SN-ADJJ.####", + } + ).name + + warehouse = "_Test Warehouse - _TC" + + for item_code in [serial_item, batch_item, serial_batch_item]: + make_stock_entry( + item_code=item_code, + target=warehouse, + qty=10, + basic_rate=100, + ) + + _reco = create_stock_reconciliation( + item_code=item_code, + warehouse=warehouse, + qty=0.0, + ) + + serial_batch_bundle = frappe.get_all( + "Stock Ledger Entry", + {"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name}, + "serial_and_batch_bundle", + ) + + self.assertEqual(len(serial_batch_bundle), 1) + + _reco.cancel() + + serial_batch_bundle = frappe.get_all( + "Stock Ledger Entry", + {"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name}, + "serial_and_batch_bundle", + ) + + self.assertEqual(len(serial_batch_bundle), 0) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) From d00f6672a8d9db1e28f9a86ff8efbedcdc95c41a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 26 Dec 2023 15:24:04 +0530 Subject: [PATCH 78/79] fix: not able to import serial batch bundle using csv (#38950) --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 4 ++-- 1 file changed, 2 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 9a3f7e5b46..afb53fb112 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 @@ -1185,7 +1185,7 @@ def create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=Non doc.append( "entries", { - "qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1), + "qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1), "warehouse": warehouse, "batch_no": row.batch_no, "serial_no": row.serial_no, @@ -1213,7 +1213,7 @@ def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=Non doc.append( "entries", { - "qty": (d.get("qty") or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1), + "qty": (flt(d.get("qty")) or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1), "warehouse": warehouse or d.get("warehouse"), "batch_no": d.get("batch_no"), "serial_no": d.get("serial_no"), From b09c9354fb621c4283d6ebde91f3d061ea88f7f6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 26 Dec 2023 20:11:58 +0530 Subject: [PATCH 79/79] fix: min order qty optional in production plan (#38956) * fix: min order qty optional in production plan * fix: test cases --- erpnext/manufacturing/doctype/bom/bom.json | 3 ++- .../production_plan/production_plan.js | 2 ++ .../production_plan/production_plan.json | 9 +++++++- .../production_plan/production_plan.py | 19 +++++++++++++-- .../production_plan/test_production_plan.py | 23 +++++++++++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index e8d3542835..5083873681 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -218,6 +218,7 @@ "options": "\nWork Order\nJob Card" }, { + "default": "1", "fieldname": "conversion_rate", "fieldtype": "Float", "label": "Conversion Rate", @@ -636,7 +637,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-08-07 11:38:08.152294", + "modified": "2023-12-26 19:34:08.159312", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index dd102b0fae..cd92263543 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -305,6 +305,8 @@ frappe.ui.form.on('Production Plan', { frappe.throw(__("Select the Warehouse")); } + frm.set_value("consider_minimum_order_qty", 0); + if (frm.doc.ignore_existing_ordered_qty) { frm.events.get_items_for_material_requests(frm); } else { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 49386c4ebc..257b60c486 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -48,6 +48,7 @@ "material_request_planning", "include_non_stock_items", "include_subcontracted_items", + "consider_minimum_order_qty", "include_safety_stock", "ignore_existing_ordered_qty", "column_break_25", @@ -423,13 +424,19 @@ "fieldtype": "Link", "label": "Sub Assembly Warehouse", "options": "Warehouse" + }, + { + "default": "0", + "fieldname": "consider_minimum_order_qty", + "fieldtype": "Check", + "label": "Consider Minimum Order Qty" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-11-03 14:08:11.928027", + "modified": "2023-12-26 16:31:13.740777", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 4b72a83b05..2bfd4be53a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -67,6 +67,7 @@ class ProductionPlan(Document): combine_items: DF.Check combine_sub_items: DF.Check company: DF.Link + consider_minimum_order_qty: DF.Check customer: DF.Link | None for_warehouse: DF.Link | None from_date: DF.Date | None @@ -1211,7 +1212,14 @@ def get_subitems( def get_material_request_items( - row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict + doc, + row, + sales_order, + company, + ignore_existing_ordered_qty, + include_safety_stock, + warehouse, + bin_dict, ): total_qty = row["qty"] @@ -1220,8 +1228,14 @@ def get_material_request_items( required_qty = total_qty elif total_qty > bin_dict.get("projected_qty", 0): required_qty = total_qty - bin_dict.get("projected_qty", 0) - if required_qty > 0 and required_qty < row["min_order_qty"]: + + if ( + doc.get("consider_minimum_order_qty") + and required_qty > 0 + and required_qty < row["min_order_qty"] + ): required_qty = row["min_order_qty"] + item_group_defaults = get_item_group_defaults(row.item_code, company) if not row["purchase_uom"]: @@ -1559,6 +1573,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d if details.qty > 0: items = get_material_request_items( + doc, details, sales_order, company, diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index f86725d601..cb99b8845a 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1499,6 +1499,29 @@ class TestProductionPlan(FrappeTestCase): after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) self.assertAlmostEqual(after_qty, before_qty) + def test_min_order_qty_in_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}).name + rm_item = make_item(properties={"is_stock_item": 1, "min_order_qty": 1000}).name + + 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, do_not_submit=1) + + pln.for_warehouse = rm_warehouse + mr_items = get_items_for_material_requests(pln.as_dict()) + for d in mr_items: + self.assertEqual(d.get("quantity"), 10.0) + + pln.consider_minimum_order_qty = 1 + mr_items = get_items_for_material_requests(pln.as_dict()) + for d in mr_items: + self.assertEqual(d.get("quantity"), 1000.0) + def create_production_plan(**args): """