From 8903c1bc6f24c4e0187f05739887ac6f4af6a7b6 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Oct 2023 11:12:55 +0530 Subject: [PATCH 001/163] feat: multi-select customer group in AR Report --- .../accounts_receivable.js | 9 ++++++--- .../accounts_receivable.py | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 1073be0bdc..393a6ff4ed 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -114,10 +114,13 @@ frappe.query_reports["Accounts Receivable"] = { "reqd": 1 }, { - "fieldname": "customer_group", + "fieldname":"customer_group", "label": __("Customer Group"), - "fieldtype": "Link", - "options": "Customer Group" + "fieldtype": "MultiSelectList", + "options": "Customer Group", + get_data: function(txt) { + return frappe.db.get_link_options('Customer Group', txt); + } }, { "fieldname": "payment_terms_template", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index b9c7a0bfb8..dfe801c73b 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -823,7 +823,9 @@ class ReceivablePayableReport(object): self.customer = qb.DocType("Customer") if self.filters.get("customer_group"): - self.get_hierarchical_filters("Customer Group", "customer_group") + groups = get_customer_group_with_children(self.filters.customer_group) + customers = qb.from_(self.customer).select(self.customer.name).where(self.customer['customer_group'].isin(groups)) + self.qb_selection_filter.append(self.ple.party.isin(customers)) if self.filters.get("territory"): self.get_hierarchical_filters("Territory", "territory") @@ -1115,3 +1117,18 @@ class ReceivablePayableReport(object): .run() ) self.err_journals = [x[0] for x in results] if results else [] + +def get_customer_group_with_children(customer_groups): + if not isinstance(customer_groups, list): + customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d] + + all_customer_groups = [] + for d in customer_groups: + if frappe.db.exists("Customer Group", d): + lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"]) + children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) + all_customer_groups += [c.name for c in children] + else: + frappe.throw(_("Customer Group: {0} does not exist").format(d)) + + return list(set(all_customer_groups)) From b60c57a97dee1ab1a2b1654d4cc2d146e0141ff2 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Oct 2023 11:22:55 +0530 Subject: [PATCH 002/163] fix: minor issue --- .../report/accounts_receivable/accounts_receivable.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index dfe801c73b..39dfd296e3 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -824,7 +824,11 @@ class ReceivablePayableReport(object): if self.filters.get("customer_group"): groups = get_customer_group_with_children(self.filters.customer_group) - customers = qb.from_(self.customer).select(self.customer.name).where(self.customer['customer_group'].isin(groups)) + customers = ( + qb.from_(self.customer) + .select(self.customer.name) + .where(self.customer["customer_group"].isin(groups)) + ) self.qb_selection_filter.append(self.ple.party.isin(customers)) if self.filters.get("territory"): @@ -1118,6 +1122,7 @@ class ReceivablePayableReport(object): ) self.err_journals = [x[0] for x in results] if results else [] + def get_customer_group_with_children(customer_groups): if not isinstance(customer_groups, list): customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d] From ece704939055d2998a93701614dce5c4eea9dd76 Mon Sep 17 00:00:00 2001 From: Richard Case Date: Mon, 30 Oct 2023 00:15:05 +0000 Subject: [PATCH 003/163] feat: in_party_currency option for AR/AP reports --- .../accounts_payable/accounts_payable.js | 5 +++++ .../accounts_receivable.js | 5 +++++ .../accounts_receivable.py | 22 +++++++++++-------- 3 files changed, 23 insertions(+), 9 deletions(-) mode change 100755 => 100644 erpnext/accounts/report/accounts_receivable/accounts_receivable.py diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 9c73cbb344..8fa81cd9dc 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -143,6 +143,11 @@ frappe.query_reports["Accounts Payable"] = { "fieldname": "show_future_payments", "label": __("Show Future Payments"), "fieldtype": "Check", + }, + { + "fieldname": "in_party_currency", + "label": __("In Party Currency"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 1073be0bdc..c49b67a727 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -172,6 +172,11 @@ frappe.query_reports["Accounts Receivable"] = { "fieldname": "show_remarks", "label": __("Show Remarks"), "fieldtype": "Check", + }, + { + "fieldname": "in_party_currency", + "label": __("In Party Currency"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py old mode 100755 new mode 100644 index b9c7a0bfb8..e8cf91507b --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -28,8 +28,8 @@ from erpnext.accounts.utils import get_currency_precision # 6. Configurable Ageing Groups (0-30, 30-60 etc) can be set via filters # 7. For overpayment against an invoice with payment terms, there will be an additional row # 8. Invoice details like Sales Persons, Delivery Notes are also fetched comma separated -# 9. Report amounts are in "Party Currency" if party is selected, or company currency for multi-party -# 10. This reports is based on all GL Entries that are made against account_type "Receivable" or "Payable" +# 9. Report amounts are in party currency if in_party_currency is selected, otherwise company currency +# 10. This report is based on Payment Ledger Entries def execute(filters=None): @@ -84,6 +84,9 @@ class ReceivablePayableReport(object): self.total_row_map = {} self.skip_total_row = 1 + if self.filters.get("in_party_currency"): + self.skip_total_row = 1 + def get_data(self): self.get_ple_entries() self.get_sales_invoices_or_customers_based_on_sales_person() @@ -140,7 +143,7 @@ class ReceivablePayableReport(object): if self.filters.get("group_by_party"): self.init_subtotal_row(ple.party) - if self.filters.get("group_by_party"): + if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"): self.init_subtotal_row("Total") def get_invoices(self, ple): @@ -210,8 +213,7 @@ class ReceivablePayableReport(object): if not row: return - # amount in "Party Currency", if its supplied. If not, amount in company currency - if self.filters.get("party_type") and self.filters.get("party"): + if self.filters.get("in_party_currency"): amount = ple.amount_in_account_currency else: amount = ple.amount @@ -242,8 +244,10 @@ class ReceivablePayableReport(object): def update_sub_total_row(self, row, party): total_row = self.total_row_map.get(party) - for field in self.get_currency_fields(): - total_row[field] += row.get(field, 0.0) + if total_row: + for field in self.get_currency_fields(): + total_row[field] += row.get(field, 0.0) + total_row["currency"] = row.get("currency", "") def append_subtotal_row(self, party): sub_total_row = self.total_row_map.get(party) @@ -295,7 +299,7 @@ class ReceivablePayableReport(object): if self.filters.get("group_by_party"): self.append_subtotal_row(self.previous_party) if self.data: - self.data.append(self.total_row_map.get("Total")) + self.data.append(self.total_row_map.get("Total", {})) def append_row(self, row): self.allocate_future_payments(row) @@ -426,7 +430,7 @@ class ReceivablePayableReport(object): party_details = self.get_party_details(row.party) or {} row.update(party_details) - if self.filters.get("party_type") and self.filters.get("party"): + if self.filters.get("in_party_currency"): row.currency = row.account_currency else: row.currency = self.company_currency From 208d5042ee96c01eec19ed54bfbc609a08e6de16 Mon Sep 17 00:00:00 2001 From: Richard Case Date: Mon, 30 Oct 2023 00:45:02 +0000 Subject: [PATCH 004/163] chore: update tests --- .../accounts/report/accounts_payable/test_accounts_payable.py | 1 + .../report/accounts_receivable/test_accounts_receivable.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 9f03d92cd5..b4cb25ff1b 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -40,6 +40,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "range2": 60, "range3": 90, "range4": 120, + "in_party_currency": 1, } data = execute(filters) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index cbeb6d3106..04a4e8038e 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -557,6 +557,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): "range2": 60, "range3": 90, "range4": 120, + "in_party_currency": 1, } si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) From 3d9938221a7fef7e08c0072364d8ebb855933d11 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 1 Nov 2023 15:40:54 +0530 Subject: [PATCH 005/163] fix: remove validation for negative outstanding invoices --- .../doctype/payment_entry/payment_entry.py | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e6403fddef..f98cd65950 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -83,7 +83,6 @@ class PaymentEntry(AccountsController): self.apply_taxes() self.set_amounts_after_tax() self.clear_unallocated_reference_document_rows() - self.validate_payment_against_negative_invoice() self.validate_transaction_reference() self.set_title() self.set_remarks() @@ -952,35 +951,6 @@ class PaymentEntry(AccountsController): self.name, ) - def validate_payment_against_negative_invoice(self): - if (self.payment_type != "Pay" or self.party_type != "Customer") and ( - self.payment_type != "Receive" or self.party_type != "Supplier" - ): - return - - total_negative_outstanding = sum( - abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 - ) - - paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount - additional_charges = sum(flt(d.amount) for d in self.deductions) - - if not total_negative_outstanding: - if self.party_type == "Customer": - msg = _("Cannot pay to Customer without any negative outstanding invoice") - else: - msg = _("Cannot receive from Supplier without any negative outstanding invoice") - - frappe.throw(msg, InvalidPaymentEntry) - - elif paid_amount - additional_charges > total_negative_outstanding: - frappe.throw( - _("Paid Amount cannot be greater than total negative outstanding amount {0}").format( - fmt_money(total_negative_outstanding) - ), - InvalidPaymentEntry, - ) - def set_title(self): if frappe.flags.in_import and self.title: # do not set title dynamically if title exists during data import. @@ -1083,9 +1053,7 @@ class PaymentEntry(AccountsController): item=self, ) - dr_or_cr = ( - "credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit" - ) + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" for d in self.get("references"): cost_center = self.cost_center From 6bd56d2d5f62814eb2f777934ed894bc5709e947 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 1 Nov 2023 20:11:00 +0530 Subject: [PATCH 006/163] refactor: `split_invoices_based_on_payment_terms` - Invoices were in the wrong order due to the logic. The invoices with payment terms were added first and the rest after. - Overly long function with unnecessary loops (reduced to one main loop) and complexity - The split row as per payment terms was not ordered. So the second installment was allocated first --- .../doctype/payment_entry/payment_entry.py | 145 ++++++++++-------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e6403fddef..fc6e1f462e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1698,13 +1698,42 @@ def get_outstanding_reference_documents(args, validate=False): return data -def split_invoices_based_on_payment_terms(outstanding_invoices, company): - invoice_ref_based_on_payment_terms = {} +def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list: + """Split a list of invoices based on their payment terms.""" + exc_rates = get_currency_data(outstanding_invoices, company) + outstanding_invoices_after_split = [] + for entry in outstanding_invoices: + if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]: + if payment_term_template := frappe.db.get_value( + entry.voucher_type, entry.voucher_no, "payment_terms_template" + ): + split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates) + if not split_rows: + continue + + frappe.msgprint( + _("Spliting {} {} into {} row(s) as per Payment Terms").format( + split_rows[0]["voucher_type"], split_rows[0]["voucher_no"], len(split_rows) + ), + alert=True, + ) + outstanding_invoices_after_split += split_rows + continue + + # If not an invoice or no payment terms template, add as it is + outstanding_invoices_after_split.append(entry) + + return outstanding_invoices_after_split + + +def get_currency_data(outstanding_invoices: list, company: str = None) -> dict: + """Get currency and conversion data for a list of invoices.""" + exc_rates = frappe._dict() company_currency = ( frappe.db.get_value("Company", company, "default_currency") if company else None ) - exc_rates = frappe._dict() + for doctype in ["Sales Invoice", "Purchase Invoice"]: invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] for x in frappe.db.get_all( @@ -1719,72 +1748,54 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company): company_currency=company_currency, ) - for idx, d in enumerate(outstanding_invoices): - if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]: - payment_term_template = frappe.db.get_value( - d.voucher_type, d.voucher_no, "payment_terms_template" + return exc_rates + + +def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list: + """Split invoice based on its payment schedule table.""" + split_rows = [] + allocate_payment_based_on_payment_terms = frappe.db.get_value( + "Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms" + ) + + if not allocate_payment_based_on_payment_terms: + return [invoice] + + payment_schedule = frappe.get_all( + "Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date" + ) + for payment_term in payment_schedule: + if not payment_term.outstanding > 0.1: + continue + + doc_details = exc_rates.get(payment_term.parent, None) + is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and ( + doc_details.party_account_currency != doc_details.company_currency + ) + payment_term_outstanding = flt(payment_term.outstanding) + if not is_multi_currency_acc: + payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding) + + split_rows.append( + frappe._dict( + { + "due_date": invoice.due_date, + "currency": invoice.currency, + "voucher_no": invoice.voucher_no, + "voucher_type": invoice.voucher_type, + "posting_date": invoice.posting_date, + "invoice_amount": flt(invoice.invoice_amount), + "outstanding_amount": payment_term_outstanding + if payment_term_outstanding + else invoice.outstanding_amount, + "payment_term_outstanding": payment_term_outstanding, + "payment_amount": payment_term.payment_amount, + "payment_term": payment_term.payment_term, + } ) - if payment_term_template: - allocate_payment_based_on_payment_terms = frappe.get_cached_value( - "Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms" - ) - if allocate_payment_based_on_payment_terms: - payment_schedule = frappe.get_all( - "Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"] - ) + ) - for payment_term in payment_schedule: - if payment_term.outstanding > 0.1: - doc_details = exc_rates.get(payment_term.parent, None) - is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and ( - doc_details.party_account_currency != doc_details.company_currency - ) - payment_term_outstanding = flt(payment_term.outstanding) - if not is_multi_currency_acc: - payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding) - - invoice_ref_based_on_payment_terms.setdefault(idx, []) - invoice_ref_based_on_payment_terms[idx].append( - frappe._dict( - { - "due_date": d.due_date, - "currency": d.currency, - "voucher_no": d.voucher_no, - "voucher_type": d.voucher_type, - "posting_date": d.posting_date, - "invoice_amount": flt(d.invoice_amount), - "outstanding_amount": payment_term_outstanding - if payment_term_outstanding - else d.outstanding_amount, - "payment_term_outstanding": payment_term_outstanding, - "payment_amount": payment_term.payment_amount, - "payment_term": payment_term.payment_term, - "account": d.account, - } - ) - ) - - outstanding_invoices_after_split = [] - if invoice_ref_based_on_payment_terms: - for idx, ref in invoice_ref_based_on_payment_terms.items(): - voucher_no = ref[0]["voucher_no"] - voucher_type = ref[0]["voucher_type"] - - frappe.msgprint( - _("Spliting {} {} into {} row(s) as per Payment Terms").format( - voucher_type, voucher_no, len(ref) - ), - alert=True, - ) - - outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx] - - existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices)) - index = outstanding_invoices.index(existing_row[0]) - outstanding_invoices.pop(index) - - outstanding_invoices_after_split += outstanding_invoices - return outstanding_invoices_after_split + return split_rows def get_orders_to_be_billed( From 1f4b38174899d99be2a3ac894a328c6b7ae7475f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 2 Nov 2023 17:38:43 +0530 Subject: [PATCH 007/163] fix: test for invoice returns --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f98cd65950..49ca76ed4f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1073,8 +1073,8 @@ class PaymentEntry(AccountsController): gle.update( { - dr_or_cr: allocated_amount_in_company_currency, - dr_or_cr + "_in_account_currency": d.allocated_amount, + dr_or_cr: abs(allocated_amount_in_company_currency), + dr_or_cr + "_in_account_currency": abs(d.allocated_amount), "against_voucher_type": against_voucher_type, "against_voucher": against_voucher, "cost_center": cost_center, From cd1e016163141a4851b9128a2ff008809942482e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 2 Nov 2023 17:39:27 +0530 Subject: [PATCH 008/163] test: receive payments from payable party --- .../payment_entry/test_payment_entry.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index edfec41918..3e8292946c 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1262,6 +1262,39 @@ class TestPaymentEntry(FrappeTestCase): so.reload() self.assertEqual(so.advance_paid, so.rounded_total) + def test_receive_payment_from_payable_party_type(self): + pe = create_payment_entry( + party_type="Supplier", + party="_Test Supplier", + payment_type="Receive", + paid_from="Creditors - _TC", + paid_to="_Test Cash - _TC", + save=True, + submit=True, + ) + self.voucher_no = pe.name + self.expected_gle = [ + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + {"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0}, + ] + self.check_gl_entries() + + def check_gl_entries(self): + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select( + gle.account, + gle.debit, + gle.credit, + ) + .where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0)) + .orderby(gle.account) + ).run(as_dict=True) + for row in range(len(self.expected_gle)): + for field in ["account", "debit", "credit"]: + self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 40157235916578ae88d2ec9a85f481c92fec9278 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 3 Nov 2023 11:58:25 +0530 Subject: [PATCH 009/163] fix: credit note receive payment entry --- .../doctype/payment_entry/payment_entry.py | 19 +++++++++++++++++-- erpnext/accounts/utils.py | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 49ca76ed4f..23b58947d3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -33,6 +33,7 @@ from erpnext.accounts.utils import ( get_account_currency, get_balance_on, get_outstanding_invoices, + get_party_types_from_account_type, ) from erpnext.controllers.accounts_controller import ( AccountsController, @@ -1071,10 +1072,24 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name + reverse_dr_or_cr = 0 + if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: + is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") + payable_party_types = get_party_types_from_account_type("Payable") + receivable_party_types = get_party_types_from_account_type("Receivable") + if is_return and self.party_type in receivable_party_types and self.payment_type == "Pay": + reverse_dr_or_cr = 1 + elif is_return and self.party_type in payable_party_types and self.payment_type == "Receive": + reverse_dr_or_cr = 1 + gle.update( { - dr_or_cr: abs(allocated_amount_in_company_currency), - dr_or_cr + "_in_account_currency": abs(d.allocated_amount), + dr_or_cr: abs(allocated_amount_in_company_currency) + if reverse_dr_or_cr + else allocated_amount_in_company_currency, + dr_or_cr + "_in_account_currency": abs(d.allocated_amount) + if reverse_dr_or_cr + else d.allocated_amount, "against_voucher_type": against_voucher_type, "against_voucher": against_voucher, "cost_center": cost_center, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1c7052f8ff..0a116a7345 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2041,3 +2041,7 @@ def create_gain_loss_journal( journal_entry.save() journal_entry.submit() return journal_entry.name + + +def get_party_types_from_account_type(account_type): + return frappe.db.get_list("Party Type", {"account_type": account_type}, pluck="name") From 4867ca353c1ba199844830eb539d9dca5455949b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 3 Nov 2023 12:00:56 +0530 Subject: [PATCH 010/163] refactor: move common util for fetching party types using account type --- .../report/accounts_receivable/accounts_receivable.py | 6 ++---- .../accounts_receivable_summary.py | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 20444f9496..4cc0f0c6d1 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -14,7 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, get_dimension_with_children, ) -from erpnext.accounts.utils import get_currency_precision +from erpnext.accounts.utils import get_currency_precision, get_party_types_from_account_type # This report gives a summary of all Outstanding Invoices considering the following @@ -72,9 +72,7 @@ class ReceivablePayableReport(object): self.currency_precision = get_currency_precision() or 2 self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit" self.account_type = self.filters.account_type - self.party_type = frappe.db.get_all( - "Party Type", {"account_type": self.account_type}, pluck="name" - ) + self.party_type = get_party_types_from_account_type(self.account_type) self.party_details = {} self.invoices = set() self.skip_total_row = 0 diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 60274cd8b1..d50cf0708e 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -8,6 +8,7 @@ from frappe.utils import cint, flt from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport +from erpnext.accounts.utils import get_party_types_from_account_type def execute(filters=None): @@ -22,9 +23,7 @@ def execute(filters=None): class AccountsReceivableSummary(ReceivablePayableReport): def run(self, args): self.account_type = args.get("account_type") - self.party_type = frappe.db.get_all( - "Party Type", {"account_type": self.account_type}, pluck="name" - ) + self.party_type = get_party_types_from_account_type(self.account_type) self.party_naming_by = frappe.db.get_value( args.get("naming_by")[0], None, args.get("naming_by")[1] ) From 05f24ede96a30d194db9334a55706689f63462cb Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 4 Nov 2023 11:23:15 +0530 Subject: [PATCH 011/163] fix: link between parent and child procedure --- .../quality_procedure/quality_procedure.py | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py index e8604080fb..6834abc9d4 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py @@ -16,16 +16,13 @@ class QualityProcedure(NestedSet): def on_update(self): NestedSet.on_update(self) self.set_parent() + self.remove_parent_from_old_child() + self.add_child_to_parent() + self.remove_child_from_old_parent() def after_insert(self): self.set_parent() - - # add child to parent if missing - if self.parent_quality_procedure: - parent = frappe.get_doc("Quality Procedure", self.parent_quality_procedure) - if not [d for d in parent.processes if d.procedure == self.name]: - parent.append("processes", {"procedure": self.name, "process_description": self.name}) - parent.save() + self.add_child_to_parent() def on_trash(self): # clear from child table (sub procedures) @@ -36,15 +33,6 @@ class QualityProcedure(NestedSet): ) NestedSet.on_trash(self, allow_root_deletion=True) - def set_parent(self): - for process in self.processes: - # Set parent for only those children who don't have a parent - has_parent = frappe.db.get_value( - "Quality Procedure", process.procedure, "parent_quality_procedure" - ) - if not has_parent and process.procedure: - frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name) - def check_for_incorrect_child(self): for process in self.processes: if process.procedure: @@ -61,6 +49,48 @@ class QualityProcedure(NestedSet): title=_("Invalid Child Procedure"), ) + def set_parent(self): + """Set `Parent Procedure` in `Child Procedures`""" + + for process in self.processes: + if process.procedure: + if not frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure"): + frappe.db.set_value( + "Quality Procedure", process.procedure, "parent_quality_procedure", self.name + ) + + def remove_parent_from_old_child(self): + """Remove `Parent Procedure` from `Old Child Procedures`""" + + if old_doc := self.get_doc_before_save(): + if old_child_procedures := set([d.procedure for d in old_doc.processes if d.procedure]): + current_child_procedures = set([d.procedure for d in self.processes if d.procedure]) + + if removed_child_procedures := list(old_child_procedures.difference(current_child_procedures)): + for child_procedure in removed_child_procedures: + frappe.db.set_value("Quality Procedure", child_procedure, "parent_quality_procedure", None) + + def add_child_to_parent(self): + """Add `Child Procedure` to `Parent Procedure`""" + + if self.parent_quality_procedure: + parent = frappe.get_doc("Quality Procedure", self.parent_quality_procedure) + if not [d for d in parent.processes if d.procedure == self.name]: + parent.append("processes", {"procedure": self.name, "process_description": self.name}) + parent.save() + + def remove_child_from_old_parent(self): + """Remove `Child Procedure` from `Old Parent Procedure`""" + + if old_doc := self.get_doc_before_save(): + if old_parent := old_doc.parent_quality_procedure: + if self.parent_quality_procedure != old_parent: + parent = frappe.get_doc("Quality Procedure", old_parent) + for process in parent.processes: + if process.procedure == self.name: + parent.remove(process) + parent.save() + @frappe.whitelist() def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False): From 8fbd4cea5b374ddbd611f1c0ada26d9998d82b27 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 4 Nov 2023 12:46:06 +0530 Subject: [PATCH 012/163] chore: add missing filters for `Parent Procedure` --- .../doctype/quality_procedure/quality_procedure.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js index fd2b6a4eaa..79fd2ebdbe 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js @@ -3,10 +3,10 @@ frappe.ui.form.on('Quality Procedure', { refresh: function(frm) { - frm.set_query("procedure","processes", (frm) =>{ + frm.set_query('procedure', 'processes', (frm) =>{ return { filters: { - name: ["not in", [frm.parent_quality_procedure, frm.name]] + name: ['not in', [frm.parent_quality_procedure, frm.name]] } }; }); @@ -14,7 +14,8 @@ frappe.ui.form.on('Quality Procedure', { frm.set_query('parent_quality_procedure', function(){ return { filters: { - is_group: 1 + is_group: 1, + name: ['!=', frm.doc.name] } }; }); From 0e100cd451892f3024d031ca7b45964c533cdc26 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 09:51:26 +0530 Subject: [PATCH 013/163] fix: skip check for removed validation --- .../doctype/payment_entry/test_payment_entry.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 3e8292946c..b6b93b6b11 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -683,17 +683,6 @@ class TestPaymentEntry(FrappeTestCase): self.validate_gl_entries(pe.name, expected_gle) def test_payment_against_negative_sales_invoice(self): - pe1 = frappe.new_doc("Payment Entry") - pe1.payment_type = "Pay" - pe1.company = "_Test Company" - pe1.party_type = "Customer" - pe1.party = "_Test Customer" - pe1.paid_from = "_Test Cash - _TC" - pe1.paid_amount = 100 - pe1.received_amount = 100 - - self.assertRaises(InvalidPaymentEntry, pe1.validate) - si1 = create_sales_invoice() # create full payment entry against si1 @@ -751,8 +740,6 @@ class TestPaymentEntry(FrappeTestCase): # pay more than outstanding against si1 pe3 = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Cash - _TC") - pe3.paid_amount = pe3.received_amount = 300 - self.assertRaises(InvalidPaymentEntry, pe3.validate) # pay negative outstanding against si1 pe3.paid_to = "Debtors - _TC" From 98a8288da2e2516153e30a2e028104f220df3644 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 09:53:11 +0530 Subject: [PATCH 014/163] fix: handle gle for standalone credit and debit notes --- .../doctype/payment_entry/payment_entry.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 23b58947d3..c0e3ab3ed4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1072,24 +1072,27 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name - reverse_dr_or_cr = 0 + reverse_dr_or_cr, standalone_note = 0, 0 if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: - is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") + is_return, return_against = frappe.db.get_value( + d.reference_doctype, d.reference_name, ["is_return", "return_against"] + ) payable_party_types = get_party_types_from_account_type("Payable") receivable_party_types = get_party_types_from_account_type("Receivable") - if is_return and self.party_type in receivable_party_types and self.payment_type == "Pay": + if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"): reverse_dr_or_cr = 1 - elif is_return and self.party_type in payable_party_types and self.payment_type == "Receive": + elif ( + is_return and self.party_type in payable_party_types and (self.payment_type == "Receive") + ): reverse_dr_or_cr = 1 + if is_return and not return_against and not reverse_dr_or_cr: + dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" + gle.update( { - dr_or_cr: abs(allocated_amount_in_company_currency) - if reverse_dr_or_cr - else allocated_amount_in_company_currency, - dr_or_cr + "_in_account_currency": abs(d.allocated_amount) - if reverse_dr_or_cr - else d.allocated_amount, + dr_or_cr: abs(allocated_amount_in_company_currency), + dr_or_cr + "_in_account_currency": abs(d.allocated_amount), "against_voucher_type": against_voucher_type, "against_voucher": against_voucher, "cost_center": cost_center, From 84f0d1ff1ff231f0388033d2304d977b07411253 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 10:30:49 +0530 Subject: [PATCH 015/163] chore: linting issues --- .../report/tax_withholding_details/tax_withholding_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index e842d2e8dc..06c9e44b45 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -316,7 +316,7 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts): if not tds_accounts: frappe.throw( _("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")), - title="Accounts Missing Error", + title=_("Accounts Missing Error"), ) gle = frappe.qb.DocType("GL Entry") query = ( From a9d91189b0e23ccb860e9f954e882ee0ebebbc2c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 6 Nov 2023 11:05:04 +0530 Subject: [PATCH 016/163] fix: make `Material Request Item` required if `Material Request` is set in PO --- .../doctype/purchase_order_item/purchase_order_item.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 b1da97d634..2b6ffb752f 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -470,6 +470,7 @@ "fieldname": "material_request", "fieldtype": "Link", "label": "Material Request", + "mandatory_depends_on": "eval: doc.material_request_item", "no_copy": 1, "oldfieldname": "prevdoc_docname", "oldfieldtype": "Link", @@ -485,6 +486,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Material Request Item", + "mandatory_depends_on": "eval: doc.material_request", "no_copy": 1, "oldfieldname": "prevdoc_detail_docname", "oldfieldtype": "Data", @@ -914,7 +916,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-27 15:50:42.655573", + "modified": "2023-11-06 11:00:53.596417", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", From de445b32f5a13d115ce3f49c190f23b1efdf6632 Mon Sep 17 00:00:00 2001 From: vishal Date: Mon, 6 Nov 2023 12:21:47 +0530 Subject: [PATCH 017/163] feat(accounts_receivable): test_case added for multi-select customer group --- .../test_accounts_receivable.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index cbeb6d3106..3582fe0c4a 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -475,6 +475,30 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): report = execute(filters)[1] self.assertEqual(len(report), 0) + def test_multi_customer_group_filter(self): + si = self.create_sales_invoice() + cus_group = frappe.db.get_value("Customer", self.customer, "customer_group") + # Create a list of customer groups, e.g., ["Group1", "Group2"] + cus_groups_list = [cus_group, "Group2"] + + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + "customer_group": cus_groups_list, # Use the list of customer groups + } + report = execute(filters)[1] + + # Assert that the report contains data for the specified customer groups + self.assertTrue(len(report) > 0) + + for row in report: + # Assert that the customer group of each row is in the list of customer groups + self.assertIn(row.customer_group, cus_groups_list) + def test_party_account_filter(self): si1 = self.create_sales_invoice() self.customer2 = ( From 30402033bc06296a32ffaf98fd28f18974be2248 Mon Sep 17 00:00:00 2001 From: vishal Date: Mon, 6 Nov 2023 13:02:04 +0530 Subject: [PATCH 018/163] fix: minor change added to test_case --- .../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 3582fe0c4a..f83285a1a7 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -479,7 +479,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): si = self.create_sales_invoice() cus_group = frappe.db.get_value("Customer", self.customer, "customer_group") # Create a list of customer groups, e.g., ["Group1", "Group2"] - cus_groups_list = [cus_group, "Group2"] + cus_groups_list = [cus_group, "_Test Customer Group 1"] filters = { "company": self.company, From 5cce522ecdb7f13a001d4abbf6c3682088edb1b6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 6 Nov 2023 17:13:29 +0530 Subject: [PATCH 019/163] fix: don't reset rate if greater than zero in standalone debit note --- erpnext/controllers/buying_controller.py | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 3a802bd26f..ece08d83f9 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -105,26 +105,26 @@ class BuyingController(SubcontractingController): def set_rate_for_standalone_debit_note(self): if self.get("is_return") and self.get("update_stock") and not self.return_against: for row in self.items: + if row.rate <= 0: + # override the rate with valuation rate + row.rate = get_incoming_rate( + { + "item_code": row.item_code, + "warehouse": row.warehouse, + "posting_date": self.get("posting_date"), + "posting_time": self.get("posting_time"), + "qty": row.qty, + "serial_and_batch_bundle": row.get("serial_and_batch_bundle"), + "company": self.company, + "voucher_type": self.doctype, + "voucher_no": self.name, + }, + raise_error_if_no_rate=False, + ) - # override the rate with valuation rate - row.rate = get_incoming_rate( - { - "item_code": row.item_code, - "warehouse": row.warehouse, - "posting_date": self.get("posting_date"), - "posting_time": self.get("posting_time"), - "qty": row.qty, - "serial_and_batch_bundle": row.get("serial_and_batch_bundle"), - "company": self.company, - "voucher_type": self.doctype, - "voucher_no": self.name, - }, - raise_error_if_no_rate=False, - ) - - row.discount_percentage = 0.0 - row.discount_amount = 0.0 - row.margin_rate_or_amount = 0.0 + row.discount_percentage = 0.0 + row.discount_amount = 0.0 + row.margin_rate_or_amount = 0.0 def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) From 30c6b83a103b262e7bd80601076fdb78a358c068 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 6 Nov 2023 18:55:06 +0530 Subject: [PATCH 020/163] test: add test case for Quality Procedure` --- .../test_quality_procedure.py | 143 ++++++++++++------ 1 file changed, 97 insertions(+), 46 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py index 04e8211214..467186debd 100644 --- a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py @@ -1,56 +1,107 @@ # Copyright (c) 2018, Frappe and Contributors # See license.txt -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from .quality_procedure import add_node -class TestQualityProcedure(unittest.TestCase): +class TestQualityProcedure(FrappeTestCase): def test_add_node(self): - try: - procedure = frappe.get_doc( - dict( - doctype="Quality Procedure", - quality_procedure_name="Test Procedure 1", - processes=[dict(process_description="Test Step 1")], - ) - ).insert() - - frappe.local.form_dict = frappe._dict( - doctype="Quality Procedure", - quality_procedure_name="Test Child 1", - parent_quality_procedure=procedure.name, - cmd="test", - is_root="false", - ) - node = add_node() - - procedure.reload() - - self.assertEqual(procedure.is_group, 1) - - # child row created - self.assertTrue([d for d in procedure.processes if d.procedure == node.name]) - - node.delete() - procedure.reload() - - # child unset - self.assertFalse([d for d in procedure.processes if d.name == node.name]) - - finally: - procedure.delete() - - -def create_procedure(): - return frappe.get_doc( - dict( - doctype="Quality Procedure", - quality_procedure_name="Test Procedure 1", - is_group=1, - processes=[dict(process_description="Test Step 1")], + procedure = create_procedure( + { + "quality_procedure_name": "Test Procedure 1", + "is_group": 1, + "processes": [dict(process_description="Test Step 1")], + } ) - ).insert() + + frappe.local.form_dict = frappe._dict( + doctype="Quality Procedure", + quality_procedure_name="Test Child 1", + parent_quality_procedure=procedure.name, + cmd="test", + is_root="false", + ) + node = add_node() + + procedure.reload() + + self.assertEqual(procedure.is_group, 1) + + # child row created + self.assertTrue([d for d in procedure.processes if d.procedure == node.name]) + + node.delete() + procedure.reload() + + # child unset + self.assertFalse([d for d in procedure.processes if d.name == node.name]) + + def test_remove_parent_from_old_child(self): + child_qp = create_procedure( + { + "quality_procedure_name": "Test Child 1", + "is_group": 0, + } + ) + group_qp = create_procedure( + { + "quality_procedure_name": "Test Group", + "is_group": 1, + "processes": [dict(procedure=child_qp.name)], + } + ) + + child_qp.reload() + self.assertEqual(child_qp.parent_quality_procedure, group_qp.name) + + group_qp.reload() + del group_qp.processes[0] + group_qp.save() + + child_qp.reload() + self.assertEqual(child_qp.parent_quality_procedure, None) + + def remove_child_from_old_parent(self): + child_qp = create_procedure( + { + "quality_procedure_name": "Test Child 1", + "is_group": 0, + } + ) + group_qp = create_procedure( + { + "quality_procedure_name": "Test Group", + "is_group": 1, + "processes": [dict(procedure=child_qp.name)], + } + ) + + group_qp.reload() + self.assertTrue([d for d in group_qp.processes if d.procedure == child_qp.name]) + + child_qp.reload() + self.assertEqual(child_qp.parent_quality_procedure, group_qp.name) + + child_qp.parent_quality_procedure = None + child_qp.save() + + group_qp.reload() + self.assertFalse([d for d in group_qp.processes if d.procedure == child_qp.name]) + + +def create_procedure(kwargs=None): + kwargs = frappe._dict(kwargs or {}) + + doc = frappe.new_doc("Quality Procedure") + doc.quality_procedure_name = kwargs.quality_procedure_name or "_Test Procedure" + doc.is_group = kwargs.is_group or 0 + + for process in kwargs.processes or []: + doc.append("processes", process) + + doc.insert() + + return doc From f9fc6c9c9d5faef90f345df6e04ea4d2b8b8b69b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 6 Nov 2023 18:01:05 +0530 Subject: [PATCH 021/163] fix(test): `test_gl_entries_for_standalone_debit_note` --- .../doctype/purchase_invoice/test_purchase_invoice.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 13593bcf9b..ae1f2d8570 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1783,9 +1783,14 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): set_advance_flag(company="_Test Company", flag=0, default_account="") def test_gl_entries_for_standalone_debit_note(self): - make_purchase_invoice(qty=5, rate=500, update_stock=True) + from erpnext.stock.doctype.item.test_item import make_item - returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True) + item_code = make_item(properties={"is_stock_item": 1}) + make_purchase_invoice(item_code=item_code, qty=5, rate=500, update_stock=True) + + returned_inv = make_purchase_invoice( + item_code=item_code, qty=-5, rate=5, update_stock=True, is_return=True + ) # override the rate with valuation rate sle = frappe.get_all( @@ -1795,7 +1800,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): )[0] rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) - self.assertAlmostEqual(returned_inv.items[0].rate, rate) + self.assertAlmostEqual(rate, 500) def test_payment_allocation_for_payment_terms(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import ( From 8722318081e30544b4cc76c97bdd1fe3362373d6 Mon Sep 17 00:00:00 2001 From: hyaray Date: Mon, 6 Nov 2023 22:19:45 +0800 Subject: [PATCH 022/163] fix: add translation wrapper --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 58945bba77..d9cc212e8c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -710,7 +710,7 @@ erpnext.work_order = { return new Promise((resolve, reject) => { frappe.prompt({ fieldtype: 'Float', - label: __('Qty for {0}', [purpose]), + label: __('Qty for {0}', [__(purpose)]), fieldname: 'qty', description: __('Max: {0}', [max]), default: max From 67e74d03edb5bd3190a44df811a30b2b130eeb39 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 6 Nov 2023 20:23:26 +0530 Subject: [PATCH 023/163] fix: typo in AR report --- .../report/accounts_receivable/accounts_receivable.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index f24a24e42e..12e40037f1 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -117,7 +117,7 @@ class ReceivablePayableReport(object): for ple in self.ple_entries: # get the balance object for voucher_type - if self.filters.get("ingore_accounts"): + if self.filters.get("ignore_accounts"): key = (ple.voucher_type, ple.voucher_no, ple.party) else: key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) @@ -188,7 +188,7 @@ class ReceivablePayableReport(object): ): return - if self.filters.get("ingore_accounts"): + if self.filters.get("ignore_accounts"): key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) else: key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party) @@ -200,7 +200,7 @@ class ReceivablePayableReport(object): if ple.against_voucher_no in self.return_entries: return_against = self.return_entries.get(ple.against_voucher_no) if return_against: - if self.filters.get("ingore_accounts"): + if self.filters.get("ignore_accounts"): key = (ple.against_voucher_type, return_against, ple.party) else: key = (ple.account, ple.against_voucher_type, return_against, ple.party) @@ -209,7 +209,7 @@ class ReceivablePayableReport(object): if not row: # no invoice, this is an invoice / stand-alone payment / credit note - if self.filters.get("ingore_accounts"): + if self.filters.get("ignore_accounts"): row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party)) else: row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party)) From d582a7379580367e7d81bed58ed02773bd15d00c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 10:02:08 +0530 Subject: [PATCH 024/163] feat: settings page for repost --- .../__init__.py | 0 .../repost_accounting_ledger_settings.js | 8 ++++ .../repost_accounting_ledger_settings.json | 41 +++++++++++++++++ .../repost_accounting_ledger_settings.py | 9 ++++ .../test_repost_accounting_ledger_settings.py | 9 ++++ .../doctype/repost_allowed_types/__init__.py | 0 .../repost_allowed_types.json | 45 +++++++++++++++++++ .../repost_allowed_types.py | 9 ++++ 8 files changed, 121 insertions(+) create mode 100644 erpnext/accounts/doctype/repost_accounting_ledger_settings/__init__.py create mode 100644 erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.js create mode 100644 erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json create mode 100644 erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py create mode 100644 erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py create mode 100644 erpnext/accounts/doctype/repost_allowed_types/__init__.py create mode 100644 erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json create mode 100644 erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/__init__.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.js b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.js new file mode 100644 index 0000000000..8c83ca5043 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Repost Accounting Ledger Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json new file mode 100644 index 0000000000..8aaa6e8f0a --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-11-07 09:57:20.619939", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "allowed_types" + ], + "fields": [ + { + "fieldname": "allowed_types", + "fieldtype": "Table", + "label": "Allowed Doctypes", + "options": "Repost Allowed Types" + } + ], + "in_create": 1, + "issingle": 1, + "links": [], + "modified": "2023-11-07 10:12:07.519155", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Repost Accounting Ledger Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py new file mode 100644 index 0000000000..2b8230df86 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class RepostAccountingLedgerSettings(Document): + pass diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py new file mode 100644 index 0000000000..ec4e87ffc0 --- /dev/null +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestRepostAccountingLedgerSettings(FrappeTestCase): + pass diff --git a/erpnext/accounts/doctype/repost_allowed_types/__init__.py b/erpnext/accounts/doctype/repost_allowed_types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json new file mode 100644 index 0000000000..ede12fbc18 --- /dev/null +++ b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-11-07 09:58:03.595382", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "column_break_sfzb", + "allowed" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Doctype", + "options": "DocType" + }, + { + "default": "0", + "fieldname": "allowed", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allowed" + }, + { + "fieldname": "column_break_sfzb", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-11-07 10:01:39.217861", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Repost Allowed Types", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py new file mode 100644 index 0000000000..0e4883b0c9 --- /dev/null +++ b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class RepostAllowedTypes(Document): + pass From ebb186c8df6cd713800475d490962e75ea6ab6a6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 10:12:18 +0530 Subject: [PATCH 025/163] chore: patch to update default repost settings value --- erpnext/patches.txt | 1 + .../patches/v14_0/add_default_for_repost_settings.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 erpnext/patches/v14_0/add_default_for_repost_settings.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e0f32c55da..d394db6d96 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -345,6 +345,7 @@ erpnext.patches.v14_0.rename_over_order_allowance_field erpnext.patches.v14_0.migrate_delivery_stop_lock_field execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50) execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50) +erpnext.patches.v14_0.add_default_for_repost_settings erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based erpnext.patches.v15_0.set_reserved_stock_in_bin diff --git a/erpnext/patches/v14_0/add_default_for_repost_settings.py b/erpnext/patches/v14_0/add_default_for_repost_settings.py new file mode 100644 index 0000000000..6cafc66aab --- /dev/null +++ b/erpnext/patches/v14_0/add_default_for_repost_settings.py @@ -0,0 +1,12 @@ +import frappe + + +def execute(): + """ + Update Repost Accounting Ledger Settings with default values + """ + allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] + repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") + for x in allowed_types: + repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) + repost_settings.save() From 5a068410c6ff4bc4ac100098c712d09bb70854df Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 11:03:03 +0530 Subject: [PATCH 026/163] refactor: configurable repost settings --- .../repost_accounting_ledger.js | 4 +--- .../repost_accounting_ledger.py | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js index 3a87a380d1..c7b7a148cf 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js @@ -5,9 +5,7 @@ frappe.ui.form.on("Repost Accounting Ledger", { setup: function(frm) { frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) { return { - filters: { - name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']], - } + query: "erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.get_repost_allowed_types" } } diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index dbb0971fde..1d17a1c20e 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -10,9 +10,12 @@ from frappe.utils.data import comma_and class RepostAccountingLedger(Document): def __init__(self, *args, **kwargs): super(RepostAccountingLedger, self).__init__(*args, **kwargs) - self._allowed_types = set( - ["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"] - ) + self._allowed_types = [ + x.document_type + for x in frappe.db.get_all( + "Repost Allowed Types", filters={"allowed": True}, fields=["document_type"] + ) + ] def validate(self): self.validate_vouchers() @@ -186,3 +189,18 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])) ) ) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters): + filters = {"allowed": True} + + if txt: + filters.update({"document_type": ("like", f"%{txt}%")}) + + if allowed_types := frappe.db.get_all( + "Repost Allowed Types", filters=filters, fields=["document_type"], as_list=1 + ): + return allowed_types + return [] From b651b36fff496a92713f634868a63a4ebe24f8c9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 11:10:42 +0530 Subject: [PATCH 027/163] refactor: support for expense claim repost --- .../repost_accounting_ledger/repost_accounting_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 1d17a1c20e..8aaafd0b16 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -160,7 +160,7 @@ def start_repost(account_repost_doc=str) -> None: doc.docstatus = 1 doc.make_gl_entries() - elif doc.doctype in ["Payment Entry", "Journal Entry"]: + elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]: if not repost_doc.delete_cancelled_entries: doc.make_gl_entries(1) doc.make_gl_entries() From adff2871606c8f0ed51971cd65db355d38032fb9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 12:12:27 +0530 Subject: [PATCH 028/163] fix: type error on new payment entry --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 0203c45058..b3ae627c6e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -154,7 +154,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_dynamic_labels(frm); frm.events.show_general_ledger(frm); erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm); - if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) { + if((frm.doc.references) && (frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0}))) { frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() { frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name}); }, __('Actions')); From ac79b8483f0769ab7fd3dc4975b7be34e15b32b2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 12:46:06 +0530 Subject: [PATCH 029/163] refactor(test): update repost settings for test cases --- .../test_repost_accounting_ledger.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 0e75dd2e3e..dda0ec778f 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -20,10 +20,18 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.create_company() self.create_customer() self.create_item() + self.update_repost_settings() def teadDown(self): frappe.db.rollback() + def update_repost_settings(self): + allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] + repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") + for x in allowed_types: + repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) + repost_settings.save() + def test_01_basic_functions(self): si = create_sales_invoice( item=self.item, From 61705047b037f91a735207d18635de6263af804d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 13:42:09 +0530 Subject: [PATCH 030/163] refactor: select distinct types --- .../repost_accounting_ledger/repost_accounting_ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 8aaafd0b16..69cfe9fcd7 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -13,7 +13,7 @@ class RepostAccountingLedger(Document): self._allowed_types = [ x.document_type for x in frappe.db.get_all( - "Repost Allowed Types", filters={"allowed": True}, fields=["document_type"] + "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"] ) ] @@ -200,7 +200,7 @@ def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters filters.update({"document_type": ("like", f"%{txt}%")}) if allowed_types := frappe.db.get_all( - "Repost Allowed Types", filters=filters, fields=["document_type"], as_list=1 + "Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1 ): return allowed_types return [] From 11c8d9fcf1e7eef1890b719b37c823822b6a108d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 14:18:07 +0530 Subject: [PATCH 031/163] refactor(test): repost test case for purchase invoice --- .../doctype/purchase_invoice/test_purchase_invoice.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 13593bcf9b..d2adc514b0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1898,6 +1898,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): disable_dimension() def test_repost_accounting_entries(self): + # update repost settings + settings = frappe.get_doc("Repost Accounting Ledger Settings") + if not [x for x in settings.allowed_types if x.document_type == "Purchase Invoice"]: + settings.append("allowed_types", {"document_type": "Purchase Invoice", "allowed": True}) + settings.save() + pi = make_purchase_invoice( rate=1000, price_list_rate=1000, From 10b9570429e16906f3f879228c065b668f63882b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 14:30:06 +0530 Subject: [PATCH 032/163] refactor: update permissions for repost settings --- .../repost_accounting_ledger_settings.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index 8aaa6e8f0a..8aa0a840c7 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "creation": "2023-11-07 09:57:20.619939", "doctype": "DocType", "engine": "InnoDB", @@ -18,7 +17,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2023-11-07 10:12:07.519155", + "modified": "2023-11-07 14:24:13.321522", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", @@ -30,12 +29,18 @@ "email": 1, "print": 1, "read": 1, - "role": "System Manager", + "role": "Administrator", "share": 1, "write": 1 + }, + { + "read": 1, + "role": "System Manager", + "select": 1 } ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file From ee60fa940cdcd6c2bd6c47d875e055275535d216 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 7 Nov 2023 17:33:29 +0530 Subject: [PATCH 033/163] chore: typo in `Stock Entry` enqueue msg --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c41349fcfb..b06de2e2fc 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -161,7 +161,7 @@ class StockEntry(StockController): if self.is_enqueue_action(): frappe.msgprint( _( - "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage" + "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Draft stage" ) ) self.queue_action("submit", timeout=2000) @@ -172,7 +172,7 @@ class StockEntry(StockController): if self.is_enqueue_action(): frappe.msgprint( _( - "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage" + "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Submitted stage" ) ) self.queue_action("cancel", timeout=2000) From 416bd400bb94c1fe115f6929e9252c4ce9f3cd40 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 7 Nov 2023 17:38:07 +0530 Subject: [PATCH 034/163] refactor: optimize for speed --- erpnext/utilities/bulk_transaction.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 5e57b31793..fcee265644 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -192,9 +192,7 @@ def mark_retrired_transaction(log_doc, doc_name): record = 0 for d in log_doc.get("logger_data"): if d.transaction_name == doc_name and d.transaction_status == "Failed": - d.retried = 1 + frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1) record = record + 1 - log_doc.save() - return record From 162c0497d1db69e3af5f142459e3a875f03962af Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 7 Nov 2023 14:44:04 +0100 Subject: [PATCH 035/163] test: `get_outstanding_reference_documents` (triggered via UI) --- .../payment_entry/test_payment_entry.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index edfec41918..b3511f0f52 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -6,10 +6,11 @@ import unittest import frappe from frappe import qb from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import flt, nowdate +from frappe.utils import add_days, flt, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import ( InvalidPaymentEntry, + get_outstanding_reference_documents, get_payment_entry, get_reference_details, ) @@ -1262,6 +1263,42 @@ class TestPaymentEntry(FrappeTestCase): so.reload() self.assertEqual(so.advance_paid, so.rounded_total) + def test_outstanding_invoices_api(self): + """ + Test if `get_outstanding_reference_documents` fetches invoices in the right order. + """ + customer = create_customer("Max Mustermann", "INR") + create_payment_terms_template() + + si = create_sales_invoice(qty=1, rate=100, customer=customer) + si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer) + si2.payment_terms_template = "Test Receivable Template" + si2.submit() + + args = { + "posting_date": nowdate(), + "company": "_Test Company", + "party_type": "Customer", + "payment_type": "Pay", + "party": customer, + "party_account": "Debtors - _TC", + } + args.update( + { + "get_outstanding_invoices": True, + "from_posting_date": nowdate(), + "to_posting_date": add_days(nowdate(), 7), + } + ) + references = get_outstanding_reference_documents(args) + + self.assertEqual(len(references), 3) + self.assertEqual(references[0].voucher_no, si.name) + self.assertEqual(references[1].voucher_no, si2.name) + self.assertEqual(references[2].voucher_no, si2.name) + self.assertEqual(references[1].payment_term, "Basic Amount Receivable") + self.assertEqual(references[2].payment_term, "Tax Receivable") + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") @@ -1322,6 +1359,9 @@ def create_payment_terms_template(): def create_payment_terms_template_with_discount( name=None, discount_type=None, discount=None, template_name=None ): + """ + Create a Payment Terms Template with % or amount discount. + """ create_payment_term(name or "30 Credit Days with 10% Discount") template_name = template_name or "Test Discount Template" From 2984a86f37e72bbdefd0348d7c456bd6ada540bb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 12:30:24 +0530 Subject: [PATCH 036/163] fix: use get_all instead of get_list --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0a116a7345..aef0c38c63 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2044,4 +2044,4 @@ def create_gain_loss_journal( def get_party_types_from_account_type(account_type): - return frappe.db.get_list("Party Type", {"account_type": account_type}, pluck="name") + return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name") From 33eedb97dcb531cc8e14813d742266f2c5168fac Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 8 Nov 2023 12:38:09 +0530 Subject: [PATCH 037/163] fix: ignore Stock Reservation for future dated PR --- .../doctype/purchase_receipt/purchase_receipt.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index cbc1693eaa..d905fe5ea6 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -7,7 +7,7 @@ from frappe import _, throw from frappe.desk.notifications import clear_doctype_notifications from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import CombineDatetime -from frappe.utils import cint, flt, getdate, nowdate +from frappe.utils import cint, flt, get_datetime, getdate, nowdate from pypika import functions as fn import erpnext @@ -760,8 +760,12 @@ class PurchaseReceipt(BuyingController): update_billing_percentage(pr_doc, update_modified=update_modified) def reserve_stock_for_sales_order(self): - if self.is_return or not cint( - frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order_on_purchase") + if ( + self.is_return + or not frappe.db.get_single_value("Stock Settings", "enable_stock_reservation") + or not frappe.db.get_single_value( + "Stock Settings", "auto_reserve_stock_for_sales_order_on_purchase" + ) ): return @@ -782,6 +786,11 @@ class PurchaseReceipt(BuyingController): so_items_details_map.setdefault(item.sales_order, []).append(item_details) if so_items_details_map: + if get_datetime("{} {}".format(self.posting_date, self.posting_time)) > get_datetime(): + return frappe.msgprint( + _("Cannot create Stock Reservation Entries for future dated Purchase Receipts.") + ) + for so, items_details in so_items_details_map.items(): so_doc = frappe.get_doc("Sales Order", so) so_doc.create_stock_reservation_entries( From 4783e4beee88674aa96eca52321e4b8677a24c17 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Wed, 8 Nov 2023 10:42:47 +0100 Subject: [PATCH 038/163] Update de.csv (#37981) added translation for DocType Competitor https://www.duden.de/rechtschreibung/Konkurrent --- erpnext/translations/de.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index c627f81984..c856fefcc0 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -533,6 +533,7 @@ Company currencies of both the companies should match for Inter Company Transact Company is manadatory for company account,Bitte gib ein Unternehmen für dieses Unternehmenskonto an., Company name not same,Firma nicht gleich, Company {0} does not exist,Unternehmen {0} existiert nicht, +Competitor,Konkurrent, Compensatory leave request days not in valid holidays,"Tage des Ausgleichsurlaubs, die nicht in den gültigen Feiertagen sind", Complaint,Beschwerde, Completion Date,Fertigstellungstermin, From 70d99eebc065c9454674101a112e6824e5c057f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 8 Nov 2023 18:38:39 +0530 Subject: [PATCH 039/163] Revert "fix: set empty value for tax template in item details (#37496)" This reverts commit b0d440c34b9cb4d0e0d75153c279ccaa6206253d. --- .../doctype/pos_invoice/test_pos_invoice.py | 115 +++++++++--------- .../sales_invoice/test_sales_invoice.py | 112 +++++++++-------- erpnext/stock/doctype/item/test_item.py | 10 +- erpnext/stock/get_item_details.py | 1 - 4 files changed, 120 insertions(+), 118 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 982bdc198a..200b82a447 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -6,7 +6,6 @@ import unittest import frappe from frappe import _ -from frappe.utils import add_days, nowdate from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile @@ -126,64 +125,70 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(inv.grand_total, 5474.0) def test_tax_calculation_with_item_tax_template(self): - import json + inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1) + item_row = inv.get("items")[0] - from erpnext.stock.get_item_details import get_item_details - - # set tax template in item - item = frappe.get_cached_doc("Item", "_Test Item") - item.set( - "taxes", - [ - { - "item_tax_template": "_Test Account Excise Duty @ 15 - _TC", - "valid_from": add_days(nowdate(), -5), - } - ], - ) - item.save() - - # create POS invoice with item - pos_inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True) - item_details = get_item_details( - doc=pos_inv, - args={ - "item_code": item.item_code, - "company": pos_inv.company, - "doctype": "POS Invoice", - "conversion_rate": 1.0, - }, - ) - tax_map = json.loads(item_details.item_tax_rate) - for tax in tax_map: - pos_inv.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": tax, - "rate": tax_map[tax], - "description": "Test", - "cost_center": "_Test Cost Center - _TC", - }, - ) - pos_inv.submit() - pos_inv.load_from_db() - - # check if correct tax values are applied from tax template - self.assertEqual(pos_inv.net_total, 386.4) - - expected_taxes = [ - { - "tax_amount": 57.96, - "total": 444.36, - }, + add_items = [ + (54, "_Test Account Excise Duty @ 12 - _TC"), + (288, "_Test Account Excise Duty @ 15 - _TC"), + (144, "_Test Account Excise Duty @ 20 - _TC"), + (430, "_Test Item Tax Template 1 - _TC"), ] + for qty, item_tax_template in add_items: + item_row_copy = copy.deepcopy(item_row) + item_row_copy.qty = qty + item_row_copy.item_tax_template = item_tax_template + inv.append("items", item_row_copy) - for i in range(len(expected_taxes)): - for key in expected_taxes[i]: - self.assertEqual(expected_taxes[i][key], pos_inv.get("taxes")[i].get(key)) + inv.append( + "taxes", + { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "rate": 11, + }, + ) + inv.append( + "taxes", + { + "account_head": "_Test Account Education Cess - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "rate": 0, + }, + ) + inv.append( + "taxes", + { + "account_head": "_Test Account S&H Education Cess - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "doctype": "Sales Taxes and Charges", + "rate": 3, + }, + ) + inv.insert() - self.assertEqual(pos_inv.get("base_total_taxes_and_charges"), 57.96) + self.assertEqual(inv.net_total, 4600) + + self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41) + self.assertEqual(inv.get("taxes")[0].total, 5102.41) + + self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80) + self.assertEqual(inv.get("taxes")[1].total, 5300.21) + + self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36) + self.assertEqual(inv.get("taxes")[2].total, 5675.57) + + self.assertEqual(inv.grand_total, 5675.57) + self.assertEqual(inv.rounding_adjustment, 0.43) + self.assertEqual(inv.rounded_total, 5676.0) def test_tax_calculation_with_multiple_items_and_discount(self): inv = create_pos_invoice(qty=1, rate=75, do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 21cc253959..5a41336b2f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -516,72 +516,70 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.grand_total, 5474.0) def test_tax_calculation_with_item_tax_template(self): - import json - - from erpnext.stock.get_item_details import get_item_details - - # set tax template in item - item = frappe.get_cached_doc("Item", "_Test Item") - item.set( - "taxes", - [ - { - "item_tax_template": "_Test Item Tax Template 1 - _TC", - "valid_from": add_days(nowdate(), -5), - } - ], - ) - item.save() - - # create sales invoice with item si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) - item_details = get_item_details( - doc=si, - args={ - "item_code": item.item_code, - "company": si.company, - "doctype": "Sales Invoice", - "conversion_rate": 1.0, + item_row = si.get("items")[0] + + add_items = [ + (54, "_Test Account Excise Duty @ 12 - _TC"), + (288, "_Test Account Excise Duty @ 15 - _TC"), + (144, "_Test Account Excise Duty @ 20 - _TC"), + (430, "_Test Item Tax Template 1 - _TC"), + ] + for qty, item_tax_template in add_items: + item_row_copy = copy.deepcopy(item_row) + item_row_copy.qty = qty + item_row_copy.item_tax_template = item_tax_template + si.append("items", item_row_copy) + + si.append( + "taxes", + { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "rate": 11, }, ) - tax_map = json.loads(item_details.item_tax_rate) - for tax in tax_map: - si.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": tax, - "rate": tax_map[tax], - "description": "Test", - "cost_center": "_Test Cost Center - _TC", - }, - ) - si.submit() - si.load_from_db() - - # check if correct tax values are applied from tax template - self.assertEqual(si.net_total, 386.4) - - expected_taxes = [ + si.append( + "taxes", { - "tax_amount": 19.32, - "total": 405.72, + "account_head": "_Test Account Education Cess - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Education Cess", + "doctype": "Sales Taxes and Charges", + "rate": 0, }, + ) + si.append( + "taxes", { - "tax_amount": 38.64, - "total": 444.36, + "account_head": "_Test Account S&H Education Cess - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "S&H Education Cess", + "doctype": "Sales Taxes and Charges", + "rate": 3, }, - { - "tax_amount": 57.96, - "total": 502.32, - }, - ] + ) + si.insert() - for i in range(len(expected_taxes)): - for key in expected_taxes[i]: - self.assertEqual(expected_taxes[i][key], si.get("taxes")[i].get(key)) + self.assertEqual(si.net_total, 4600) - self.assertEqual(si.get("base_total_taxes_and_charges"), 115.92) + self.assertEqual(si.get("taxes")[0].tax_amount, 502.41) + self.assertEqual(si.get("taxes")[0].total, 5102.41) + + self.assertEqual(si.get("taxes")[1].tax_amount, 197.80) + self.assertEqual(si.get("taxes")[1].total, 5300.21) + + self.assertEqual(si.get("taxes")[2].tax_amount, 375.36) + self.assertEqual(si.get("taxes")[2].total, 5675.57) + + self.assertEqual(si.grand_total, 5675.57) + self.assertEqual(si.rounding_adjustment, 0.43) + self.assertEqual(si.rounded_total, 5676.0) def test_tax_calculation_with_multiple_items_and_discount(self): si = create_sales_invoice(qty=1, rate=75, do_not_save=True) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 09d3dd1dad..a942f58bd6 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -163,7 +163,7 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item With Item Tax Template", "tax_category": "_Test Tax Category 2", - "item_tax_template": "", + "item_tax_template": None, }, { "item_code": "_Test Item Inherit Group Item Tax Template 1", @@ -178,7 +178,7 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item Inherit Group Item Tax Template 1", "tax_category": "_Test Tax Category 2", - "item_tax_template": "", + "item_tax_template": None, }, { "item_code": "_Test Item Inherit Group Item Tax Template 2", @@ -193,7 +193,7 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item Inherit Group Item Tax Template 2", "tax_category": "_Test Tax Category 2", - "item_tax_template": "", + "item_tax_template": None, }, { "item_code": "_Test Item Override Group Item Tax Template", @@ -208,12 +208,12 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item Override Group Item Tax Template", "tax_category": "_Test Tax Category 2", - "item_tax_template": "", + "item_tax_template": None, }, ] expected_item_tax_map = { - "": {}, + None: {}, "_Test Account Excise Duty @ 10 - _TC": {"_Test Account Excise Duty - _TC": 10}, "_Test Account Excise Duty @ 12 - _TC": {"_Test Account Excise Duty - _TC": 12}, "_Test Account Excise Duty @ 15 - _TC": {"_Test Account Excise Duty - _TC": 15}, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e29fc882ce..c766cab0a1 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -610,7 +610,6 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): # all templates have validity and no template is valid if not taxes_with_validity and (not taxes_with_no_validity): - out["item_tax_template"] = "" return None # do not change if already a valid template From 9a171db97f67f88fad1589016afb4e808571d04c Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 8 Nov 2023 22:02:09 +0530 Subject: [PATCH 040/163] fix: asset depreciation ledger (#37991) * fix: include opening acc depr while calculating asset depr ledger report * chore: include opening acc depr properly in acc depr amt * chore: add cost_center in asset depr ledger report * fix: handle finance books properly in asset depr ledger report * chore: rename 'include default book entries' to 'include default FB entries' --- .../asset_depreciation_ledger.js | 22 ++++++-- .../asset_depreciation_ledger.json | 6 +-- .../asset_depreciation_ledger.py | 51 ++++++++++++++----- .../report/balance_sheet/balance_sheet.js | 2 +- .../accounts/report/cash_flow/cash_flow.js | 2 +- .../consolidated_financial_statement.js | 2 +- .../accounts/report/financial_statements.py | 4 +- .../report/general_ledger/general_ledger.js | 2 +- .../report/general_ledger/general_ledger.py | 4 +- .../report/trial_balance/trial_balance.js | 2 +- .../report/trial_balance/trial_balance.py | 4 +- .../fixed_asset_register.js | 2 +- .../fixed_asset_register.py | 2 +- erpnext/translations/af.csv | 2 +- erpnext/translations/am.csv | 2 +- erpnext/translations/ar.csv | 2 +- erpnext/translations/bg.csv | 2 +- erpnext/translations/bn.csv | 2 +- erpnext/translations/bs.csv | 2 +- erpnext/translations/ca.csv | 2 +- erpnext/translations/cs.csv | 2 +- erpnext/translations/da.csv | 2 +- erpnext/translations/de.csv | 2 +- erpnext/translations/el.csv | 2 +- erpnext/translations/es.csv | 2 +- erpnext/translations/et.csv | 2 +- erpnext/translations/fa.csv | 2 +- erpnext/translations/fi.csv | 2 +- erpnext/translations/fr.csv | 2 +- erpnext/translations/gu.csv | 2 +- erpnext/translations/he.csv | 2 +- erpnext/translations/hi.csv | 2 +- erpnext/translations/hr.csv | 2 +- erpnext/translations/hu.csv | 2 +- erpnext/translations/id.csv | 2 +- erpnext/translations/is.csv | 2 +- erpnext/translations/it.csv | 2 +- erpnext/translations/ja.csv | 2 +- erpnext/translations/km.csv | 2 +- erpnext/translations/kn.csv | 2 +- erpnext/translations/ko.csv | 2 +- erpnext/translations/ku.csv | 2 +- erpnext/translations/lo.csv | 2 +- erpnext/translations/lt.csv | 2 +- erpnext/translations/lv.csv | 2 +- erpnext/translations/mk.csv | 2 +- erpnext/translations/ml.csv | 2 +- erpnext/translations/mr.csv | 2 +- erpnext/translations/ms.csv | 2 +- erpnext/translations/my.csv | 2 +- erpnext/translations/nl.csv | 2 +- erpnext/translations/no.csv | 2 +- erpnext/translations/pl.csv | 2 +- erpnext/translations/ps.csv | 2 +- erpnext/translations/pt-BR.csv | 2 +- erpnext/translations/pt.csv | 2 +- erpnext/translations/ro.csv | 2 +- erpnext/translations/ru.csv | 2 +- erpnext/translations/rw.csv | 2 +- erpnext/translations/si.csv | 2 +- erpnext/translations/sk.csv | 2 +- erpnext/translations/sl.csv | 2 +- erpnext/translations/sq.csv | 2 +- erpnext/translations/sr.csv | 2 +- erpnext/translations/sv.csv | 2 +- erpnext/translations/sw.csv | 2 +- erpnext/translations/ta.csv | 2 +- erpnext/translations/te.csv | 2 +- erpnext/translations/th.csv | 2 +- erpnext/translations/tr.csv | 2 +- erpnext/translations/uk.csv | 2 +- erpnext/translations/ur.csv | 2 +- erpnext/translations/uz.csv | 2 +- erpnext/translations/vi.csv | 2 +- erpnext/translations/zh.csv | 2 +- erpnext/translations/zh_tw.csv | 2 +- 76 files changed, 131 insertions(+), 100 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js index 126cd03795..12b94347e0 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.js @@ -31,6 +31,18 @@ frappe.query_reports["Asset Depreciation Ledger"] = { "fieldtype": "Link", "options": "Asset" }, + { + "fieldname":"asset_category", + "label": __("Asset Category"), + "fieldtype": "Link", + "options": "Asset Category" + }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center" + }, { "fieldname":"finance_book", "label": __("Finance Book"), @@ -38,10 +50,10 @@ frappe.query_reports["Asset Depreciation Ledger"] = { "options": "Finance Book" }, { - "fieldname":"asset_category", - "label": __("Asset Category"), - "fieldtype": "Link", - "options": "Asset Category" - } + "fieldname": "include_default_book_assets", + "label": __("Include Default FB Assets"), + "fieldtype": "Check", + "default": 1 + }, ] } diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json index 0ef9d858dd..9002e23ed3 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json @@ -1,15 +1,15 @@ { - "add_total_row": 1, + "add_total_row": 0, "columns": [], "creation": "2016-04-08 14:49:58.133098", "disabled": 0, "docstatus": 0, "doctype": "Report", "filters": [], - "idx": 2, + "idx": 6, "is_standard": "Yes", "letterhead": null, - "modified": "2023-07-26 21:05:33.554778", + "modified": "2023-11-08 20:17:05.774211", "modified_by": "Administrator", "module": "Accounts", "name": "Asset Depreciation Ledger", diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py index f21c94b494..d285f28d8e 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.utils import flt +from frappe.utils import cstr, flt def execute(filters=None): @@ -32,7 +32,6 @@ def get_data(filters): filters_data.append(["against_voucher", "=", filters.get("asset")]) if filters.get("asset_category"): - assets = frappe.db.sql_list( """select name from tabAsset where asset_category = %s and docstatus=1""", @@ -41,12 +40,27 @@ def get_data(filters): filters_data.append(["against_voucher", "in", assets]) - if filters.get("finance_book"): - filters_data.append(["finance_book", "in", ["", filters.get("finance_book")]]) + company_fb = frappe.get_cached_value("Company", filters.get("company"), "default_finance_book") + + if filters.get("include_default_book_assets") and company_fb: + if filters.get("finance_book") and cstr(filters.get("finance_book")) != cstr(company_fb): + frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Assets'")) + else: + finance_book = company_fb + elif filters.get("finance_book"): + finance_book = filters.get("finance_book") + else: + finance_book = None + + if finance_book: + or_filters_data = [["finance_book", "in", ["", finance_book]], ["finance_book", "is", "not set"]] + else: + or_filters_data = [["finance_book", "in", [""]], ["finance_book", "is", "not set"]] gl_entries = frappe.get_all( "GL Entry", filters=filters_data, + or_filters=or_filters_data, fields=["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"], order_by="against_voucher, posting_date", ) @@ -61,7 +75,9 @@ def get_data(filters): asset_data = assets_details.get(d.against_voucher) if asset_data: if not asset_data.get("accumulated_depreciation_amount"): - asset_data.accumulated_depreciation_amount = d.debit + asset_data.accumulated_depreciation_amount = d.debit + asset_data.get( + "opening_accumulated_depreciation" + ) else: asset_data.accumulated_depreciation_amount += d.debit @@ -70,7 +86,7 @@ def get_data(filters): { "depreciation_amount": d.debit, "depreciation_date": d.posting_date, - "amount_after_depreciation": ( + "value_after_depreciation": ( flt(row.gross_purchase_amount) - flt(row.accumulated_depreciation_amount) ), "depreciation_entry": d.voucher_no, @@ -88,10 +104,12 @@ def get_assets_details(assets): fields = [ "name as asset", "gross_purchase_amount", + "opening_accumulated_depreciation", "asset_category", "status", "depreciation_method", "purchase_date", + "cost_center", ] for d in frappe.get_all("Asset", fields=fields, filters={"name": ("in", assets)}): @@ -121,6 +139,12 @@ def get_columns(): "fieldtype": "Currency", "width": 120, }, + { + "label": _("Opening Accumulated Depreciation"), + "fieldname": "opening_accumulated_depreciation", + "fieldtype": "Currency", + "width": 140, + }, { "label": _("Depreciation Amount"), "fieldname": "depreciation_amount", @@ -134,8 +158,8 @@ def get_columns(): "width": 210, }, { - "label": _("Amount After Depreciation"), - "fieldname": "amount_after_depreciation", + "label": _("Value After Depreciation"), + "fieldname": "value_after_depreciation", "fieldtype": "Currency", "width": 180, }, @@ -153,12 +177,13 @@ def get_columns(): "options": "Asset Category", "width": 120, }, - {"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120}, { - "label": _("Depreciation Method"), - "fieldname": "depreciation_method", - "fieldtype": "Data", - "width": 130, + "label": _("Cost Center"), + "fieldtype": "Link", + "fieldname": "cost_center", + "options": "Cost Center", + "width": 100, }, + {"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120}, {"label": _("Purchase Date"), "fieldname": "purchase_date", "fieldtype": "Date", "width": 120}, ] diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index c2b57f768f..b05e744ae0 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -17,7 +17,7 @@ frappe.query_reports["Balance Sheet"]["filters"].push({ frappe.query_reports["Balance Sheet"]["filters"].push({ fieldname: "include_default_book_entries", - label: __("Include Default Book Entries"), + label: __("Include Default FB Entries"), fieldtype: "Check", default: 1, }); diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js index 6b8ed27e64..ef17eb1503 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.js +++ b/erpnext/accounts/report/cash_flow/cash_flow.js @@ -17,7 +17,7 @@ frappe.query_reports["Cash Flow"]["filters"].splice(8, 1); frappe.query_reports["Cash Flow"]["filters"].push( { "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), + "label": __("Include Default FB Entries"), "fieldtype": "Check", "default": 1 } diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 590408c6f8..0e0c42dad9 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -104,7 +104,7 @@ frappe.query_reports["Consolidated Financial Statement"] = { }, { "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), + "label": __("Include Default FB Entries"), "fieldtype": "Check", "default": 1 }, diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 693725d8f5..096bb10706 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -561,9 +561,7 @@ def apply_additional_conditions(doctype, query, from_date, ignore_closing_entrie company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb): - frappe.throw( - _("To use a different finance book, please uncheck 'Include Default Book Entries'") - ) + frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'")) query = query.where( (gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index c0b4f59579..4cb443cf92 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -175,7 +175,7 @@ frappe.query_reports["General Ledger"] = { }, { "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), + "label": __("Include Default FB Entries"), "fieldtype": "Check", "default": 1 }, diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 5e484cf558..94cd293615 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -259,9 +259,7 @@ def get_conditions(filters): if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr( filters.get("company_fb") ): - frappe.throw( - _("To use a different finance book, please uncheck 'Include Default Book Entries'") - ) + frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'")) else: conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)") else: diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index edd40b68ef..2c4c762073 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -95,7 +95,7 @@ frappe.query_reports["Trial Balance"] = { }, { "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), + "label": __("Include Default FB Entries"), "fieldtype": "Check", "default": 1 }, diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 2a8aa0c202..8b7f0bbc00 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -275,9 +275,7 @@ def get_opening_balance( company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb): - frappe.throw( - _("To use a different finance book, please uncheck 'Include Default Book Entries'") - ) + frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'")) opening_balance = opening_balance.where( (closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 48d33314ec..812b7f78e1 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -52,7 +52,7 @@ frappe.query_reports["Fixed Asset Register"] = { }, { "fieldname": "include_default_book_assets", - "label": __("Include Default Book Assets"), + "label": __("Include Default FB Assets"), "fieldtype": "Check", "default": 1 }, diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 383be97347..45811a9344 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -223,7 +223,7 @@ def get_assets_linked_to_fb(filters): company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb): - frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'")) + frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Assets'")) query = query.where( (afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index f45731468d..ee77d98948 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -1153,7 +1153,7 @@ In Value,In Waarde, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",In die geval van 'n multi-vlak program sal kliënte outomaties toegewys word aan die betrokke vlak volgens hul besteding, Inactive,onaktiewe, Incentives,aansporings, -Include Default Book Entries,Sluit standaardboekinskrywings in, +Include Default FB Entries,Sluit standaardboekinskrywings in, Include Exploded Items,Sluit ontplofte items in, Include POS Transactions,Sluit POS-transaksies in, Include UOM,Sluit UOM in, diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv index 0453d5d685..a5f09a7488 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -1153,7 +1153,7 @@ In Value,እሴት ውስጥ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","በባለብዙ ደረጃ መርሃግብር ሁኔታ, ደንበኞች በተጠቀሱት ወጪ መሰረት ለተሰጣቸው ደረጃ ደረጃ በራስ መተላለፍ ይኖራቸዋል", Inactive,ገባሪ አይደለም, Incentives,ማበረታቻዎች, -Include Default Book Entries,ነባሪ የመጽሐፍ ግቤቶችን አካትት።, +Include Default FB Entries,ነባሪ የመጽሐፍ ግቤቶችን አካትት።, Include Exploded Items,የተበተኑ ንጥሎችን አካት, Include POS Transactions,የ POS ሽግግሮችን አክል, Include UOM,UOM አካት, diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index 67b409e7ef..195b9c7963 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -1153,7 +1153,7 @@ In Value,القيمة القادمة, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",في حالة البرنامج متعدد المستويات ، سيتم تعيين العملاء تلقائيًا إلى الطبقة المعنية وفقًا للإنفاق, Inactive,غير نشط, Incentives,الحوافز, -Include Default Book Entries,تضمين إدخالات دفتر افتراضي, +Include Default FB Entries,تضمين إدخالات دفتر افتراضي, Include Exploded Items,تشمل البنود المستبعدة, Include POS Transactions,تشمل معاملات نقطه البيع, Include UOM,تضمين UOM, diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv index 787f81ee7f..c2bacf4f93 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -1153,7 +1153,7 @@ In Value,В стойност, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","В случай на многостепенна програма, клиентите ще бъдат автоматично зададени на съответния подреждан по тяхна сметка", Inactive,неактивен, Incentives,Стимули, -Include Default Book Entries,Включете записи по подразбиране на книги, +Include Default FB Entries,Включете записи по подразбиране на книги, Include Exploded Items,Включете експлодираните елементи, Include POS Transactions,Включете POS транзакции, Include UOM,Включете UOM, diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv index 69fd08cb51..d7366e14ab 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -1153,7 +1153,7 @@ In Value,মান, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","মাল্টি-টিয়ার প্রোগ্রামের ক্ষেত্রে, গ্রাহকরা তাদের ব্যয় অনুযায়ী সংশ্লিষ্ট টায়ারে স্বয়ংক্রিয়ভাবে নিয়োগ পাবেন", Inactive,নিষ্ক্রিয়, Incentives,ইনসেনটিভ, -Include Default Book Entries,ডিফল্ট বুক এন্ট্রি অন্তর্ভুক্ত করুন, +Include Default FB Entries,ডিফল্ট বুক এন্ট্রি অন্তর্ভুক্ত করুন, Include Exploded Items,বিস্ফোরিত আইটেম অন্তর্ভুক্ত করুন, Include POS Transactions,পিওএস লেনদেন অন্তর্ভুক্ত করুন, Include UOM,UOM অন্তর্ভুক্ত করুন, diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv index ef680a36ad..df4083eed6 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -1153,7 +1153,7 @@ In Value,u vrijednost, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","U slučaju višeslojnog programa, Korisnici će automatski biti dodeljeni za dotičnu grupu po njihovom trošenju", Inactive,Neaktivan, Incentives,Poticaji, -Include Default Book Entries,Uključite zadane unose knjiga, +Include Default FB Entries,Uključite zadane unose knjiga, Include Exploded Items,Uključite eksplodirane predmete, Include POS Transactions,Uključite POS transakcije, Include UOM,Uključite UOM, diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv index fa545a4c19..b3cf2c5fe1 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -1153,7 +1153,7 @@ In Value,En valor, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","En el cas del programa de diversos nivells, els clients seran assignats automàticament al nivell corresponent segons el seu gastat", Inactive,Inactiu, Incentives,Incentius, -Include Default Book Entries,Inclou les entrades de llibres predeterminats, +Include Default FB Entries,Inclou les entrades de llibres predeterminats, Include Exploded Items,Inclou articles explotats, Include POS Transactions,Inclou transaccions de POS, Include UOM,Inclou UOM, diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv index 7fb1679f9c..b6deaa46d8 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -1153,7 +1153,7 @@ In Value,v Hodnota, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",V případě víceúrovňového programu budou zákazníci automaticky přiděleni danému vrstvě podle svých vynaložených nákladů, Inactive,Neaktivní, Incentives,Pobídky, -Include Default Book Entries,Zahrnout výchozí položky knihy, +Include Default FB Entries,Zahrnout výchozí položky knihy, Include Exploded Items,Zahrnout výbušné položky, Include POS Transactions,Zahrnout POS transakce, Include UOM,Zahrnout UOM, diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv index 4eb39609f2..4bcc307583 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -1153,7 +1153,7 @@ In Value,I Value, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","I tilfælde af multi-tier program, vil kunder automatisk blive tildelt den pågældende tier som per deres brugt", Inactive,inaktive, Incentives,Incitamenter, -Include Default Book Entries,Inkluder standardbogsindlæg, +Include Default FB Entries,Inkluder standardbogsindlæg, Include Exploded Items,Inkluder eksploderede elementer, Include POS Transactions,Inkluder POS-transaktioner, Include UOM,Inkluder UOM, diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index c856fefcc0..61e301eed4 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1161,7 +1161,7 @@ In Value,Wert bei, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",Im Falle eines mehrstufigen Programms werden Kunden automatisch der betroffenen Ebene entsprechend ihrer Ausgaben zugewiesen, Inactive,Inaktiv, Incentives,Anreize, -Include Default Book Entries,Standardbucheinträge einschließen, +Include Default FB Entries,Standardbucheinträge einschließen, Include Exploded Items,Unterartikel einbeziehen, Include POS Transactions,POS-Transaktionen einschließen, Include UOM,Fügen Sie UOM hinzu, diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv index 21fb435901..e67eaffc69 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -1153,7 +1153,7 @@ In Value,στην Αξία, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Στην περίπτωση προγράμματος πολλαπλών βαθμίδων, οι Πελάτες θα αντιστοιχούν αυτόματα στη σχετική βαθμίδα σύμφωνα με το ποσό που δαπανώνται", Inactive,Αδρανής, Incentives,Κίνητρα, -Include Default Book Entries,Συμπεριλάβετε τις προεπιλεγμένες καταχωρίσεις βιβλίων, +Include Default FB Entries,Συμπεριλάβετε τις προεπιλεγμένες καταχωρίσεις βιβλίων, Include Exploded Items,Συμπεριλάβετε εκραγμένα στοιχεία, Include POS Transactions,Συμπεριλάβετε τις συναλλαγές POS, Include UOM,Συμπεριλάβετε UOM, diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv index 2abe418707..0c90694f8f 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -1153,7 +1153,7 @@ In Value,En valor, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","En el caso del programa de varios niveles, los Clientes se asignarán automáticamente al nivel correspondiente según su gasto", Inactive,Inactivo, Incentives,Incentivos, -Include Default Book Entries,Incluir entradas de libro predeterminadas, +Include Default FB Entries,Incluir entradas de libro predeterminadas, Include Exploded Items,Incluir Elementos Estallados, Include POS Transactions,Incluir transacciones POS, Include UOM,Incluir UOM, diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv index a4a8736006..69a89d9d22 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -1153,7 +1153,7 @@ In Value,väärtuse, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",Mitmekordsete programmide korral määratakse Kliendid automaatselt asjaomasele tasemele vastavalt nende kasutatud kuludele, Inactive,Mitteaktiivne, Incentives,Soodustused, -Include Default Book Entries,Lisage vaikeraamatu kanded, +Include Default FB Entries,Lisage vaikeraamatu kanded, Include Exploded Items,Kaasa lõhutud esemed, Include POS Transactions,Kaasa POS-tehingud, Include UOM,Lisa UOM, diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv index bd40c8b396..cddabfb448 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -1153,7 +1153,7 @@ In Value,با ارزش, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",در مورد برنامه چند لایه، مشتریان به صورت خودکار به سطر مربوطه اختصاص داده می شوند، همانطور که در هزینه های خود هستند, Inactive,غیر فعال, Incentives,انگیزه, -Include Default Book Entries,شامل ورودی های پیش فرض کتاب, +Include Default FB Entries,شامل ورودی های پیش فرض کتاب, Include Exploded Items,شامل موارد انفجار, Include POS Transactions,شامل معاملات POS, Include UOM,شامل UOM, diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv index 33cf1574ae..eae6053915 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -1153,7 +1153,7 @@ In Value,in Arvo, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",Monitasoisen ohjelman tapauksessa asiakkaat määräytyvät automaattisesti kyseiselle tasolle niiden kulutuksen mukaan, Inactive,Epäaktiivinen, Incentives,kannustimet/bonukset, -Include Default Book Entries,Sisällytä oletustiedot, +Include Default FB Entries,Sisällytä oletustiedot, Include Exploded Items,Sisällytä räjähtämättömiä kohteita, Include POS Transactions,Sisällytä POS-tapahtumia, Include UOM,Sisällytä UOM, diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index d15af74d47..5c34759a70 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -1058,7 +1058,7 @@ In Stock: ,En Stock :, In Value,En valeur, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Dans le cas d'un programme à plusieurs échelons, les clients seront automatiquement affectés au niveau approprié en fonction de leurs dépenses", Incentives,Incitations, -Include Default Book Entries,Inclure les entrées de livre par défaut, +Include Default FB Entries,Inclure les entrées de livre par défaut, Include Exploded Items,Inclure les articles éclatés, Include POS Transactions,Inclure les transactions du point de vente, Include UOM,Inclure UdM, diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv index 06a3cc64af..604ec411c4 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -1153,7 +1153,7 @@ In Value,ભાવ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","મલ્ટિ-ટાયર પ્રોગ્રામના કિસ્સામાં, ગ્રાહક તેમના ખર્ચ મુજબ સંબંધિત ટાયરમાં ઓટો હશે", Inactive,નિષ્ક્રિય, Incentives,ઇનસેન્ટીવ્સ, -Include Default Book Entries,ડિફaultલ્ટ બુક એન્ટ્રીઓ શામેલ કરો, +Include Default FB Entries,ડિફaultલ્ટ બુક એન્ટ્રીઓ શામેલ કરો, Include Exploded Items,વિસ્ફોટ થયેલ આઇટમ્સ શામેલ કરો, Include POS Transactions,POS વ્યવહારો શામેલ કરો, Include UOM,યુએમએમ શામેલ કરો, diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv index d5fcab6454..5407578f2b 100644 --- a/erpnext/translations/he.csv +++ b/erpnext/translations/he.csv @@ -1153,7 +1153,7 @@ In Value,ערך, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","במקרה של תוכנית רב-שכבתית, הלקוחות יוקצו אוטומטית לשכבה הנוגעת בדבר שהוצאו", Inactive,לֹא פָּעִיל, Incentives,תמריצים, -Include Default Book Entries,כלול רשומות ברירת מחדל לספרים, +Include Default FB Entries,כלול רשומות ברירת מחדל לספרים, Include Exploded Items,כלול פריטים מפוצצים, Include POS Transactions,כלול עסקאות קופה, Include UOM,כלול UOM, diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv index a5caa666c2..00532df0f1 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -1153,7 +1153,7 @@ In Value,मूल्य में, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","मल्टी-स्तरीय कार्यक्रम के मामले में, ग्राहक अपने खर्च के अनुसार संबंधित स्तर को स्वचालित रूप से सौंपा जाएगा", Inactive,निष्क्रिय, Incentives,प्रोत्साहन, -Include Default Book Entries,डिफ़ॉल्ट बुक प्रविष्टियाँ शामिल करें, +Include Default FB Entries,डिफ़ॉल्ट बुक प्रविष्टियाँ शामिल करें, Include Exploded Items,विस्फोट किए गए आइटम शामिल करें, Include POS Transactions,पीओएस लेनदेन शामिल करें, Include UOM,यूओएम शामिल करें, diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv index 2834602248..3cc9ef3be2 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -1153,7 +1153,7 @@ In Value,u vrijednost, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","U slučaju višerazinskog programa, Kupci će biti automatski dodijeljeni odgovarajućem stupcu po njihovu potrošenom", Inactive,neaktivan, Incentives,poticaji, -Include Default Book Entries,Uključite zadane unose u knjige, +Include Default FB Entries,Uključite zadane unose u knjige, Include Exploded Items,Uključi eksplodirane predmete, Include POS Transactions,Uključi POS transakcije, Include UOM,Uključi UOM, diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv index a262c8a994..42175bbe34 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -1153,7 +1153,7 @@ In Value,Az Értékben, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Többszintű program esetében az ügyfeleket automatikusan az adott kategóriába sorolják, az általuk elköltöttek szerint", Inactive,Inaktív, Incentives,Ösztönzők, -Include Default Book Entries,Tartalmazza az alapértelmezett könyvbejegyzéseket, +Include Default FB Entries,Tartalmazza az alapértelmezett könyvbejegyzéseket, Include Exploded Items,Tartalmazza a robbantott elemeket, Include POS Transactions,Tartalmazza a POS kassza tranzakciókat, Include UOM,Ide tartozik az ANYJ, diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv index c4e50fdfe5..d69eef389d 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -1153,7 +1153,7 @@ In Value,Nilai, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Dalam kasus program multi-tier, Pelanggan akan ditugaskan secara otomatis ke tingkat yang bersangkutan sesuai yang mereka habiskan", Inactive,Tidak aktif, Incentives,Insentif, -Include Default Book Entries,Sertakan Entri Buku Default, +Include Default FB Entries,Sertakan Entri Buku Default, Include Exploded Items,Sertakan barang yang meledak, Include POS Transactions,Sertakan Transaksi POS, Include UOM,Termasuk UOM, diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv index 50c06ecfbb..1acefbb3ee 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -1153,7 +1153,7 @@ In Value,Virði, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Þegar um er að ræða fjölþættaráætlun, verða viðskiptavinir sjálfkrafa tengdir viðkomandi flokka eftir því sem þeir eru í", Inactive,Óvirkt, Incentives,Incentives, -Include Default Book Entries,Hafa sjálfgefnar bókarfærslur með, +Include Default FB Entries,Hafa sjálfgefnar bókarfærslur með, Include Exploded Items,Inniheldur sprauta hluti, Include POS Transactions,Innifalið POS-viðskipti, Include UOM,Innifalið UOM, diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv index 37608958c3..e6e64254bb 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -1153,7 +1153,7 @@ In Value,In valore, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Nel caso di un programma multilivello, i clienti verranno assegnati automaticamente al livello interessato come da loro speso", Inactive,Inattivo, Incentives,Incentivi, -Include Default Book Entries,Includi voci di libro predefinite, +Include Default FB Entries,Includi voci di libro predefinite, Include Exploded Items,Includi elementi esplosi, Include POS Transactions,Includi transazioni POS, Include UOM,Includi UOM, diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv index 888ec800c9..dd5820ae77 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -1153,7 +1153,7 @@ In Value,値内, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",マルチティアプログラムの場合、顧客は、消費されるごとに自動的に関係する層に割り当てられます, Inactive,非アクティブ, Incentives,インセンティブ, -Include Default Book Entries,デフォルトのブックエントリを含める, +Include Default FB Entries,デフォルトのブックエントリを含める, Include Exploded Items,分解された項目を含める, Include POS Transactions,POSトランザクションを含める, Include UOM,UOMを含める, diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv index d2003c004e..2740d7fc8a 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -1153,7 +1153,7 @@ In Value,នៅក្នុងតម្លៃ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",ក្នុងករណីមានកម្មវិធីពហុលំដាប់អតិថិជននឹងត្រូវបានចាត់តាំងដោយខ្លួនឯងទៅថ្នាក់ដែលពាក់ព័ន្ធដោយចំណាយរបស់ពួកគេ, Inactive,អសកម្ម, Incentives,ការលើកទឹកចិត្ត, -Include Default Book Entries,រួមបញ្ចូលធាតុសៀវភៅលំនាំដើម។, +Include Default FB Entries,រួមបញ្ចូលធាតុសៀវភៅលំនាំដើម។, Include Exploded Items,រួមបញ្ចូលធាតុផ្ទុះ, Include POS Transactions,បញ្ចូលប្រតិបត្តិការ POS, Include UOM,រួមបញ្ចូល UOM, diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv index 72066711ca..8b271682eb 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -1153,7 +1153,7 @@ In Value,ಮೌಲ್ಯ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","ಮಲ್ಟಿ-ಟೈರ್ ಪ್ರೋಗ್ರಾಂನ ಸಂದರ್ಭದಲ್ಲಿ, ಗ್ರಾಹಕರು ತಮ್ಮ ಖರ್ಚುಗೆ ಅನುಗುಣವಾಗಿ ಆಯಾ ಶ್ರೇಣಿಗೆ ಸ್ವಯಂ ನಿಯೋಜಿಸಲಾಗುವುದು", Inactive,ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ, Incentives,ಪ್ರೋತ್ಸಾಹ, -Include Default Book Entries,ಡೀಫಾಲ್ಟ್ ಪುಸ್ತಕ ನಮೂದುಗಳನ್ನು ಸೇರಿಸಿ, +Include Default FB Entries,ಡೀಫಾಲ್ಟ್ ಪುಸ್ತಕ ನಮೂದುಗಳನ್ನು ಸೇರಿಸಿ, Include Exploded Items,ಸ್ಫೋಟಗೊಂಡ ವಸ್ತುಗಳನ್ನು ಸೇರಿಸಿ, Include POS Transactions,ಪಿಒಎಸ್ ಟ್ರಾನ್ಸಾಕ್ಷನ್ಸ್ ಸೇರಿಸಿ, Include UOM,UOM ಸೇರಿಸಿ, diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv index 99119256c1..edd3f2731f 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -1153,7 +1153,7 @@ In Value,값에서, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",다중 계층 프로그램의 경우 고객은 지출 한대로 해당 계층에 자동으로 할당됩니다., Inactive,비활성, Incentives,장려책, -Include Default Book Entries,기본 도서 항목 포함, +Include Default FB Entries,기본 도서 항목 포함, Include Exploded Items,분해 된 항목 포함, Include POS Transactions,POS 트랜잭션 포함, Include UOM,UOM 포함, diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv index 8fec05993d..e18ce45132 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -1153,7 +1153,7 @@ In Value,di Nirx, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Di rewşê de bernameya pir-tier, Ewrûpa dê ji hêla xercê xwe ve girêdayî xerîb be", Inactive,Bêkar, Incentives,aborîve, -Include Default Book Entries,Navnîşanên Pirtûka Pêvek Bawer bikin, +Include Default FB Entries,Navnîşanên Pirtûka Pêvek Bawer bikin, Include Exploded Items,Included Dead Items, Include POS Transactions,Têkiliyên POSê de, Include UOM,UOM, diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv index 0831788651..46acd22939 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -1153,7 +1153,7 @@ In Value,ໃນມູນຄ່າ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","ໃນກໍລະນີຂອງໂຄງການຫຼາຍຂັ້ນ, ລູກຄ້າຈະໄດ້ຮັບການມອບຫມາຍໂດຍອັດຕະໂນມັດໃຫ້ກັບຂັ້ນຕອນທີ່ກ່ຽວຂ້ອງຕາມການໃຊ້ຈ່າຍຂອງເຂົາເຈົ້າ", Inactive,Inactive, Incentives,ສິ່ງຈູງໃຈ, -Include Default Book Entries,ລວມທັງການອອກສຽງປື້ມແບບເລີ່ມຕົ້ນ, +Include Default FB Entries,ລວມທັງການອອກສຽງປື້ມແບບເລີ່ມຕົ້ນ, Include Exploded Items,ລວມເອົາສິ່ງທີ່ເກີດຂື້ນ, Include POS Transactions,ລວມທຸລະກໍາ POS, Include UOM,ລວມ UOM, diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv index 82152754e8..292c9d88e2 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -1153,7 +1153,7 @@ In Value,vertės, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Kalbant apie daugiapakopę programą, klientai bus automatiškai priskirti atitinkamam lygmeniui, atsižvelgiant į jų išleidimą", Inactive,Neaktyvus, Incentives,Paskatos, -Include Default Book Entries,Įtraukite numatytuosius knygų įrašus, +Include Default FB Entries,Įtraukite numatytuosius knygų įrašus, Include Exploded Items,Įtraukti sprogus elementus, Include POS Transactions,Įtraukti POS operacijas, Include UOM,Įtraukti UOM, diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv index 8c4526ca52..52641b25af 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -1153,7 +1153,7 @@ In Value,Vērtībā, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Daudzpakāpju programmas gadījumā Klienti tiks automātiski piešķirti attiecīgajam līmenim, salīdzinot ar viņu iztērēto", Inactive,Neaktīvs, Incentives,Stimuli, -Include Default Book Entries,Iekļaujiet noklusējuma grāmatas ierakstus, +Include Default FB Entries,Iekļaujiet noklusējuma grāmatas ierakstus, Include Exploded Items,Iekļaut izpūstas preces, Include POS Transactions,Iekļaut POS darījumus, Include UOM,Iekļaut UOM, diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv index a622524bf1..0a290198cc 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -1153,7 +1153,7 @@ In Value,Во вредност, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Во случај на повеќеслојна програма, корисниците ќе бидат автоматски доделени на засегнатите нивоа, како на нивните потрошени", Inactive,Неактивен, Incentives,Стимулации, -Include Default Book Entries,Вклучете стандардни записи за книги, +Include Default FB Entries,Вклучете стандардни записи за книги, Include Exploded Items,Вклучи експлодирани елементи, Include POS Transactions,Вклучете POS-трансакции, Include UOM,Вклучете UOM, diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv index 777d5c64ff..04af8ab1ad 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -1153,7 +1153,7 @@ In Value,മൂല്യത്തിൽ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","മൾട്ടി-ടയർ പരിപാടിയുടെ കാര്യത്തിൽ, കസ്റ്റമർമാർ ചെലവഴിച്ച തുക പ്രകാരം ബന്ധപ്പെട്ട ടീമിൽ ഓട്ടോ നിർണ്ണയിക്കും", Inactive,നിഷ്ക്രിയം, Incentives,ഇൻസെന്റീവ്സ്, -Include Default Book Entries,സ്ഥിരസ്ഥിതി പുസ്തക എൻ‌ട്രികൾ‌ ഉൾ‌പ്പെടുത്തുക, +Include Default FB Entries,സ്ഥിരസ്ഥിതി പുസ്തക എൻ‌ട്രികൾ‌ ഉൾ‌പ്പെടുത്തുക, Include Exploded Items,എക്സ്പ്ലോഡഡ് ഇനങ്ങൾ ഉൾപ്പെടുത്തുക, Include POS Transactions,POS ഇടപാടുകൾ ഉൾപ്പെടുത്തുക, Include UOM,UOM ഉൾപ്പെടുത്തുക, diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv index 624f1ab481..785ab65c54 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -1153,7 +1153,7 @@ In Value,मूल्य, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",बहु-स्तरीय कार्यक्रमाच्या बाबतीत ग्राहक त्यांच्या खर्चानुसार संबंधित टायरला स्वयंचलितरित्या नियुक्त केले जातील, Inactive,निष्क्रिय, Incentives,प्रोत्साहन, -Include Default Book Entries,डीफॉल्ट पुस्तक नोंदी समाविष्ट करा, +Include Default FB Entries,डीफॉल्ट पुस्तक नोंदी समाविष्ट करा, Include Exploded Items,विस्फोट केलेल्या वस्तू समाविष्ट करा, Include POS Transactions,पीओएस व्यवहार समाविष्ट करा, Include UOM,यूओएम समाविष्ट करा, diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv index 75e150a88d..db20d3c054 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -1153,7 +1153,7 @@ In Value,Dalam Nilai, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Dalam hal program multi-tier, Pelanggan akan ditugaskan secara automatik ke peringkat yang bersangkutan seperti yang dibelanjakannya", Inactive,Tidak aktif, Incentives,Insentif, -Include Default Book Entries,Sertakan Penyertaan Buku Lalai, +Include Default FB Entries,Sertakan Penyertaan Buku Lalai, Include Exploded Items,Termasuk Item Meletup, Include POS Transactions,Termasuk Transaksi POS, Include UOM,Termasuk UOM, diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv index 36cd8740d7..f4b8676fee 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -1153,7 +1153,7 @@ In Value,Value တစ်ခုအတွက်, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Multi-tier အစီအစဉ်၏အမှု၌, Customer များအလိုအလျောက်သူတို့ရဲ့သုံးစွဲနှုန်းအတိုင်းစိုးရိမ်ပူပန်ဆင့်မှတာဝန်ပေးအပ်ပါလိမ့်မည်", Inactive,မလှုပ်ရှားတတ်သော, Incentives,မက်လုံးတွေပေးပြီး, -Include Default Book Entries,ပုံမှန်စာအုပ် Entries Include, +Include Default FB Entries,ပုံမှန်စာအုပ် Entries Include, Include Exploded Items,ပေါက်ကွဲပစ္စည်းများ Include, Include POS Transactions,POS အရောင်းအဝယ် Include, Include UOM,UOM Include, diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index 5859833861..1778c8044a 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -1153,7 +1153,7 @@ In Value,in Value, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",In het geval van een meerlagig programma worden klanten automatisch toegewezen aan de betreffende laag op basis van hun bestede tijd, Inactive,Inactief, Incentives,Incentives, -Include Default Book Entries,Standaard boekvermeldingen opnemen, +Include Default FB Entries,Standaard boekvermeldingen opnemen, Include Exploded Items,Exploded Items opnemen, Include POS Transactions,POS-transacties opnemen, Include UOM,Inclusief UOM, diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv index a3236ac084..542217afe7 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -1153,7 +1153,7 @@ In Value,I verdi, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Når det gjelder flerlagsprogram, vil kundene automatisk bli tilordnet den aktuelle delen som brukt", Inactive,inaktiv, Incentives,Motivasjon, -Include Default Book Entries,Inkluder standardbokoppføringer, +Include Default FB Entries,Inkluder standardbokoppføringer, Include Exploded Items,Inkluder eksploderte elementer, Include POS Transactions,Inkluder POS-transaksjoner, Include UOM,Inkluder UOM, diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv index df41e39862..247d0bae11 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -1151,7 +1151,7 @@ In Stock: ,W magazynie:, In Value,w polu Wartość, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","W przypadku programu wielowarstwowego Klienci zostaną automatycznie przypisani do danego poziomu, zgodnie z wydatkami", Inactive,Nieaktywny, -Include Default Book Entries,Dołącz domyślne wpisy książki, +Include Default FB Entries,Dołącz domyślne wpisy książki, Include Exploded Items,Dołącz rozstrzelone przedmioty, Include POS Transactions,Uwzględnij transakcje POS, Include UOM,Dołącz UOM, diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv index 5a0b2a50cb..09d4df31ff 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -1153,7 +1153,7 @@ In Value,په ارزښت, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",د څو اړخیز پروګرام په صورت کې، پیرودونکي به د خپل مصرف په اساس اړوند ټیټ ته ګمارل کیږي, Inactive,غیر فعال, Incentives,هڅوونکي, -Include Default Book Entries,د ډیفالټ کتاب ننوتل شامل کړئ, +Include Default FB Entries,د ډیفالټ کتاب ننوتل شامل کړئ, Include Exploded Items,چاودیدونکي توکي شامل کړئ, Include POS Transactions,د POS تعاملات شامل کړئ, Include UOM,UOM شامل کړئ, diff --git a/erpnext/translations/pt-BR.csv b/erpnext/translations/pt-BR.csv index bc5b616080..92845b0a40 100644 --- a/erpnext/translations/pt-BR.csv +++ b/erpnext/translations/pt-BR.csv @@ -1153,7 +1153,7 @@ In Value,Valor Entrada, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","No caso do programa multicamadas, os Clientes serão atribuídos automaticamente ao nível em questão de acordo com o gasto", Inactive,Inativo, Incentives,Incentivos, -Include Default Book Entries,Incluir Entradas de Livro Padrão, +Include Default FB Entries,Incluir Entradas de Livro Padrão, Include Exploded Items,Incluir Itens Explodidos, Include POS Transactions,Incluir Transações PDV, Include UOM,Incluir UDM, diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv index e6846c6a37..58cf6c8316 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -1153,7 +1153,7 @@ In Value,No Valor, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","No caso do programa multicamadas, os Clientes serão atribuídos automaticamente ao nível em questão de acordo com o gasto", Inactive,Inativo, Incentives,Incentivos, -Include Default Book Entries,Incluir entradas de livro padrão, +Include Default FB Entries,Incluir entradas de livro padrão, Include Exploded Items,Incluir itens explodidos, Include POS Transactions,Incluir transações POS, Include UOM,Incluir UOM, diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv index ac7e598e2d..935b1e66fd 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -1153,7 +1153,7 @@ In Value,În valoare, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","În cazul unui program cu mai multe niveluri, Clienții vor fi automat alocați nivelului respectiv în funcție de cheltuielile efectuate", Inactive,Inactiv, Incentives,stimulente, -Include Default Book Entries,Includeți intrări implicite în cărți, +Include Default FB Entries,Includeți intrări implicite în cărți, Include Exploded Items,Includeți articole explodate, Include POS Transactions,Includeți tranzacțiile POS, Include UOM,Includeți UOM, diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 52c29982f3..2f6f361b10 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -1151,7 +1151,7 @@ In Value,В цене, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",В случае многоуровневой программы Клиенты будут автоматически назначены соответствующему уровню в соответствии с затраченными, Inactive,Неактивный, Incentives,Стимулирование, -Include Default Book Entries,Включить записи в книгу по умолчанию, +Include Default FB Entries,Включить записи в книгу по умолчанию, Include Exploded Items,Включить раздробленные элементы, Include POS Transactions,Включить POS-транзакции, Include UOM,Включить UOM, diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv index f035d57985..59362a1e29 100644 --- a/erpnext/translations/rw.csv +++ b/erpnext/translations/rw.csv @@ -1153,7 +1153,7 @@ In Value,Agaciro, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Kubijyanye na gahunda yo mu byiciro byinshi, Abakiriya bazahabwa imodoka bashinzwe urwego bireba nkuko bakoresheje", Inactive,Kudakora, Incentives,Inkunga, -Include Default Book Entries,Shyiramo Ibisanzwe Byanditswe, +Include Default FB Entries,Shyiramo Ibisanzwe Byanditswe, Include Exploded Items,Shyiramo Ibintu Biturika, Include POS Transactions,Shyiramo ibikorwa bya POS, Include UOM,Shyiramo UOM, diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv index 4047263492..dd2acc45a2 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -1153,7 +1153,7 @@ In Value,අගය දී, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","බහු ස්ථරයේ වැඩසටහනක දී, පාරිභෝගිකයින් විසින් වැය කරනු ලබන පරිදි පාරිභෝගිකයින්ට අදාල ස්ථානයට ස්වයංක්රීයව පවරනු ලැබේ", Inactive,අක්රියයි, Incentives,සහන, -Include Default Book Entries,පෙරනිමි පොත් ඇතුළත් කිරීම් ඇතුළත් කරන්න, +Include Default FB Entries,පෙරනිමි පොත් ඇතුළත් කිරීම් ඇතුළත් කරන්න, Include Exploded Items,පුපුරණ ද්රව්ය අඩංගු කරන්න, Include POS Transactions,POS ගනුදෙනු, Include UOM,UOM ඇතුළත් කරන්න, diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv index 98e1663f1c..025c8b788f 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -1153,7 +1153,7 @@ In Value,v Hodnota, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",V prípade viacvrstvového programu budú zákazníci automaticky priradení príslušnému vrstvu podľa ich vynaložených prostriedkov, Inactive,neaktívne, Incentives,Pobídky, -Include Default Book Entries,Zahrnúť predvolené položky knihy, +Include Default FB Entries,Zahrnúť predvolené položky knihy, Include Exploded Items,Zahrňte explodované položky, Include POS Transactions,Zahrňte POS transakcie, Include UOM,Zahrňte UOM, diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv index 5380714bdc..86b5e58129 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -1153,7 +1153,7 @@ In Value,V vrednosti, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",V primeru večstopenjskega programa bodo stranke samodejno dodeljene zadevni stopnji glede na porabljene, Inactive,Neaktivno, Incentives,Spodbude, -Include Default Book Entries,Vključi privzete vnose v knjige, +Include Default FB Entries,Vključi privzete vnose v knjige, Include Exploded Items,Vključi eksplodirane elemente, Include POS Transactions,Vključite POS transakcije, Include UOM,Vključi UOM, diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv index 2a893d272b..3cfa4297df 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -1153,7 +1153,7 @@ In Value,Në Vlera, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Në rastin e programit multi-shtresor, Konsumatorët do të caktohen automatikisht në nivelin përkatës sipas shpenzimeve të tyre", Inactive,joaktiv, Incentives,Nxitjet, -Include Default Book Entries,Përfshini hyrje të librave të paracaktuar, +Include Default FB Entries,Përfshini hyrje të librave të paracaktuar, Include Exploded Items,Përfshirja e artikujve të eksploduar, Include POS Transactions,Përfshi transaksione POS, Include UOM,Përfshi UOM, diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv index c1e5eb0eea..621772f39f 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -1153,7 +1153,7 @@ In Value,У вредности, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","У случају мулти-тиер програма, Корисници ће аутоматски бити додијељени за одређени ниво према њиховом потрошеном", Inactive,Неактиван, Incentives,Подстицаји, -Include Default Book Entries,Укључивање заданих уноса у књиге, +Include Default FB Entries,Укључивање заданих уноса у књиге, Include Exploded Items,Укључите експлодиране ставке, Include POS Transactions,Укључите ПОС трансакције, Include UOM,Укључите УОМ, diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv index 8b4ab068eb..4fef88b7f4 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -1153,7 +1153,7 @@ In Value,Värde, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",När det gäller program med flera nivåer kommer kunderna automatiskt att tilldelas den aktuella tiern enligt deras tillbringade, Inactive,Inaktiv, Incentives,Sporen, -Include Default Book Entries,Inkludera standardbokposter, +Include Default FB Entries,Inkludera standardbokposter, Include Exploded Items,Inkludera explosiva artiklar, Include POS Transactions,Inkludera POS-transaktioner, Include UOM,Inkludera UOM, diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv index fa2287c3bb..3b4d8aee64 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -1153,7 +1153,7 @@ In Value,Kwa Thamani, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Katika kesi ya mpango wa mipango mbalimbali, Wateja watapatiwa auto kwa tier husika kama kwa matumizi yao", Inactive,Haikufanya kazi, Incentives,Vidokezo, -Include Default Book Entries,Jumuisha Ingizo Mbadala za Kitabu, +Include Default FB Entries,Jumuisha Ingizo Mbadala za Kitabu, Include Exploded Items,Jumuisha Vipengee Vipengee, Include POS Transactions,Jumuisha Shughuli za POS, Include UOM,Jumuisha UOM, diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv index 6eaae34a6e..f40e512427 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -1153,7 +1153,7 @@ In Value,மதிப்பு, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","பல அடுக்கு திட்டத்தின் விஷயத்தில், வாடிக்கையாளர்கள் தங்கள் செலவினங்களின்படி சம்பந்தப்பட்ட அடுக்குக்கு கார் ஒதுக்கப்படுவார்கள்", Inactive,செயல்படா, Incentives,செயல் தூண்டுதல், -Include Default Book Entries,இயல்புநிலை புத்தக உள்ளீடுகளைச் சேர்க்கவும், +Include Default FB Entries,இயல்புநிலை புத்தக உள்ளீடுகளைச் சேர்க்கவும், Include Exploded Items,வெடித்துள்ள பொருட்கள் அடங்கும், Include POS Transactions,POS பரிமாற்றங்களைச் சேர்க்கவும், Include UOM,UOM ஐ சேர்க்கவும், diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv index d3f739af8b..22fd7d7d1d 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -1153,7 +1153,7 @@ In Value,విలువ, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","మల్టీ-టైర్ ప్రోగ్రామ్ విషయంలో, కస్టమర్లు వారి గడువు ప్రకారం, సంబంధిత స్థాయికి కేటాయించబడతారు", Inactive,క్రియారహిత, Incentives,ఇన్సెంటివ్స్, -Include Default Book Entries,డిఫాల్ట్ బుక్ ఎంట్రీలను చేర్చండి, +Include Default FB Entries,డిఫాల్ట్ బుక్ ఎంట్రీలను చేర్చండి, Include Exploded Items,ఎక్స్ప్లోడ్ ఐటెమ్లను చేర్చండి, Include POS Transactions,POS లావాదేవీలను చేర్చండి, Include UOM,UOM ని చేర్చండి, diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv index a065595898..5dfb93c585 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -1153,7 +1153,7 @@ In Value,ในราคา, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",ในกรณีของโปรแกรมแบบหลายชั้นลูกค้าจะได้รับการกำหนดให้โดยอัตโนมัติตามระดับที่เกี่ยวข้องตามการใช้จ่าย, Inactive,เฉื่อยชา, Incentives,แรงจูงใจ, -Include Default Book Entries,รวมรายการหนังสือเริ่มต้น, +Include Default FB Entries,รวมรายการหนังสือเริ่มต้น, Include Exploded Items,รวมรายการที่ระเบิดแล้ว, Include POS Transactions,รวมธุรกรรม POS, Include UOM,รวม UOM, diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index 9e916f0315..82d28240c1 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -1153,7 +1153,7 @@ In Value,Giriş Maliyeti, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Çok katmanlı program söz konusu olduğunda, Müşteriler harcanan esasa göre ilgili kademeye otomatik olarak atanacaktır.", Inactive,etkisiz, Incentives,Teşvikler, -Include Default Book Entries,Varsayılan Defter Girişlerini Dahil et, +Include Default FB Entries,Varsayılan Defter Girişlerini Dahil et, Include Exploded Items,Patlatılmış Öğeleri Dahil et, Include POS Transactions,POS İşlemlerini Dahil et, Include UOM,Birimi Dahil et, diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv index 8d1fb04fdb..f77c6da35e 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -1153,7 +1153,7 @@ In Value,У Сумі, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","У випадку багаторівневої програми, Клієнти будуть автоматично призначені для відповідного рівня відповідно до витрачених ними витрат", Inactive,Неактивний, Incentives,Стимули, -Include Default Book Entries,Включити записи за замовчуванням, +Include Default FB Entries,Включити записи за замовчуванням, Include Exploded Items,Включити вибухнуті елементи, Include POS Transactions,Включити POS-транзакції, Include UOM,Включити UOM, diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv index 649c1c7759..4dc872be5d 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -1153,7 +1153,7 @@ In Value,قدر میں, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",کثیر درجے کے پروگرام کے معاملے میں، صارفین اپنے اخراجات کے مطابق متعلقہ درجے کو خود کار طریقے سے تفویض کریں گے, Inactive,غیر فعال, Incentives,ترغیبات, -Include Default Book Entries,ڈیفالٹ کتاب اندراجات شامل کریں۔, +Include Default FB Entries,ڈیفالٹ کتاب اندراجات شامل کریں۔, Include Exploded Items,دھماکہ خیز اشیاء شامل کریں, Include POS Transactions,پی او ایس کے لین دین میں شامل کریں, Include UOM,UOM شامل کریں, diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv index 5ca51ccb0f..c09aa895e9 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -1153,7 +1153,7 @@ In Value,Qiymatida, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",Ko'p qatlamli dasturda mijozlar o'zlari sarflagan xarajatlariga muvofiq tegishli darajaga avtomatik tarzda topshiriladi, Inactive,Faol emas, Incentives,Rag'batlantirish, -Include Default Book Entries,Odatiy kitob yozuvlarini qo'shing, +Include Default FB Entries,Odatiy kitob yozuvlarini qo'shing, Include Exploded Items,Portlatilgan narsalarni qo'shish, Include POS Transactions,Qalin operatsiyalarni qo'shish, Include UOM,UOM ni qo'shing, diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv index 7a005fa414..eb251a5978 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -1153,7 +1153,7 @@ In Value,Trong giá trị, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Trong trường hợp chương trình nhiều tầng, Khách hàng sẽ được tự động chỉ định cho cấp có liên quan theo mức chi tiêu của họ", Inactive,Không hoạt động, Incentives,Ưu đãi, -Include Default Book Entries,Bao gồm các mục sách mặc định, +Include Default FB Entries,Bao gồm các mục sách mặc định, Include Exploded Items,Bao gồm các mục đã Phát hiện, Include POS Transactions,Bao gồm giao dịch POS, Include UOM,Bao gồm UOM, diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index cf89dc6852..08f8d33578 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -1153,7 +1153,7 @@ In Value,金额, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",在多层程序的情况下,客户将根据其花费自动分配到相关层, Inactive,非活动的, Incentives,激励政策, -Include Default Book Entries,包括默认工作簿条目, +Include Default FB Entries,包括默认工作簿条目, Include Exploded Items,包含爆炸物料, Include POS Transactions,包括POS交易, Include UOM,包括基本计量单位, diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv index 8ecbaaa9c5..dd683c5a27 100644 --- a/erpnext/translations/zh_tw.csv +++ b/erpnext/translations/zh_tw.csv @@ -1166,7 +1166,7 @@ In Value,在數值, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent",在多層程序的情況下,客戶將根據其花費自動分配到相關層, Inactive,待用, Incentives,獎勵, -Include Default Book Entries,包括默認工作簿條目, +Include Default FB Entries,包括默認工作簿條目, Include Exploded Items,包含爆炸物品, Include UOM,包括UOM, Included in Gross Profit,包含在毛利潤中, From a89afb65d72b7beb985449151b25fc678b4c6891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Wed, 8 Nov 2023 21:52:32 +0100 Subject: [PATCH 041/163] fix: Mark Status field in Payment Entry 'no_copy'. --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index d7b6a198df..4d50a35ed4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -595,6 +595,7 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", + "no_copy": 1, "options": "\nDraft\nSubmitted\nCancelled", "read_only": 1 }, @@ -750,7 +751,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-23 18:07:38.023010", + "modified": "2023-11-08 21:51:03.482709", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From 6e3e094c957d8c2b71ed522e9a8034364345e5a7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 2 Nov 2023 17:19:06 +0530 Subject: [PATCH 042/163] refactor: ignore disabled account while selecting Income Accounts --- .../doctype/sales_invoice/sales_invoice.js | 20 +++++++++---------- erpnext/controllers/queries.py | 2 ++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index d4d923902f..b1a7b10eea 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -187,7 +187,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm); } - make_maintenance_schedule() { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule", @@ -563,15 +562,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) { } } -// Income Account in Details Table -// -------------------------------- -cur_frm.set_query("income_account", "items", function(doc) { - return{ - query: "erpnext.controllers.queries.get_income_account", - filters: {'company': doc.company} - } -}); - // Cost Center in Details Table // ----------------------------- cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) { @@ -666,6 +656,16 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("income_account", "items", function() { + return{ + query: "erpnext.controllers.queries.get_income_account", + filters: { + 'company': frm.doc.company, + "disabled": 0 + } + } + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Return / Credit Note', diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 5ec24743d9..199732b152 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -611,6 +611,8 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): if filters.get("company"): condition += "and tabAccount.company = %(company)s" + condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}" + return frappe.db.sql( """select tabAccount.name from `tabAccount` where (tabAccount.report_type = "Profit and Loss" From 779260fb497d6ac8000a89b7b762145c58e84471 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 9 Nov 2023 12:19:45 +0530 Subject: [PATCH 043/163] fix: make item field read-only in batch --- erpnext/stock/doctype/batch/batch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json index e6cb3516a3..e20030a568 100644 --- a/erpnext/stock/doctype/batch/batch.json +++ b/erpnext/stock/doctype/batch/batch.json @@ -61,6 +61,7 @@ "oldfieldname": "item", "oldfieldtype": "Link", "options": "Item", + "read_only_depends_on": "eval:!doc.__islocal", "reqd": 1 }, { @@ -207,7 +208,7 @@ "image_field": "image", "links": [], "max_attachments": 5, - "modified": "2023-03-12 15:56:09.516586", + "modified": "2023-11-09 12:17:28.339975", "modified_by": "Administrator", "module": "Stock", "name": "Batch", @@ -224,7 +225,6 @@ "read": 1, "report": 1, "role": "Item Manager", - "set_user_permissions": 1, "share": 1, "write": 1 } From a8216b9727401bfb6900f15447ab935756d7f353 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 9 Nov 2023 12:22:26 +0530 Subject: [PATCH 044/163] fix: make adjustment entry using stock reconciliation (#37995) fix: do adjustment entry using stock reconciliation --- .../stock_ledger_entry.json | 9 +++++- .../stock_reconciliation.py | 20 +++++++++++++ erpnext/stock/stock_ledger.py | 30 +++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 569f58a69f..dcbd9b2d06 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -11,6 +11,7 @@ "warehouse", "posting_date", "posting_time", + "is_adjustment_entry", "column_break_6", "voucher_type", "voucher_no", @@ -333,6 +334,12 @@ "fieldname": "has_serial_no", "fieldtype": "Check", "label": "Has Serial No" + }, + { + "default": "0", + "fieldname": "is_adjustment_entry", + "fieldtype": "Check", + "label": "Is Adjustment Entry" } ], "hide_toolbar": 1, @@ -341,7 +348,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-04-03 16:33:16.270722", + "modified": "2023-10-23 18:07:42.063615", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 323ad4f57d..1bf143b49c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -442,6 +442,11 @@ class StockReconciliation(StockController): sl_entries = [] for row in self.items: + + if not row.qty and not row.valuation_rate and not row.current_qty: + self.make_adjustment_entry(row, sl_entries) + continue + item = frappe.get_cached_value( "Item", row.item_code, ["has_serial_no", "has_batch_no"], as_dict=1 ) @@ -492,6 +497,21 @@ class StockReconciliation(StockController): ) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) + def make_adjustment_entry(self, row, sl_entries): + from erpnext.stock.stock_ledger import get_stock_value_difference + + difference_amount = get_stock_value_difference( + row.item_code, row.warehouse, self.posting_date, self.posting_time + ) + + if not difference_amount: + return + + args = self.get_sle_for_items(row) + args.update({"stock_value_difference": -1 * difference_amount, "is_adjustment_entry": 1}) + + sl_entries.append(args) + def get_sle_for_serialized_items(self, row, sl_entries): if row.current_serial_and_batch_bundle: args = self.get_sle_for_items(row) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e9381d42b9..63908945c3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -759,9 +759,11 @@ class update_entries_after(object): sle.valuation_rate = self.wh_data.valuation_rate sle.stock_value = self.wh_data.stock_value sle.stock_queue = json.dumps(self.wh_data.stock_queue) - sle.stock_value_difference = stock_value_difference - sle.doctype = "Stock Ledger Entry" + if not sle.is_adjustment_entry or not self.args.get("sle_id"): + sle.stock_value_difference = stock_value_difference + + sle.doctype = "Stock Ledger Entry" frappe.get_doc(sle).db_update() if not self.args.get("sle_id"): @@ -1939,3 +1941,27 @@ def is_internal_transfer(sle): if data.is_internal_supplier and data.represents_company == data.company: return True + + +def get_stock_value_difference(item_code, warehouse, posting_date, posting_time, voucher_no=None): + table = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(table) + .select(Sum(table.stock_value_difference).as_("value")) + .where( + (table.is_cancelled == 0) + & (table.item_code == item_code) + & (table.warehouse == warehouse) + & ( + (table.posting_date < posting_date) + | ((table.posting_date == posting_date) & (table.posting_time <= posting_time)) + ) + ) + ) + + if voucher_no: + query = query.where(table.voucher_no != voucher_no) + + difference_amount = query.run() + return flt(difference_amount[0][0]) if difference_amount else 0 From 56ac3424d22b68bbc551913133803686431d1694 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 9 Nov 2023 12:50:44 +0100 Subject: [PATCH 045/163] fix: Alert message and make sure invoice due dates are different for effective test - Make invoice due dates are different so that the invoice with the earliest due date is allocated first in the test - Translate voucher type, simplify alert message. The invoice could be "split" into 1 row, no. of rows in the message seems unnecessary. --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- .../accounts/doctype/payment_entry/test_payment_entry.py | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index fc6e1f462e..de48b1189b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1713,8 +1713,8 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list continue frappe.msgprint( - _("Spliting {} {} into {} row(s) as per Payment Terms").format( - split_rows[0]["voucher_type"], split_rows[0]["voucher_no"], len(split_rows) + _("Splitting {0} {1} as per Payment Terms").format( + _(entry.voucher_type), frappe.bold(entry.voucher_no) ), alert=True, ) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index b3511f0f52..797a363aba 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1270,7 +1270,10 @@ class TestPaymentEntry(FrappeTestCase): customer = create_customer("Max Mustermann", "INR") create_payment_terms_template() - si = create_sales_invoice(qty=1, rate=100, customer=customer) + # SI has an earlier due date and SI2 has a later due date + si = create_sales_invoice( + qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4) + ) si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer) si2.payment_terms_template = "Test Receivable Template" si2.submit() @@ -1286,8 +1289,8 @@ class TestPaymentEntry(FrappeTestCase): args.update( { "get_outstanding_invoices": True, - "from_posting_date": nowdate(), - "to_posting_date": add_days(nowdate(), 7), + "from_posting_date": add_days(nowdate(), -4), + "to_posting_date": add_days(nowdate(), 2), } ) references = get_outstanding_reference_documents(args) From 4b4b176fcf99241bf17067989ebc850b2de24bcb Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 9 Nov 2023 13:03:52 +0100 Subject: [PATCH 046/163] style: Remove spaces introduced via merge conflict --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 0d5387ef40..603f24a09c 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1288,7 +1288,7 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].voucher_no, si2.name) self.assertEqual(references[1].payment_term, "Basic Amount Receivable") self.assertEqual(references[2].payment_term, "Tax Receivable") - + def test_receive_payment_from_payable_party_type(self): pe = create_payment_entry( party_type="Supplier", From c2bda2c70535ad9e8a61e752bbb551f387460125 Mon Sep 17 00:00:00 2001 From: Vishakh Desai <78500008+vishakhdesai@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:26:34 +0530 Subject: [PATCH 047/163] fix: Supplier Quotation fields (#37963) --- erpnext/accounts/party.py | 7 ++- .../request_for_quotation.json | 16 ++++- .../supplier_quotation.json | 60 ++++++++++++++++++- erpnext/public/js/utils/party.js | 2 +- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 16e73ea52f..371474e0a6 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -31,7 +31,12 @@ from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen from erpnext.utilities.regional import temporary_flag -PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"} +PURCHASE_TRANSACTION_TYPES = { + "Supplier Quotation", + "Purchase Order", + "Purchase Receipt", + "Purchase Invoice", +} SALES_TRANSACTION_TYPES = { "Quotation", "Sales Order", diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index 06dbd86ba1..fd73f77ff8 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -9,6 +9,8 @@ "field_order": [ "naming_series", "company", + "billing_address", + "billing_address_display", "vendor", "column_break1", "transaction_date", @@ -292,13 +294,25 @@ "fieldtype": "Check", "label": "Send Document Print", "print_hide": 1 + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Company Billing Address", + "options": "Address" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address Details", + "read_only": 1 } ], "icon": "fa fa-shopping-cart", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-09 12:20:26.850623", + "modified": "2023-11-06 12:45:28.898706", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 7b635b36ba..ad1aa2b108 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -79,6 +79,7 @@ "pricing_rule_details", "pricing_rules", "address_and_contact_tab", + "supplier_address_section", "supplier_address", "address_display", "column_break_72", @@ -86,6 +87,14 @@ "contact_display", "contact_mobile", "contact_email", + "shipping_address_section", + "shipping_address", + "column_break_zjaq", + "shipping_address_display", + "company_billing_address_section", + "billing_address", + "column_break_gcth", + "billing_address_display", "terms_tab", "tc_name", "terms", @@ -838,6 +847,55 @@ "fieldname": "named_place", "fieldtype": "Data", "label": "Named Place" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "column_break_zjaq", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Small Text", + "label": "Shipping Address Details", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" + }, + { + "fieldname": "supplier_address_section", + "fieldtype": "Section Break", + "label": "Supplier Address" + }, + { + "fieldname": "company_billing_address_section", + "fieldtype": "Section Break", + "label": "Company Billing Address" + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Company Billing Address", + "options": "Address" + }, + { + "fieldname": "column_break_gcth", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address Details", + "read_only": 1 } ], "icon": "fa fa-shopping-cart", @@ -845,7 +903,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:20:15.880114", + "modified": "2023-11-03 13:21:40.172508", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 5c41aa0680..cba615c0d2 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -4,7 +4,7 @@ frappe.provide("erpnext.utils"); const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']; -const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; +const PURCHASE_DOCTYPES = ['Supplier Quotation','Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; erpnext.utils.get_party_details = function(frm, method, args, callback) { if (!method) { From 92f6d2c87cb54214d7afe41adcc3084dccce903c Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:57:11 +0100 Subject: [PATCH 048/163] chore: Update de.csv (#37810) chore: chore: Update de.csv --- erpnext/translations/de.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 61e301eed4..73755be653 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -5074,7 +5074,7 @@ Percentage you are allowed to transfer more against the quantity ordered. For ex PUR-ORD-.YYYY.-,PUR-ORD-.YYYY.-, Get Items from Open Material Requests,Hole Artikel von offenen Material Anfragen, Fetch items based on Default Supplier.,Abrufen von Elementen basierend auf dem Standardlieferanten., -Required By,Benötigt von, +Required By,Benötigt bis, Order Confirmation No,Auftragsbestätigung Nr, Order Confirmation Date,Auftragsbestätigungsdatum, Customer Mobile No,Mobilnummer des Kunden, From 45b4bfc94783bb0180c0a57a2f3a5bb7086008a3 Mon Sep 17 00:00:00 2001 From: jabir-elat <44110258+jabir-elat@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:30:00 +0530 Subject: [PATCH 049/163] fix: sales order not assigned to territory orders (#37905) filtered sales order are not assigned to 'territory_orders' which results in 0 order amount and 0 billing amount in the output --- .../selling/report/territory_wise_sales/territory_wise_sales.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py index 5dfc1db097..ecb63d890a 100644 --- a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py +++ b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py @@ -80,7 +80,7 @@ def get_data(filters=None): territory_orders = [] if t_quotation_names and sales_orders: - list(filter(lambda x: x.quotation in t_quotation_names, sales_orders)) + territory_orders = list(filter(lambda x: x.quotation in t_quotation_names, sales_orders)) t_order_names = [] if territory_orders: t_order_names = [t.name for t in territory_orders] From 1fc5844025d97de150195b94070b0c4061378992 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 9 Nov 2023 15:01:58 +0100 Subject: [PATCH 050/163] fix: Re-add no.of rows split in alert message --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 45bb434d65..fc22f53f4d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1699,8 +1699,8 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list continue frappe.msgprint( - _("Splitting {0} {1} as per Payment Terms").format( - _(entry.voucher_type), frappe.bold(entry.voucher_no) + _("Splitting {0} {1} into {2} rows as per Payment Terms").format( + _(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows) ), alert=True, ) From 922fffda1f4160aeeffdb576c76845f5bbd587dc Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 9 Nov 2023 17:34:27 +0100 Subject: [PATCH 051/163] fix(payments): incoming payment requests aren't supposed to be in 'initiated' state (only outgoing are) (#37447) * fix(payments): incoming payment requests aren't supposed to be in 'initiated' state (only outgoing are) * fixup! fix(payments): incoming payment requests aren't supposed to be in 'initiated' state (only outgoing are) --- .../doctype/payment_request/payment_request.py | 7 ------- erpnext/patches.txt | 1 + .../v15_0/migrate_payment_request_status.py | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v15_0/migrate_payment_request_status.py diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 5f0b434c70..c2e01c4ba3 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -175,13 +175,6 @@ class PaymentRequest(Document): if self.payment_url: self.db_set("payment_url", self.payment_url) - if ( - self.payment_url - or not self.payment_gateway_account - or (self.payment_gateway_account and self.payment_channel == "Phone") - ): - self.db_set("status", "Initiated") - def get_payment_url(self): if self.reference_doctype != "Fees": data = frappe.db.get_value( diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d394db6d96..0aeadce6c8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -338,6 +338,7 @@ erpnext.patches.v15_0.delete_woocommerce_settings_doctype erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults erpnext.patches.v14_0.update_invoicing_period_in_subscription execute:frappe.delete_doc("Page", "welcome-to-erpnext") +erpnext.patches.v15_0.migrate_payment_request_status erpnext.patches.v15_0.delete_payment_gateway_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item erpnext.patches.v15_0.update_sre_from_voucher_details diff --git a/erpnext/patches/v15_0/migrate_payment_request_status.py b/erpnext/patches/v15_0/migrate_payment_request_status.py new file mode 100644 index 0000000000..746a67bced --- /dev/null +++ b/erpnext/patches/v15_0/migrate_payment_request_status.py @@ -0,0 +1,15 @@ +import frappe + + +def execute(): + """ + Description: + Change Inward Payment Requests from statut 'Initiated' to correct status 'Requested'. + Status 'Initiated' is reserved for Outward Payment Requests and was a semantic error in previour versions. + """ + + if frappe.reload_doc("accounts", "doctype", "Payment Request"): + so = frappe.qb.DocType("Payment Request") + frappe.qb.update(so).set(so.status, "Requested").where( + so.payment_request_type == "Inward" + ).where(so.status == "Initiated").run() From 1d8fcd66e6ca6d17a274a8b18699b40a7ac93e1a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 09:49:55 +0530 Subject: [PATCH 052/163] fix: new logic for handling revaluation journals --- .../accounts_receivable/accounts_receivable.js | 10 ++++++++-- .../accounts_receivable/accounts_receivable.py | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 43ea532291..b4bc8870d3 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -180,13 +180,19 @@ frappe.query_reports["Accounts Receivable"] = { "fieldname": "in_party_currency", "label": __("In Party Currency"), "fieldtype": "Check", - }, - { + }, + { + "fieldname": "for_revaluation_journals", + "label": __("Revaluation Journals"), + "fieldtype": "Check", + }, + { "fieldname": "ignore_accounts", "label": __("Group by Voucher"), "fieldtype": "Check", } + ], "formatter": function(value, row, column, data, default_formatter) { diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 05403743fa..706d743849 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -283,11 +283,20 @@ class ReceivablePayableReport(object): row.invoice_grand_total = row.invoiced - if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( - (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) - or (row.voucher_no in self.err_journals) - ): + must_consider = False + if self.filters.get("for_revaluation_journals"): + if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) or ( + (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + ): + must_consider = True + else: + if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( + (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + or (row.voucher_no in self.err_journals) + ): + must_consider = True + if must_consider: # non-zero oustanding, we must consider this row if self.is_invoice(row) and self.filters.based_on_payment_terms: From 815c616f18ae1cd6ec1dbd315fa5f0376edef523 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 15:26:52 +0530 Subject: [PATCH 053/163] chore: remove 'Bulk Transaction Log' doctype --- .../doctype/bulk_transaction_log/__init__.py | 0 .../bulk_transaction_log.js | 30 ------- .../bulk_transaction_log.json | 51 ------------ .../bulk_transaction_log.py | 67 ---------------- .../test_bulk_transaction_log.py | 79 ------------------- 5 files changed, 227 deletions(-) delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js deleted file mode 100644 index 0073170a85..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Bulk Transaction Log', { - - refresh: function(frm) { - frm.disable_save(); - frm.add_custom_button(__('Retry Failed Transactions'), ()=>{ - frappe.confirm(__("Retry Failing Transactions ?"), ()=>{ - query(frm, 1); - } - ); - }); - } -}); - -function query(frm) { - frappe.call({ - method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", - args: { - log_date: frm.doc.log_date - } - }).then((r) => { - if (r.message === "No Failed Records") { - frappe.show_alert(__(r.message), 5); - } else { - frappe.show_alert(__("Retrying Failed Transactions"), 5); - } - }); -} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json deleted file mode 100644 index da42cf1bd4..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "creation": "2021-11-30 13:41:16.343827", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "log_date", - "logger_data" - ], - "fields": [ - { - "fieldname": "log_date", - "fieldtype": "Date", - "label": "Log Date", - "read_only": 1 - }, - { - "fieldname": "logger_data", - "fieldtype": "Table", - "label": "Logger Data", - "options": "Bulk Transaction Log Detail" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2022-02-03 17:23:02.935325", - "modified_by": "Administrator", - "module": "Bulk Transaction", - "name": "Bulk Transaction Log", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py deleted file mode 100644 index 0596be4462..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from datetime import date - -import frappe -from frappe.model.document import Document - -from erpnext.utilities.bulk_transaction import task, update_logger - - -class BulkTransactionLog(Document): - pass - - -@frappe.whitelist() -def retry_failing_transaction(log_date=None): - if not log_date: - log_date = str(date.today()) - btp = frappe.qb.DocType("Bulk Transaction Log Detail") - data = ( - frappe.qb.from_(btp) - .select(btp.transaction_name, btp.from_doctype, btp.to_doctype) - .distinct() - .where(btp.retried != 1) - .where(btp.transaction_status == "Failed") - .where(btp.date == log_date) - ).run(as_dict=True) - - if data: - if len(data) > 10: - frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date) - else: - job(data, log_date) - else: - return "No Failed Records" - - -def job(data, log_date): - for d in data: - failed = [] - try: - frappe.db.savepoint("before_creation_of_record") - task(d.transaction_name, d.from_doctype, d.to_doctype) - except Exception as e: - frappe.db.rollback(save_point="before_creation_of_record") - failed.append(e) - update_logger( - d.transaction_name, - e, - d.from_doctype, - d.to_doctype, - status="Failed", - log_date=log_date, - restarted=1, - ) - - if not failed: - update_logger( - d.transaction_name, - None, - d.from_doctype, - d.to_doctype, - status="Success", - log_date=log_date, - restarted=1, - ) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py deleted file mode 100644 index c673be89b3..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest -from datetime import date - -import frappe - -from erpnext.utilities.bulk_transaction import transaction_processing - - -class TestBulkTransactionLog(unittest.TestCase): - def setUp(self): - create_company() - create_customer() - create_item() - - def test_entry_in_log(self): - so_name = create_so() - transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice") - doc = frappe.get_doc("Bulk Transaction Log", str(date.today())) - for d in doc.get("logger_data"): - if d.transaction_name == so_name: - self.assertEqual(d.transaction_name, so_name) - self.assertEqual(d.transaction_status, "Success") - self.assertEqual(d.from_doctype, "Sales Order") - self.assertEqual(d.to_doctype, "Sales Invoice") - self.assertEqual(d.retried, 0) - - -def create_company(): - if not frappe.db.exists("Company", "_Test Company"): - frappe.get_doc( - { - "doctype": "Company", - "company_name": "_Test Company", - "country": "India", - "default_currency": "INR", - } - ).insert() - - -def create_customer(): - if not frappe.db.exists("Customer", "Bulk Customer"): - frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert() - - -def create_item(): - if not frappe.db.exists("Item", "MK"): - frappe.get_doc( - { - "doctype": "Item", - "item_code": "MK", - "item_name": "Milk", - "description": "Milk", - "item_group": "Products", - } - ).insert() - - -def create_so(intent=None): - so = frappe.new_doc("Sales Order") - so.customer = "Bulk Customer" - so.company = "_Test Company" - so.transaction_date = date.today() - - so.set_warehouse = "Finished Goods - _TC" - so.append( - "items", - { - "item_code": "MK", - "delivery_date": date.today(), - "qty": 10, - "rate": 80, - }, - ) - so.insert() - so.submit() - return so.name From e5a8ad54e26d74b5713e7db057860c1262c1b93b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 15:29:57 +0530 Subject: [PATCH 054/163] chore: convert child to normal table --- .../bulk_transaction_log_detail.js | 8 ++++++++ .../bulk_transaction_log_detail.json | 18 +++++++++++++++--- .../test_bulk_transaction_log_detail.py | 9 +++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js new file mode 100644 index 0000000000..5669601d11 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Bulk Transaction Log Detail", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 8262caa020..64518de4c0 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -71,14 +71,26 @@ } ], "index_web_pages_for_search": 1, - "istable": 1, "links": [], - "modified": "2022-02-03 19:57:31.650359", + "modified": "2023-11-09 15:29:33.013547", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], "sort_field": "modified", "sort_order": "DESC", "states": [], diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py new file mode 100644 index 0000000000..5217b601f8 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBulkTransactionLogDetail(FrappeTestCase): + pass From c4f8f3613f1173a54b17e3c6ca8ea52469f32e3b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 16:07:45 +0530 Subject: [PATCH 055/163] chore: rearrange fields --- .../bulk_transaction_log_detail.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 64518de4c0..051dc0af02 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -6,12 +6,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "from_doctype", "transaction_name", "date", "time", "transaction_status", "error_description", - "from_doctype", "to_doctype", "retried" ], @@ -72,7 +72,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 15:29:33.013547", + "modified": "2023-11-09 15:33:26.828998", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", From ebd74a4e5b68534a4ceab2482618e16643f220d1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 16:38:34 +0530 Subject: [PATCH 056/163] refactor: simplify logging logic bulk_transaction --- erpnext/utilities/bulk_transaction.py | 74 +++++---------------------- 1 file changed, 14 insertions(+), 60 deletions(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index fcee265644..fd8e722648 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -3,6 +3,7 @@ from datetime import date, datetime import frappe from frappe import _ +from frappe.utils import today @frappe.whitelist() @@ -38,7 +39,7 @@ def job(deserialized_data, from_doctype, to_doctype): except Exception as e: frappe.db.rollback(save_point="before_creation_state") fail_count += 1 - update_logger( + create_log( doc_name, str(frappe.get_traceback()), from_doctype, @@ -47,7 +48,7 @@ def job(deserialized_data, from_doctype, to_doctype): log_date=str(date.today()), ) else: - update_logger( + create_log( doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today()) ) @@ -108,45 +109,18 @@ def task(doc_name, from_doctype, to_doctype): obj.insert(ignore_mandatory=True) -def check_logger_doc_exists(log_date): - return frappe.db.exists("Bulk Transaction Log", log_date) - - -def get_logger_doc(log_date): - return frappe.get_doc("Bulk Transaction Log", log_date) - - -def create_logger_doc(): - log_doc = frappe.new_doc("Bulk Transaction Log") - log_doc.set_new_name(set_name=str(date.today())) - log_doc.log_date = date.today() - - return log_doc - - -def append_data_to_logger(log_doc, doc_name, error, from_doctype, to_doctype, status, restarted): - row = log_doc.append("logger_data", {}) - row.transaction_name = doc_name - row.date = date.today() +def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): + transaction_log = frappe.new_doc("Bulk Transaction Log Detail") + transaction_log.transaction_name = doc_name + transaction_log.date = today() now = datetime.now() - row.time = now.strftime("%H:%M:%S") - row.transaction_status = status - row.error_description = str(error) - row.from_doctype = from_doctype - row.to_doctype = to_doctype - row.retried = restarted - - -def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): - if not check_logger_doc_exists(log_date): - log_doc = create_logger_doc() - append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted) - log_doc.insert() - else: - log_doc = get_logger_doc(log_date) - if record_exists(log_doc, doc_name, status): - append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted) - log_doc.save() + transaction_log.time = now.strftime("%H:%M:%S") + transaction_log.transaction_status = status + transaction_log.error_description = str(e) + transaction_log.from_doctype = from_doctype + transaction_log.to_doctype = to_doctype + transaction_log.retried = restarted + transaction_log.save() def show_job_status(fail_count, deserialized_data_count, to_doctype): @@ -176,23 +150,3 @@ def show_job_status(fail_count, deserialized_data_count, to_doctype): title="Failed", indicator="red", ) - - -def record_exists(log_doc, doc_name, status): - record = mark_retrired_transaction(log_doc, doc_name) - if record and status == "Failed": - return False - elif record and status == "Success": - return True - else: - return True - - -def mark_retrired_transaction(log_doc, doc_name): - record = 0 - for d in log_doc.get("logger_data"): - if d.transaction_name == doc_name and d.transaction_status == "Failed": - frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1) - record = record + 1 - - return record From ade09bc709ab8bca047abdad5235461a4618bc17 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 17:16:58 +0530 Subject: [PATCH 057/163] chore: add list view filters --- .../bulk_transaction_log_detail.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 051dc0af02..3a5d434a04 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -20,6 +20,7 @@ "fieldname": "transaction_name", "fieldtype": "Dynamic Link", "in_list_view": 1, + "in_standard_filter": 1, "label": "Name", "options": "from_doctype" }, @@ -39,6 +40,7 @@ { "fieldname": "from_doctype", "fieldtype": "Link", + "in_standard_filter": 1, "label": "From Doctype", "options": "DocType", "read_only": 1 @@ -54,6 +56,7 @@ "fieldname": "date", "fieldtype": "Date", "in_list_view": 1, + "in_standard_filter": 1, "label": "Date ", "read_only": 1 }, @@ -70,9 +73,10 @@ "read_only": 1 } ], + "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 15:33:26.828998", + "modified": "2023-11-09 16:55:31.026064", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", From 73090fa1306fd65ff8e290d57486697af5722081 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 17:40:04 +0530 Subject: [PATCH 058/163] chore: add indexes --- .../bulk_transaction_log_detail.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 3a5d434a04..fe1a6d9b56 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -22,7 +22,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Name", - "options": "from_doctype" + "options": "from_doctype", + "search_index": 1 }, { "fieldname": "transaction_status", @@ -43,7 +44,8 @@ "in_standard_filter": 1, "label": "From Doctype", "options": "DocType", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "to_doctype", @@ -58,7 +60,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Date ", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "time", @@ -76,7 +79,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 16:55:31.026064", + "modified": "2023-11-09 17:44:14.804836", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", @@ -99,4 +102,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From b0dfc936a10aef4dfd743959a6cf5e47ea69632e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 17:43:04 +0530 Subject: [PATCH 059/163] chore: make `from_doctype` readonly --- .../bulk_transaction_log_detail/bulk_transaction_log_detail.json | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index fe1a6d9b56..5491f56a1a 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -23,6 +23,7 @@ "in_standard_filter": 1, "label": "Name", "options": "from_doctype", + "read_only": 1, "search_index": 1 }, { From a248b13cc353fcd94b0009c74c24512ea629a376 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 20:15:33 +0530 Subject: [PATCH 060/163] feat: virtual parent doctype --- .../doctype/bulk_transaction_log/__init__.py | 0 .../bulk_transaction_log.js | 8 ++ .../bulk_transaction_log.json | 80 +++++++++++++++++++ .../bulk_transaction_log.py | 28 +++++++ .../test_bulk_transaction_log.py | 9 +++ 5 files changed, 125 insertions(+) create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js new file mode 100644 index 0000000000..07a8009816 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Bulk Transaction Log", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json new file mode 100644 index 0000000000..a874b791ca --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json @@ -0,0 +1,80 @@ +{ + "actions": [], + "allow_copy": 1, + "creation": "2023-11-09 20:14:45.139593", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "date", + "column_break_bsan", + "log_entries", + "section_break_mdmv", + "succeeded", + "column_break_qryp", + "failed" + ], + "fields": [ + { + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "read_only": 1 + }, + { + "fieldname": "log_entries", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Log Entries", + "read_only": 1 + }, + { + "fieldname": "column_break_bsan", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_mdmv", + "fieldtype": "Section Break" + }, + { + "fieldname": "succeeded", + "fieldtype": "Int", + "label": "Succeeded", + "read_only": 1 + }, + { + "fieldname": "column_break_qryp", + "fieldtype": "Column Break" + }, + { + "fieldname": "failed", + "fieldtype": "Int", + "label": "Failed", + "read_only": 1 + } + ], + "in_create": 1, + "is_virtual": 1, + "links": [], + "modified": "2023-11-10 11:13:52.733076", + "modified_by": "Administrator", + "module": "Bulk Transaction", + "name": "Bulk Transaction Log", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py new file mode 100644 index 0000000000..fb9fcf7c52 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -0,0 +1,28 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class BulkTransactionLog(Document): + def db_insert(self, *args, **kwargs): + pass + + def load_from_db(self): + pass + + def db_update(self): + pass + + @staticmethod + def get_list(args): + pass + + @staticmethod + def get_count(args): + pass + + @staticmethod + def get_stats(args): + pass diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py new file mode 100644 index 0000000000..01bb615a3e --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBulkTransactionLog(FrappeTestCase): + pass From af35590549987ab84eadbf08c10f3837242203dc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 21:07:01 +0530 Subject: [PATCH 061/163] refactor: add basic functionalities --- .../bulk_transaction_log.js | 15 ++-- .../bulk_transaction_log.py | 71 +++++++++++++++++-- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 07a8009816..218424beca 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -1,8 +1,13 @@ // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("Bulk Transaction Log", { -// refresh(frm) { - -// }, -// }); +frappe.ui.form.on("Bulk Transaction Log", { + refresh(frm) { + frm.add_custom_button(__('Succeeded Entries'), function() { + frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"}); + }, __("View")); + frm.add_custom_button(__('Failed Entries'), function() { + frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"}); + }, __("View")); + }, +}); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index fb9fcf7c52..4febb48a60 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -1,8 +1,14 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe +from frappe import qb from frappe.model.document import Document +from frappe.query_builder.functions import Count +from frappe.utils import cint +from pypika import Order + +log_detail = qb.DocType("Bulk Transaction Log Detail") class BulkTransactionLog(Document): @@ -10,14 +16,51 @@ class BulkTransactionLog(Document): pass def load_from_db(self): - pass - - def db_update(self): - pass + succeeded_logs = ( + qb.from_(log_detail) + .select(Count(log_detail.date).as_("count")) + .where((log_detail.date == self.name) & (log_detail.transaction_status == "Success")) + .run() + )[0][0] or 0 + failed_logs = ( + qb.from_(log_detail) + .select(Count(log_detail.date).as_("count")) + .where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed")) + .run() + )[0][0] or 0 + total_logs = succeeded_logs + failed_logs + transaction_log = frappe._dict( + { + "date": self.name, + "count": total_logs, + "succeeded": succeeded_logs, + "failed": failed_logs, + } + ) + super(Document, self).__init__(serialize_transaction_log(transaction_log)) @staticmethod def get_list(args): - pass + limit = cint(args.get("page_length")) or 20 + dates = ( + qb.from_(log_detail) + .select(log_detail.date) + .distinct() + .orderby(log_detail.date, order=Order.desc) + .limit(limit) + .run() + ) + + transaction_logs = ( + qb.from_(log_detail) + .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) + .where(log_detail.date.isin(dates)) + .orderby(log_detail.date, order=Order.desc) + .groupby(log_detail.date) + .limit(limit) + .run(as_dict=True) + ) + return [serialize_transaction_log(x) for x in transaction_logs] @staticmethod def get_count(args): @@ -26,3 +69,19 @@ class BulkTransactionLog(Document): @staticmethod def get_stats(args): pass + + def db_update(self, *args, **kwargs): + pass + + def delete(self): + pass + + +def serialize_transaction_log(data): + return frappe._dict( + name=data.date, + date=data.date, + log_entries=data.count, + succeeded=data.succeeded, + failed=data.failed, + ) From 194d70f8a0c93c8e2952cd58cdc5d994414198fb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 11:44:25 +0530 Subject: [PATCH 062/163] chore: show retried in list view --- .../bulk_transaction_log_detail.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 5491f56a1a..9590325a06 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -73,6 +73,7 @@ { "fieldname": "retried", "fieldtype": "Int", + "in_list_view": 1, "label": "Retried", "read_only": 1 } @@ -80,7 +81,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 17:44:14.804836", + "modified": "2023-11-10 11:44:10.758342", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", @@ -103,4 +104,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file From 0aa1636d04bed9fef260aedde4ed6a42b6a1968b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 12:20:30 +0530 Subject: [PATCH 063/163] refactor: barebones retry functionality --- .../bulk_transaction_log.js | 8 ++++++ erpnext/utilities/bulk_transaction.py | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 218424beca..6e2250052d 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -9,5 +9,13 @@ frappe.ui.form.on("Bulk Transaction Log", { frm.add_custom_button(__('Failed Entries'), function() { frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"}); }, __("View")); + if (frm.doc.failed) { + frm.add_custom_button(__('Retry Failed Transactions'), function() { + frappe.call({ + method: "erpnext.utilities.bulk_transaction.retry_failed_transactions", + args: {date: frm.doc.date} + }).then(()=> { }); + }); + } }, }); diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index fd8e722648..a596001583 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -29,6 +29,34 @@ def transaction_processing(data, from_doctype, to_doctype): job(deserialized_data, from_doctype, to_doctype) +@frappe.whitelist() +def retry_failed_transactions(date: str | None): + if date: + failed_docs = frappe.db.get_all( + "Bulk Transaction Log Detail", + filters={"date": date, "transaction_status": "Failed", "retried": 0}, + fields=["name", "transaction_name", "from_doctype", "to_doctype"], + ) + if not failed_docs: + frappe.msgprint("There are no Failed transactions") + return + + for log in failed_docs: + try: + frappe.db.savepoint("before_creation_state") + task(log.transaction_name, log.from_doctype, log.to_doctype) + except Exception as e: + frappe.db.rollback(save_point="before_creation_state") + update_log(log.name, "Failed", 1) + else: + update_log(log.name, "Success", 1) + + +def update_log(log_name, status, retried): + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status) + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried) + + def job(deserialized_data, from_doctype, to_doctype): fail_count = 0 for d in deserialized_data: From c3202886901eaecc0ab8b9bf73d8816994e6dc1f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 13:45:52 +0530 Subject: [PATCH 064/163] refactor: rollback for retries and UI alerts --- .../bulk_transaction_log.js | 2 +- erpnext/utilities/bulk_transaction.py | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 6e2250052d..3135d41cc1 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -12,7 +12,7 @@ frappe.ui.form.on("Bulk Transaction Log", { if (frm.doc.failed) { frm.add_custom_button(__('Retry Failed Transactions'), function() { frappe.call({ - method: "erpnext.utilities.bulk_transaction.retry_failed_transactions", + method: "erpnext.utilities.bulk_transaction.retry", args: {date: frm.doc.date} }).then(()=> { }); }); diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index a596001583..7e73c2fe0b 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -3,7 +3,7 @@ from datetime import date, datetime import frappe from frappe import _ -from frappe.utils import today +from frappe.utils import get_link_to_form, today @frappe.whitelist() @@ -30,7 +30,7 @@ def transaction_processing(data, from_doctype, to_doctype): @frappe.whitelist() -def retry_failed_transactions(date: str | None): +def retry(date: str | None): if date: failed_docs = frappe.db.get_all( "Bulk Transaction Log Detail", @@ -38,9 +38,21 @@ def retry_failed_transactions(date: str | None): fields=["name", "transaction_name", "from_doctype", "to_doctype"], ) if not failed_docs: - frappe.msgprint("There are no Failed transactions") - return + frappe.msgprint(_("There are no Failed transactions")) + else: + job = frappe.enqueue( + retry_failed_transactions, + failed_docs=failed_docs, + ) + frappe.msgprint( + _("Job: {0} has been triggered for processing failed transactions").format( + get_link_to_form("RQ Job", job.id) + ) + ) + +def retry_failed_transactions(failed_docs: list | None): + if failed_docs: for log in failed_docs: try: frappe.db.savepoint("before_creation_state") From 73639db9105260d0d1af3cea35caa03523f396e5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 15:27:28 +0530 Subject: [PATCH 065/163] chore: resolve linting issue --- .../doctype/bulk_transaction_log/bulk_transaction_log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 4febb48a60..1a078b53d5 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -8,14 +8,13 @@ from frappe.query_builder.functions import Count from frappe.utils import cint from pypika import Order -log_detail = qb.DocType("Bulk Transaction Log Detail") - class BulkTransactionLog(Document): def db_insert(self, *args, **kwargs): pass def load_from_db(self): + log_detail = qb.DocType("Bulk Transaction Log Detail") succeeded_logs = ( qb.from_(log_detail) .select(Count(log_detail.date).as_("count")) @@ -41,6 +40,7 @@ class BulkTransactionLog(Document): @staticmethod def get_list(args): + log_detail = qb.DocType("Bulk Transaction Log Detail") limit = cint(args.get("page_length")) or 20 dates = ( qb.from_(log_detail) From 93295bf25b67069936e23d03f5a1c559294bc25f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 11 Nov 2023 05:10:16 +0530 Subject: [PATCH 066/163] refactor: support list view filters --- .../bulk_transaction_log.json | 8 +++- .../bulk_transaction_log.py | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json index a874b791ca..75cb358ff2 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json @@ -2,6 +2,7 @@ "actions": [], "allow_copy": 1, "creation": "2023-11-09 20:14:45.139593", + "default_view": "List", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -17,6 +18,8 @@ { "fieldname": "date", "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Date", "read_only": 1 }, @@ -55,7 +58,7 @@ "in_create": 1, "is_virtual": 1, "links": [], - "modified": "2023-11-10 11:13:52.733076", + "modified": "2023-11-11 04:52:49.347376", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log", @@ -76,5 +79,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "title_field": "date" } \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 1a078b53d5..8ae54ddab8 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -40,26 +40,33 @@ class BulkTransactionLog(Document): @staticmethod def get_list(args): - log_detail = qb.DocType("Bulk Transaction Log Detail") + filter_date = parse_list_filters(args) limit = cint(args.get("page_length")) or 20 - dates = ( + log_detail = qb.DocType("Bulk Transaction Log Detail") + + dates_query = ( qb.from_(log_detail) .select(log_detail.date) .distinct() .orderby(log_detail.date, order=Order.desc) .limit(limit) - .run() ) + if filter_date: + dates_query = dates_query.where(log_detail.date == filter_date) + dates = dates_query.run() + + transaction_logs = [] + if dates: + transaction_logs_query = ( + qb.from_(log_detail) + .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) + .where(log_detail.date.isin(dates)) + .orderby(log_detail.date, order=Order.desc) + .groupby(log_detail.date) + .limit(limit) + ) + transaction_logs = transaction_logs_query.run(as_dict=True) - transaction_logs = ( - qb.from_(log_detail) - .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) - .where(log_detail.date.isin(dates)) - .orderby(log_detail.date, order=Order.desc) - .groupby(log_detail.date) - .limit(limit) - .run(as_dict=True) - ) return [serialize_transaction_log(x) for x in transaction_logs] @staticmethod @@ -85,3 +92,14 @@ def serialize_transaction_log(data): succeeded=data.succeeded, failed=data.failed, ) + + +def parse_list_filters(args): + # parse date filter + filter_date = None + for fil in args.get("filters"): + if isinstance(fil, list): + for elem in fil: + if elem == "date": + filter_date = fil[3] + return filter_date From a52a1b49af1c7b72ba85563057f60bb7cf54e81c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 11 Nov 2023 05:20:27 +0530 Subject: [PATCH 067/163] refactor: update traceback on retry --- erpnext/utilities/bulk_transaction.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 7e73c2fe0b..402c305ca9 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -59,14 +59,16 @@ def retry_failed_transactions(failed_docs: list | None): task(log.transaction_name, log.from_doctype, log.to_doctype) except Exception as e: frappe.db.rollback(save_point="before_creation_state") - update_log(log.name, "Failed", 1) + update_log(log.name, "Failed", 1, str(frappe.get_traceback())) else: update_log(log.name, "Success", 1) -def update_log(log_name, status, retried): +def update_log(log_name, status, retried, err=None): frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status) frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried) + if err: + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "error_description", err) def job(deserialized_data, from_doctype, to_doctype): From ae508144cdce348af11c406f6fd30e73f4dc3879 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 11 Nov 2023 15:05:07 +0100 Subject: [PATCH 068/163] fix(customer): quick form and integration fixes (#37386) --- erpnext/selling/doctype/customer/customer.py | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index a7a1aa2659..9e6095fac3 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -638,7 +638,7 @@ def make_contact(args, is_primary_contact=1): contact = frappe.get_doc( { "doctype": "Contact", - "first_name": args.get("name"), + "first_name": args.get("customer_name"), "is_primary_contact": is_primary_contact, "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], } @@ -647,12 +647,16 @@ def make_contact(args, is_primary_contact=1): contact.add_email(args.get("email_id"), is_primary=True) if args.get("mobile_no"): contact.add_phone(args.get("mobile_no"), is_primary_mobile_no=True) - contact.insert() + + if flags := args.get("flags"): + contact.insert(ignore_permissions=flags.get("ignore_permissions")) + else: + contact.insert() return contact -def make_address(args, is_primary_address=1): +def make_address(args, is_primary_address=1, is_shipping_address=1): reqd_fields = [] for field in ["city", "country"]: if not args.get(field): @@ -668,16 +672,23 @@ def make_address(args, is_primary_address=1): address = frappe.get_doc( { "doctype": "Address", - "address_title": args.get("name"), + "address_title": args.get("customer_name"), "address_line1": args.get("address_line1"), "address_line2": args.get("address_line2"), "city": args.get("city"), "state": args.get("state"), "pincode": args.get("pincode"), "country": args.get("country"), + "is_primary_address": is_primary_address, + "is_shipping_address": is_shipping_address, "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], } - ).insert() + ) + + if flags := args.get("flags"): + address.insert(ignore_permissions=flags.get("ignore_permissions")) + else: + address.insert() return address From 4ed9927a304fa88d89c8eaaea2810eb61ee3956e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 11 Nov 2023 15:07:23 +0100 Subject: [PATCH 069/163] fix(patch): to migrate properly in post section (#38045) --- .../patches/v15_0/migrate_payment_request_status.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/patches/v15_0/migrate_payment_request_status.py b/erpnext/patches/v15_0/migrate_payment_request_status.py index 746a67bced..9f0de56621 100644 --- a/erpnext/patches/v15_0/migrate_payment_request_status.py +++ b/erpnext/patches/v15_0/migrate_payment_request_status.py @@ -7,9 +7,7 @@ def execute(): Change Inward Payment Requests from statut 'Initiated' to correct status 'Requested'. Status 'Initiated' is reserved for Outward Payment Requests and was a semantic error in previour versions. """ - - if frappe.reload_doc("accounts", "doctype", "Payment Request"): - so = frappe.qb.DocType("Payment Request") - frappe.qb.update(so).set(so.status, "Requested").where( - so.payment_request_type == "Inward" - ).where(so.status == "Initiated").run() + so = frappe.qb.DocType("Payment Request") + frappe.qb.update(so).set(so.status, "Requested").where(so.payment_request_type == "Inward").where( + so.status == "Initiated" + ).run() From 59438ee8d48c0feee03d0d97852c9a9bb359c019 Mon Sep 17 00:00:00 2001 From: hyaray Date: Sat, 11 Nov 2023 22:08:24 +0800 Subject: [PATCH 070/163] chore: Add missing translations (#38043) chore: Add missing translations --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b0a9e405cd..2c40f4964b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1772,7 +1772,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && !["Purchase Order","Purchase Invoice"].includes(me.frm.doc.doctype)) { if (!me.frm.doc[fieldname]) { frappe.msgprint(__("Please specify") + ": " + - frappe.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name) + + __(frappe.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name)) + ". " + __("It is needed to fetch Item Details.")); valid = false; } From a74e1f1600c1c6e3a7ea18b9bf6a0e70db86311a Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 11 Nov 2023 15:50:21 +0100 Subject: [PATCH 071/163] fix(crm): ensure primary address and contact follows customer setting (#37710) Co-authored-by: Deepesh Garg --- erpnext/crm/doctype/lead/lead.py | 9 +++++++++ erpnext/selling/doctype/customer/customer.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index e897ba41eb..fdec88d70d 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -7,6 +7,8 @@ from frappe.contacts.address_and_contact import ( delete_contact_and_address, load_address_and_contact, ) +from frappe.contacts.doctype.address.address import get_default_address +from frappe.contacts.doctype.contact.contact import get_default_contact from frappe.email.inbox import link_communication_to_document from frappe.model.mapper import get_mapped_doc from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address @@ -251,6 +253,13 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False): target.customer_group = frappe.db.get_default("Customer Group") + address = get_default_address("Lead", source.name) + contact = get_default_contact("Lead", source.name) + if address: + target.customer_primary_address = address + if contact: + target.customer_primary_contact = contact + doclist = get_mapped_doc( "Lead", source_name, diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 9e6095fac3..a4bb9d5381 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -186,6 +186,8 @@ class Customer(TransactionBase): self.db_set("customer_primary_contact", contact.name) self.db_set("mobile_no", self.mobile_no) self.db_set("email_id", self.email_id) + elif self.customer_primary_contact: + frappe.set_value("Contact", self.customer_primary_contact, "is_primary_contact", 1) # ensure def create_primary_address(self): from frappe.contacts.doctype.address.address import get_address_display @@ -196,6 +198,8 @@ class Customer(TransactionBase): self.db_set("customer_primary_address", address.name) self.db_set("primary_address", address_display) + elif self.customer_primary_address: + frappe.set_value("Address", self.customer_primary_address, "is_primary_address", 1) # ensure def update_lead_status(self): """If Customer created from Lead, update lead status to "Converted" @@ -312,6 +316,7 @@ def create_contact(contact, party_type, party, email): "doctype": "Contact", "first_name": contact[0], "last_name": len(contact) > 1 and contact[1] or "", + "is_primary_contact": 1, } ) contact.append("email_ids", dict(email_id=email, is_primary=1)) From 8634abc0210fdb259c8b0e7ffc93976ea0099057 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 12 Nov 2023 11:42:26 +0530 Subject: [PATCH 072/163] fix: COA Importer app related issues (#37238) * fix: COA Importer app related issues * fix: Clear all account links fields befor import * fix: Attribute error --- .../chart_of_accounts_importer.js | 15 ++++-- .../chart_of_accounts_importer.py | 52 ++++++------------- .../coa_sample_template.csv | 17 ++++++ 3 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 erpnext/accounts/doctype/chart_of_accounts_importer/coa_sample_template.csv diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index d61f8a6c01..56fa6ce2f3 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -53,10 +53,18 @@ frappe.ui.form.on('Chart of Accounts Importer', { of Accounts. Please enter the account names and add more rows as per your requirement.`); } } - } + }, + { + label : "Company", + fieldname: "company", + fieldtype: "Link", + reqd: 1, + hidden: 1, + default: frm.doc.company, + }, ], primary_action: function() { - var data = d.get_values(); + let data = d.get_values(); if (!data.template_type) { frappe.throw(__('Please select Template Type to download template')); @@ -66,7 +74,8 @@ frappe.ui.form.on('Chart of Accounts Importer', { '/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template', { file_type: data.file_type, - template_type: data.template_type + template_type: data.template_type, + company: data.company } ); diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index d6e1be4123..5a1c139bde 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -8,6 +8,7 @@ from functools import reduce import frappe from frappe import _ +from frappe.desk.form.linked_with import get_linked_fields from frappe.model.document import Document from frappe.utils import cint, cstr from frappe.utils.csvutils import UnicodeWriter @@ -294,10 +295,8 @@ def build_response_as_excel(writer): @frappe.whitelist() -def download_template(file_type, template_type): - data = frappe._dict(frappe.local.form_dict) - - writer = get_template(template_type) +def download_template(file_type, template_type, company): + writer = get_template(template_type, company) if file_type == "CSV": # download csv file @@ -308,8 +307,7 @@ def download_template(file_type, template_type): build_response_as_excel(writer) -def get_template(template_type): - +def get_template(template_type, company): fields = [ "Account Name", "Parent Account", @@ -335,34 +333,17 @@ def get_template(template_type): ["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")] ) else: - writer = get_sample_template(writer) + writer = get_sample_template(writer, company) return writer -def get_sample_template(writer): - template = [ - ["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"], - ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], - ["Equity", "", "", "", 1, "", "Equity"], - ["Expenses", "", "", "", 1, "", "Expense"], - ["Income", "", "", "", 1, "", "Income"], - ["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"], - ["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"], - ["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"], - ["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"], - ["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"], - ["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"], - ["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"], - ["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"], - ["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"], - ["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"], - ["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"], - ["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"], - ] - - for row in template: - writer.writerow(row) +def get_sample_template(writer, company): + currency = frappe.db.get_value("Company", company, "default_currency") + with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f: + for row in f: + row = row.strip().split(",") + [currency] + writer.writerow(row) return writer @@ -453,14 +434,11 @@ def get_mandatory_account_types(): def unset_existing_data(company): - linked = frappe.db.sql( - '''select fieldname from tabDocField - where fieldtype="Link" and options="Account" and parent="Company"''', - as_dict=True, - ) - # remove accounts data from company - update_values = {d.fieldname: "" for d in linked} + + fieldnames = get_linked_fields("Account").get("Company", {}).get("fieldname", []) + linked = [{"fieldname": name} for name in fieldnames] + update_values = {d.get("fieldname"): "" for d in linked} frappe.db.set_value("Company", company, update_values, update_values) # remove accounts data from various doctypes diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/coa_sample_template.csv b/erpnext/accounts/doctype/chart_of_accounts_importer/coa_sample_template.csv new file mode 100644 index 0000000000..85a2f2112f --- /dev/null +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/coa_sample_template.csv @@ -0,0 +1,17 @@ +Application Of Funds(Assets),,,,1,,Asset +Sources Of Funds(Liabilities),,,,1,,Liability +Equity,,,,1,,Equity +Expenses,,,,1,Expense Account,Expense +Income,,,,1,Income Account,Income +Bank Accounts,Application Of Funds(Assets),,,1,Bank,Asset +Cash In Hand,Application Of Funds(Assets),,,1,Cash,Asset +Stock Assets,Application Of Funds(Assets),,,1,Stock,Asset +Cost Of Goods Sold,Expenses,,,0,Cost of Goods Sold,Expense +Asset Depreciation,Expenses,,,0,Depreciation,Expense +Fixed Assets,Application Of Funds(Assets),,,0,Fixed Asset,Asset +Accounts Payable,Sources Of Funds(Liabilities),,,0,Payable,Liability +Accounts Receivable,Application Of Funds(Assets),,,1,Receivable,Asset +Stock Expenses,Expenses,,,0,Stock Adjustment,Expense +Sample Bank,Bank Accounts,,,0,Bank,Asset +Cash,Cash In Hand,,,0,Cash,Asset +Stores,Stock Assets,,,0,Stock,Asset \ No newline at end of file From 6f6d5cb4cffebefa8754b62ec0264c4aadc97cda Mon Sep 17 00:00:00 2001 From: Niraj Gautam Date: Sun, 12 Nov 2023 11:52:35 +0530 Subject: [PATCH 073/163] fix(POS): 100 % Discount on Point of Sales (#37411) fix: Allow 100% discount in POS --- erpnext/selling/page/point_of_sale/pos_controller.js | 8 ++++++++ erpnext/selling/page/point_of_sale/pos_payment.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index db6255a4be..9bba4ebf50 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -548,6 +548,14 @@ erpnext.PointOfSale.Controller = class { if (!item_code) return; + if (rate == undefined || rate == 0) { + frappe.show_alert({ + message: __('Price is not set for the item.'), + indicator: 'orange' + }); + frappe.utils.play_sound("error"); + return; + } const new_item = { item_code, batch_no, rate, uom, [field]: value }; if (serial_no) { diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 89ce61ab16..63711c5ed2 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -203,7 +203,7 @@ erpnext.PointOfSale.Payment = class { const paid_amount = doc.paid_amount; const items = doc.items; - if (paid_amount == 0 || !items.length) { + if (!items.length || (paid_amount == 0 && doc.additional_discount_percentage != 100)) { const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order."); frappe.show_alert({ message, indicator: "orange" }); frappe.utils.play_sound("error"); From ecc305dd5929fbe1624e499cc355dd6058e21102 Mon Sep 17 00:00:00 2001 From: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:48:37 +0530 Subject: [PATCH 074/163] fix: add customer name and supplier name columns (#37557) fix: add customer name and supplier name columns --- .../address_and_contacts.py | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.py b/erpnext/selling/report/address_and_contacts/address_and_contacts.py index 4542bdff43..0a29d435a4 100644 --- a/erpnext/selling/report/address_and_contacts/address_and_contacts.py +++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.py @@ -26,7 +26,7 @@ def execute(filters=None): def get_columns(filters): party_type = filters.get("party_type") party_type_value = get_party_group(party_type) - return [ + columns = [ "{party_type}:Link/{party_type}".format(party_type=party_type), "{party_value_type}::150".format(party_value_type=frappe.unscrub(str(party_type_value))), "Address Line 1", @@ -43,6 +43,15 @@ def get_columns(filters): "Email Id", "Is Primary Contact:Check", ] + if filters.get("party_type") == "Supplier" and frappe.db.get_single_value( + "Buying Settings", "supp_master_name" + ) == ["Naming Series", "Auto Name"]: + columns.insert(1, "Supplier Name:Data:150") + if filters.get("party_type") == "Customer" and frappe.db.get_single_value( + "Selling Settings", "cust_master_name" + ) == ["Naming Series", "Auto Name"]: + columns.insert(1, "Customer Name:Data:150") + return columns def get_data(filters): @@ -50,27 +59,33 @@ def get_data(filters): party = filters.get("party_name") party_group = get_party_group(party_type) - return get_party_addresses_and_contact(party_type, party, party_group) + return get_party_addresses_and_contact(party_type, party, party_group, filters) -def get_party_addresses_and_contact(party_type, party, party_group): +def get_party_addresses_and_contact(party_type, party, party_group, filters): data = [] - filters = None + query_filters = None party_details = frappe._dict() if not party_type: return [] if party: - filters = {"name": party} + query_filters = {"name": party} + if filters.get("party_type") in ["Customer", "Supplier"]: + field = filters.get("party_type").lower() + "_name" + else: + field = "partner_name" fetch_party_list = frappe.get_list( - party_type, filters=filters, fields=["name", party_group], as_list=True + party_type, filters=query_filters, fields=["name", party_group, field], as_list=True ) party_list = [d[0] for d in fetch_party_list] party_groups = {} + party_name_map = {} for d in fetch_party_list: party_groups[d[0]] = d[1] + party_name_map[d[0]] = d[2] for d in party_list: party_details.setdefault(d, frappe._dict()) @@ -84,6 +99,8 @@ def get_party_addresses_and_contact(party_type, party, party_group): if not any([addresses, contacts]): result = [party] result.append(party_groups[party]) + if filters.get("party_type") in ["Customer", "Supplier"]: + result.append(party_name_map[party]) result.extend(add_blank_columns_for("Contact")) result.extend(add_blank_columns_for("Address")) data.append(result) @@ -95,11 +112,12 @@ def get_party_addresses_and_contact(party_type, party, party_group): for idx in range(0, max_length): result = [party] result.append(party_groups[party]) + if filters.get("party_type") in ["Customer", "Supplier"]: + result.append(party_name_map[party]) address = addresses[idx] if idx < len(addresses) else add_blank_columns_for("Address") contact = contacts[idx] if idx < len(contacts) else add_blank_columns_for("Contact") result.extend(address) result.extend(contact) - data.append(result) return data @@ -115,7 +133,6 @@ def get_party_details(party_type, party_list, doctype, party_details): for d in records: details = party_details.get(d[0]) details.setdefault(frappe.scrub(doctype), []).append(d[1:]) - return party_details From 9fde7824035580a3d8bd0b48a2e1a3d2ffc46082 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 12 Nov 2023 13:21:18 +0100 Subject: [PATCH 075/163] fix(customer): contact creation for companies (#38055) --- erpnext/selling/doctype/customer/customer.py | 37 ++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index a4bb9d5381..f298becd06 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -309,13 +309,14 @@ class Customer(TransactionBase): def create_contact(contact, party_type, party, email): """Create contact based on given contact name""" - contact = contact.split(" ") + names = contact.split(" ") contact = frappe.get_doc( { "doctype": "Contact", - "first_name": contact[0], - "last_name": len(contact) > 1 and contact[1] or "", + "first_name": names[0], + "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", + "last_name": len(names) > 1 and names[-1] or "", "is_primary_contact": 1, } ) @@ -640,14 +641,28 @@ def get_credit_limit(customer, company): def make_contact(args, is_primary_contact=1): - contact = frappe.get_doc( - { - "doctype": "Contact", - "first_name": args.get("customer_name"), - "is_primary_contact": is_primary_contact, - "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], - } - ) + values = { + "doctype": "Contact", + "is_primary_contact": is_primary_contact, + "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], + } + if args.customer_type == "Individual": + names = args.get("customer_name").split(" ") + values.update( + { + "first_name": names[0], + "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", + "last_name": len(names) > 1 and names[-1] or "", + } + ) + else: + values.update( + { + "company_name": args.get("customer_name"), + } + ) + contact = frappe.get_doc(values) + if args.get("email_id"): contact.add_email(args.get("email_id"), is_primary=True) if args.get("mobile_no"): From 94faa44697144157940a874090fd1bd02c1eb981 Mon Sep 17 00:00:00 2001 From: Arjun Date: Sun, 12 Nov 2023 18:02:13 +0530 Subject: [PATCH 076/163] fix: close `Credit Limit Crossed` dialog (#38052) --- erpnext/selling/doctype/customer/customer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index f298becd06..fee55b440f 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -501,6 +501,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, primary_action={ "label": "Send Email", "server_action": "erpnext.selling.doctype.customer.customer.send_emails", + "hide_on_success": True, "args": { "customer": customer, "customer_outstanding": customer_outstanding, From 6f432b8e454b56c00ef444924b5253c0ca1af988 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 12 Nov 2023 14:48:18 +0100 Subject: [PATCH 077/163] refactor: parse full name --- erpnext/selling/doctype/customer/customer.py | 35 ++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index fee55b440f..526b0dc70a 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -309,20 +309,19 @@ class Customer(TransactionBase): def create_contact(contact, party_type, party, email): """Create contact based on given contact name""" - names = contact.split(" ") - - contact = frappe.get_doc( + first, middle, last = parse_full_name(contact) + doc = frappe.get_doc( { "doctype": "Contact", - "first_name": names[0], - "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", - "last_name": len(names) > 1 and names[-1] or "", + "first_name": first, + "middle_name": middle, + "last_name": last, "is_primary_contact": 1, } ) - contact.append("email_ids", dict(email_id=email, is_primary=1)) - contact.append("links", dict(link_doctype=party_type, link_name=party)) - contact.insert() + doc.append("email_ids", dict(email_id=email, is_primary=1)) + doc.append("links", dict(link_doctype=party_type, link_name=party)) + return doc.insert() @frappe.whitelist() @@ -648,12 +647,12 @@ def make_contact(args, is_primary_contact=1): "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], } if args.customer_type == "Individual": - names = args.get("customer_name").split(" ") + first, middle, last = parse_full_name(args.get("customer_name")) values.update( { - "first_name": names[0], - "middle_name": len(names) > 2 and " ".join(names[1:-1]) or "", - "last_name": len(names) > 1 and names[-1] or "", + "first_name": first, + "middle_name": middle, + "last_name": last, } ) else: @@ -730,3 +729,13 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil .where((dlink.link_name == customer) & (con.name.like(f"%{txt}%"))) .run() ) + + +def parse_full_name(full_name: str) -> tuple[str, str | None, str | None]: + """Parse full name into first name, middle name and last name""" + names = full_name.split() + first_name = names[0] + middle_name = " ".join(names[1:-1]) if len(names) > 2 else None + last_name = names[-1] if len(names) > 1 else None + + return first_name, middle_name, last_name From d17e37c581732bfc78fb9b5f39bc354fa8667977 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 12 Nov 2023 14:51:40 +0100 Subject: [PATCH 078/163] test: parse full name --- .../selling/doctype/customer/test_customer.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 6e737e4b55..29dbd4f321 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -10,7 +10,11 @@ from frappe.utils import flt from erpnext.accounts.party import get_due_date from erpnext.exceptions import PartyDisabled, PartyFrozen -from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding +from erpnext.selling.doctype.customer.customer import ( + get_credit_limit, + get_customer_outstanding, + parse_full_name, +) from erpnext.tests.utils import create_test_contact_and_address test_ignore = ["Price List"] @@ -373,6 +377,22 @@ class TestCustomer(FrappeTestCase): frappe.db.set_single_value("Selling Settings", "cust_master_name", "Customer Name") + def test_parse_full_name(self): + first, middle, last = parse_full_name("John") + self.assertEqual(first, "John") + self.assertEqual(middle, None) + self.assertEqual(last, None) + + first, middle, last = parse_full_name("John Doe") + self.assertEqual(first, "John") + self.assertEqual(middle, None) + self.assertEqual(last, "Doe") + + first, middle, last = parse_full_name("John Michael Doe") + self.assertEqual(first, "John") + self.assertEqual(middle, "Michael") + self.assertEqual(last, "Doe") + def get_customer_dict(customer_name): return { From d380bf817916b930254b64847ba66b6066e65cb3 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:04:38 +0100 Subject: [PATCH 079/163] feat: remove unused method create_contact --- erpnext/selling/doctype/customer/customer.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 526b0dc70a..459fc9fcff 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -307,23 +307,6 @@ class Customer(TransactionBase): ) -def create_contact(contact, party_type, party, email): - """Create contact based on given contact name""" - first, middle, last = parse_full_name(contact) - doc = frappe.get_doc( - { - "doctype": "Contact", - "first_name": first, - "middle_name": middle, - "last_name": last, - "is_primary_contact": 1, - } - ) - doc.append("email_ids", dict(email_id=email, is_primary=1)) - doc.append("links", dict(link_doctype=party_type, link_name=party)) - return doc.insert() - - @frappe.whitelist() def make_quotation(source_name, target_doc=None): def set_missing_values(source, target): From a393a6b76c35d95e4422fbf239c468ab48e67b71 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 13 Nov 2023 10:39:40 +0530 Subject: [PATCH 080/163] refactor: raise exception on invalid date --- .../doctype/bulk_transaction_log/bulk_transaction_log.js | 2 +- .../doctype/bulk_transaction_log/bulk_transaction_log.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 3135d41cc1..dc54d606e7 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -14,7 +14,7 @@ frappe.ui.form.on("Bulk Transaction Log", { frappe.call({ method: "erpnext.utilities.bulk_transaction.retry", args: {date: frm.doc.date} - }).then(()=> { }); + }); }); } }, diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 8ae54ddab8..712caf1f91 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -15,6 +15,13 @@ class BulkTransactionLog(Document): def load_from_db(self): log_detail = qb.DocType("Bulk Transaction Log Detail") + + has_records = frappe.db.sql( + f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');" + )[0][0] + if not has_records: + raise frappe.DoesNotExistError + succeeded_logs = ( qb.from_(log_detail) .select(Count(log_detail.date).as_("count")) From 95edd82638c5cb5aabefd871966d558f51095f94 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 13 Nov 2023 11:56:43 +0530 Subject: [PATCH 081/163] refactor: add revaluation journal checkbox in AR/AP summary reports --- .../accounts_payable_summary/accounts_payable_summary.js | 5 +++++ .../accounts_receivable_summary.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 9e575e669d..0f206b1cf4 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -110,6 +110,11 @@ frappe.query_reports["Accounts Payable Summary"] = { "fieldname":"based_on_payment_terms", "label": __("Based On Payment Terms"), "fieldtype": "Check", + }, + { + "fieldname": "for_revaluation_journals", + "label": __("Revaluation Journals"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 5ad10c7890..2f6d2582b3 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -139,6 +139,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { "label": __("Show GL Balance"), "fieldtype": "Check", }, + { + "fieldname": "for_revaluation_journals", + "label": __("Revaluation Journals"), + "fieldtype": "Check", + } ], onload: function(report) { From 4a111f73620136cda022e030c58d7d58b0c22a8e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 13 Nov 2023 13:26:55 +0530 Subject: [PATCH 082/163] fix: indentation issue in the Production Plan Summary report (#38019) fix: Production Plan Summary report --- .../production_plan/production_plan.js | 4 -- .../production_plan/production_plan.py | 2 - .../production_plan/test_production_plan.py | 43 ------------------- .../production_plan_summary.js | 4 +- .../production_plan_summary.py | 26 ++++++++--- 5 files changed, 22 insertions(+), 57 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 72438ddcee..dd102b0fae 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -89,10 +89,6 @@ frappe.ui.form.on('Production Plan', { frm.trigger("show_progress"); if (frm.doc.status !== "Completed") { - frm.add_custom_button(__("Work Order Tree"), ()=> { - frappe.set_route('Tree', 'Work Order', {production_plan: frm.doc.name}); - }, __('View')); - frm.add_custom_button(__("Production Plan Summary"), ()=> { frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name}); }, __('View')); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 6b12a29b50..6efb762905 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -828,8 +828,6 @@ class ProductionPlan(Document): # Combine subassembly items sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store) - sub_assembly_items_store.sort(key=lambda d: d.bom_level, reverse=True) # sort by bom level - for idx, row in enumerate(sub_assembly_items_store): row.idx = idx + 1 self.append("sub_assembly_items", row) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index e9c6ee3af2..dd32c34358 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -664,49 +664,6 @@ class TestProductionPlan(FrappeTestCase): frappe.db.rollback() - def test_subassmebly_sorting(self): - "Test subassembly sorting in case of multiple items with nested BOMs." - from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom - - prefix = "_TestLevel_" - boms = { - "Assembly": { - "SubAssembly1": { - "ChildPart1": {}, - "ChildPart2": {}, - }, - "ChildPart6": {}, - "SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}}, - }, - "MegaDeepAssy": { - "SecretSubassy": { - "SecretPart": {"VerySecret": {"SuperSecret": {"Classified": {}}}}, - }, - # ^ assert that this is - # first item in subassy table - }, - } - create_nested_bom(boms, prefix=prefix) - - items = [prefix + item_code for item_code in boms.keys()] - plan = create_production_plan(item_code=items[0], do_not_save=True) - plan.append( - "po_items", - { - "use_multi_level_bom": 1, - "item_code": items[1], - "bom_no": frappe.db.get_value("Item", items[1], "default_bom"), - "planned_qty": 1, - "planned_start_date": now_datetime(), - }, - ) - plan.get_sub_assembly_items() - - bom_level_order = [d.bom_level for d in plan.sub_assembly_items] - self.assertEqual(bom_level_order, sorted(bom_level_order, reverse=True)) - # lowest most level of subassembly should be first - self.assertIn("SuperSecret", plan.sub_assembly_items[0].production_item) - def test_multiple_work_order_for_production_plan_item(self): "Test producing Prod Plan (making WO) in parts." diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js index 521543ab1b..afe4a6e0cb 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js @@ -22,9 +22,9 @@ frappe.query_reports["Production Plan Summary"] = { "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.fieldname == "document_name") { + if (column.fieldname == "item_code") { var color = data.pending_qty > 0 ? 'red': 'green'; - value = `${data['document_name']}`; + value = `${data['item_code']}`; } return value; diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py index 2c8f82f2cc..076690ff09 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -44,6 +44,7 @@ def get_production_plan_item_details(filters, data, order_details): { "indent": 0, "item_code": row.item_code, + "sales_order": row.get("sales_order"), "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"), "qty": row.planned_qty, "document_type": "Work Order", @@ -80,7 +81,7 @@ def get_production_plan_sub_assembly_item_details( data.append( { - "indent": 1, + "indent": 1 + item.indent, "item_code": item.production_item, "item_name": item.item_name, "qty": item.qty, @@ -98,7 +99,7 @@ def get_work_order_details(filters, order_details): for row in frappe.get_all( "Work Order", filters={"production_plan": filters.get("production_plan")}, - fields=["name", "produced_qty", "production_plan", "production_item"], + fields=["name", "produced_qty", "production_plan", "production_item", "sales_order"], ): order_details.setdefault((row.name, row.production_item), row) @@ -118,10 +119,17 @@ def get_column(filters): "label": _("Finished Good"), "fieldtype": "Link", "fieldname": "item_code", - "width": 300, + "width": 240, "options": "Item", }, - {"label": _("Item Name"), "fieldtype": "data", "fieldname": "item_name", "width": 100}, + {"label": _("Item Name"), "fieldtype": "data", "fieldname": "item_name", "width": 150}, + { + "label": _("Sales Order"), + "options": "Sales Order", + "fieldtype": "Link", + "fieldname": "sales_order", + "width": 100, + }, { "label": _("Document Type"), "fieldtype": "Link", @@ -133,10 +141,16 @@ def get_column(filters): "label": _("Document Name"), "fieldtype": "Dynamic Link", "fieldname": "document_name", - "width": 150, + "options": "document_type", + "width": 180, }, {"label": _("BOM Level"), "fieldtype": "Int", "fieldname": "bom_level", "width": 100}, {"label": _("Order Qty"), "fieldtype": "Float", "fieldname": "qty", "width": 120}, - {"label": _("Received Qty"), "fieldtype": "Float", "fieldname": "produced_qty", "width": 160}, + { + "label": _("Produced / Received Qty"), + "fieldtype": "Float", + "fieldname": "produced_qty", + "width": 200, + }, {"label": _("Pending Qty"), "fieldtype": "Float", "fieldname": "pending_qty", "width": 110}, ] From ea7565889fb9a833b3bcd9f36abca020285e17cc Mon Sep 17 00:00:00 2001 From: David Arnold Date: Mon, 13 Nov 2023 09:49:22 +0100 Subject: [PATCH 083/163] feat(customer): add button to quick-create sales order and quotation (#37320) * feat(customer): add button to quick-create sales order and quotation * fix: create buttons in customer * refactor: don't repeat yourself --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- erpnext/selling/doctype/customer/customer.js | 41 +++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 42932ad8bd..ddc7e2af8f 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -3,17 +3,32 @@ frappe.ui.form.on("Customer", { setup: function(frm) { - + frm.custom_make_buttons = { + "Opportunity": "Opportunity", + "Quotation": "Quotation", + "Sales Order": "Sales Order", + "Pricing Rule": "Pricing Rule", + }; frm.make_methods = { - 'Quotation': () => frappe.model.open_mapped_doc({ - method: "erpnext.selling.doctype.customer.customer.make_quotation", - frm: cur_frm - }), - 'Opportunity': () => frappe.model.open_mapped_doc({ - method: "erpnext.selling.doctype.customer.customer.make_opportunity", - frm: cur_frm - }) - } + "Quotation": () => + frappe.model.open_mapped_doc({ + method: "erpnext.selling.doctype.customer.customer.make_quotation", + frm: frm, + }), + "Sales Order": () => + frappe.model.with_doctype("Sales Order", function () { + var so = frappe.model.get_new_doc("Sales Order"); + so.customer = frm.doc.name; // Set the current customer as the SO customer + frappe.set_route("Form", "Sales Order", so.name); + }), + "Opportunity": () => + frappe.model.open_mapped_doc({ + method: "erpnext.selling.doctype.customer.customer.make_opportunity", + frm: frm, + }), + "Pricing Rule": () => + erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name), + }; frm.add_fetch('lead_name', 'company_name', 'customer_name'); frm.add_fetch('default_sales_partner','commission_rate','default_commission_rate'); @@ -146,9 +161,9 @@ frappe.ui.form.on("Customer", { {party_type: 'Customer', party: frm.doc.name, party_name: frm.doc.customer_name}); }, __('View')); - frm.add_custom_button(__('Pricing Rule'), function () { - erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name); - }, __('Create')); + for (const doctype in frm.make_methods) { + frm.add_custom_button(__(doctype), frm.make_methods[doctype], __("Create")); + } frm.add_custom_button(__('Get Customer Group Details'), function () { frm.trigger("get_customer_group_details"); From 9ae5c979e894c791087579fdd00aa2cdb6e1e34f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:43:46 +0530 Subject: [PATCH 084/163] fix: Identical items are added line by line instead of grouped together in POS (#37986) fix: Identical items are added line by line instead of grouped together in POS (#37986) fix: Identical items are added line by line instead of grouped together in POS (#37986) (cherry picked from commit 011cf3d73e2f4dd98c4b33c502ca34665e067c57) Co-authored-by: bVisible --- erpnext/selling/page/point_of_sale/pos_controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 9bba4ebf50..feecd9cfd8 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -609,11 +609,12 @@ erpnext.PointOfSale.Controller = class { // if item is clicked twice from item selector // then "item_code, batch_no, uom, rate" will help in getting the exact item // to increase the qty by one - const has_batch_no = batch_no; + const has_batch_no = (batch_no !== 'null' && batch_no !== null); item_row = this.frm.doc.items.find( i => i.item_code === item_code && (!has_batch_no || (has_batch_no && i.batch_no === batch_no)) && (i.uom === uom) + && (i.rate === flt(rate)) ); } From 5b446d45750c45c6a86bb420c689a06945ab4974 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 15:05:34 +0530 Subject: [PATCH 085/163] fix: handle partial return against invoices --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index fc22f53f4d..2634972800 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1074,9 +1074,7 @@ class PaymentEntry(AccountsController): reverse_dr_or_cr, standalone_note = 0, 0 if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: - is_return, return_against = frappe.db.get_value( - d.reference_doctype, d.reference_name, ["is_return", "return_against"] - ) + is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") payable_party_types = get_party_types_from_account_type("Payable") receivable_party_types = get_party_types_from_account_type("Receivable") if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"): @@ -1086,7 +1084,7 @@ class PaymentEntry(AccountsController): ): reverse_dr_or_cr = 1 - if is_return and not return_against and not reverse_dr_or_cr: + if is_return and not reverse_dr_or_cr: dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" gle.update( From 56b8d1b9277a1290b18c6745736bef8dfa4e6f90 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 13 Nov 2023 17:00:02 +0530 Subject: [PATCH 086/163] fix: remove ESS role when not mapped to employee (#37867) * fix: remove ESS role when not mapped to employee * fix: emp role removal on unlinking * fix: test case for user employee role mapping * fix: mapped employee and user on creation --- erpnext/setup/doctype/employee/employee.js | 4 ++- erpnext/setup/doctype/employee/employee.py | 29 +++++++++++++++---- .../setup/doctype/employee/test_employee.py | 9 ++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.js b/erpnext/setup/doctype/employee/employee.js index 39a215f383..efc3fd1d33 100755 --- a/erpnext/setup/doctype/employee/employee.js +++ b/erpnext/setup/doctype/employee/employee.js @@ -81,8 +81,10 @@ frappe.ui.form.on("Employee", { employee: frm.doc.name, email: frm.doc.prefered_email }, + freeze: true, + freeze_message: __("Creating User..."), callback: function (r) { - frm.set_value("user_id", r.message); + frm.reload_doc(); } }); } diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 78fb4dfc58..6f9176cf27 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -48,6 +48,9 @@ class Employee(NestedSet): else: existing_user_id = frappe.db.get_value("Employee", self.name, "user_id") if existing_user_id: + user = frappe.get_doc("User", existing_user_id) + validate_employee_role(user, ignore_emp_check=True) + user.save(ignore_permissions=True) remove_user_permission("Employee", self.name, existing_user_id) def after_rename(self, old, new, merge): @@ -230,12 +233,26 @@ class Employee(NestedSet): frappe.cache().hdel("employees_with_number", prev_number) -def validate_employee_role(doc, method): +def validate_employee_role(doc, method=None, ignore_emp_check=False): # called via User hook - if "Employee" in [d.role for d in doc.get("roles")]: - if not frappe.db.get_value("Employee", {"user_id": doc.name}): - frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role")) - doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0]) + if not ignore_emp_check: + if frappe.db.get_value("Employee", {"user_id": doc.name}): + return + + user_roles = [d.role for d in doc.get("roles")] + if "Employee" in user_roles: + frappe.msgprint( + _("User {0}: Removed Employee role as there is no mapped employee.").format(doc.name) + ) + doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0]) + + if "Employee Self Service" in user_roles: + frappe.msgprint( + _("User {0}: Removed Employee Self Service role as there is no mapped employee.").format( + doc.name + ) + ) + doc.get("roles").remove(doc.get("roles", {"role": "Employee Self Service"})[0]) def update_user_permissions(doc, method): @@ -347,6 +364,8 @@ def create_user(employee, user=None, email=None): } ) user.insert() + emp.user_id = user.name + emp.save() return user.name diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py index 5a693c5eff..9b70683626 100644 --- a/erpnext/setup/doctype/employee/test_employee.py +++ b/erpnext/setup/doctype/employee/test_employee.py @@ -25,6 +25,15 @@ class TestEmployee(unittest.TestCase): employee1_doc.status = "Left" self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + def test_user_has_employee(self): + employee = make_employee("test_emp_user_creation@company.com") + employee_doc = frappe.get_doc("Employee", employee) + user = employee_doc.user_id + self.assertTrue("Employee" in frappe.get_roles(user)) + employee_doc.user_id = "" + employee_doc.save() + self.assertTrue("Employee" not in frappe.get_roles(user)) + def tearDown(self): frappe.db.rollback() From 2f9e96e3245e210ab1a26ffe99499367b0e0afa5 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Mon, 13 Nov 2023 18:03:33 +0530 Subject: [PATCH 087/163] chore: delete comments and unlink attachments on company transactions deletion --- .../transaction_deletion_record.py | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 481a3a5ebe..d266285b29 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -108,7 +108,16 @@ class TransactionDeletionRecord(Document): if no_of_docs > 0: self.delete_version_log(docfield["parent"], docfield["fieldname"]) - self.delete_communications(docfield["parent"], docfield["fieldname"]) + + reference_docs = frappe.get_all( + docfield["parent"], filters={docfield["fieldname"]: self.company} + ) + reference_doc_names = [r.name for r in reference_docs] + + self.delete_communications(docfield["parent"], reference_doc_names) + self.delete_comments(docfield["parent"], reference_doc_names) + self.unlink_attachments(docfield["parent"], reference_doc_names) + self.populate_doctypes_table(tables, docfield["parent"], no_of_docs) self.delete_child_tables(docfield["parent"], docfield["fieldname"]) @@ -197,19 +206,49 @@ class TransactionDeletionRecord(Document): (versions.ref_doctype == doctype) & (versions.docname.isin(batch)) ).run() - def delete_communications(self, doctype, company_fieldname): - reference_docs = frappe.get_all(doctype, filters={company_fieldname: self.company}) - reference_doc_names = [r.name for r in reference_docs] - + def delete_communications(self, doctype, reference_doc_names): communications = frappe.get_all( "Communication", filters={"reference_doctype": doctype, "reference_name": ["in", reference_doc_names]}, ) communication_names = [c.name for c in communications] + if not communication_names: + return + for batch in create_batch(communication_names, self.batch_size): frappe.delete_doc("Communication", batch, ignore_permissions=True) + def delete_comments(self, doctype, reference_doc_names): + comments = frappe.get_all( + "Comment", + filters={"reference_doctype": doctype, "reference_name": ["in", reference_doc_names]}, + ) + comment_names = [c.name for c in comments] + + if not comment_names: + return + + for batch in create_batch(comment_names, self.batch_size): + frappe.delete_doc("Comment", batch, ignore_permissions=True) + + def unlink_attachments(self, doctype, reference_doc_names): + files = frappe.get_all( + "File", + filters={"attached_to_doctype": doctype, "attached_to_name": ["in", reference_doc_names]}, + ) + file_names = [c.name for c in files] + + if not file_names: + return + + file = qb.DocType("File") + + for batch in create_batch(file_names, self.batch_size): + qb.update(file).set(file.attached_to_doctype, None).set(file.attached_to_name, None).where( + file.name.isin(batch) + ).run() + @frappe.whitelist() def get_doctypes_to_be_ignored(): From b097bb20d99f1d25fbd8bdcf0277859741a0ee53 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Mon, 13 Nov 2023 18:09:09 +0530 Subject: [PATCH 088/163] fix: unrelated transation date typo --- erpnext/controllers/buying_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index ece08d83f9..a470b47a45 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -365,7 +365,7 @@ class BuyingController(SubcontractingController): { "item_code": d.item_code, "warehouse": d.get("from_warehouse"), - "posting_date": self.get("posting_date") or self.get("transation_date"), + "posting_date": self.get("posting_date") or self.get("transaction_date"), "posting_time": posting_time, "qty": -1 * flt(d.get("stock_qty")), "serial_and_batch_bundle": d.get("serial_and_batch_bundle"), From a59c942cd487e0440625663034760eb89171e2ef Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 18:09:49 +0530 Subject: [PATCH 089/163] fix: reset dr_or_cr for every reference --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 2634972800..448224b3a7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1054,9 +1054,9 @@ class PaymentEntry(AccountsController): item=self, ) - dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" - for d in self.get("references"): + # re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" cost_center = self.cost_center if d.reference_doctype == "Sales Invoice" and not cost_center: cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") @@ -1072,7 +1072,7 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name - reverse_dr_or_cr, standalone_note = 0, 0 + reverse_dr_or_cr = 0 if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") payable_party_types = get_party_types_from_account_type("Payable") @@ -1098,6 +1098,7 @@ class PaymentEntry(AccountsController): ) gl_entries.append(gle) + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" if self.unallocated_amount: exchange_rate = self.get_exchange_rate() base_unallocated_amount = self.unallocated_amount * exchange_rate From 09f9764bbdec90d1b9f85ccc5a368bf85547bea1 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 18:11:00 +0530 Subject: [PATCH 090/163] test: payment against partial return invoices --- .../payment_entry/test_payment_entry.py | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 603f24a09c..5af37b6a5e 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1290,6 +1290,9 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(references[2].payment_term, "Tax Receivable") def test_receive_payment_from_payable_party_type(self): + """ + Checks GL entries generated while receiving payments from a Payable Party Type. + """ pe = create_payment_entry( party_type="Supplier", party="_Test Supplier", @@ -1301,8 +1304,55 @@ class TestPaymentEntry(FrappeTestCase): ) self.voucher_no = pe.name self.expected_gle = [ - {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, {"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + ] + self.check_gl_entries() + + def test_payment_against_partial_return_invoice(self): + """ + Checks GL entries generated for partial return invoice payments. + """ + si = create_sales_invoice(qty=10, rate=10, customer="_Test Customer") + credit_note = create_sales_invoice( + qty=-4, rate=10, customer="_Test Customer", is_return=1, return_against=si.name + ) + pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + pe.set( + "references", + [ + { + "reference_doctype": "Sales Invoice", + "reference_name": si.name, + "due_date": si.get("due_date"), + "total_amount": si.grand_total, + "outstanding_amount": si.outstanding_amount, + "allocated_amount": si.outstanding_amount, + }, + { + "reference_doctype": "Sales Invoice", + "reference_name": credit_note.name, + "due_date": credit_note.get("due_date"), + "total_amount": credit_note.grand_total, + "outstanding_amount": credit_note.outstanding_amount, + "allocated_amount": credit_note.outstanding_amount, + }, + ], + ) + pe.save() + pe.submit() + self.voucher_no = pe.name + self.expected_gle = [ + {"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0}, + {"account": "Debtors - _TC", "debit": 0.0, "credit": 940.0}, + {"account": "Debtors - _TC", "debit": 0.0, "credit": 100.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, ] self.check_gl_entries() @@ -1316,7 +1366,7 @@ class TestPaymentEntry(FrappeTestCase): gle.credit, ) .where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0)) - .orderby(gle.account) + .orderby(gle.account, gle.debit, gle.credit, order=frappe.qb.desc) ).run(as_dict=True) for row in range(len(self.expected_gle)): for field in ["account", "debit", "credit"]: From 780b827adcba571b46ee73404f9a038c36dd0eb9 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 20:24:32 +0530 Subject: [PATCH 091/163] refactor: validate reposting settings for editables inv --- .../repost_accounting_ledger.py | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 69cfe9fcd7..1d72a46c12 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -10,12 +10,7 @@ from frappe.utils.data import comma_and class RepostAccountingLedger(Document): def __init__(self, *args, **kwargs): super(RepostAccountingLedger, self).__init__(*args, **kwargs) - self._allowed_types = [ - x.document_type - for x in frappe.db.get_all( - "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"] - ) - ] + self._allowed_types = get_allowed_types_from_settings() def validate(self): self.validate_vouchers() @@ -56,15 +51,7 @@ class RepostAccountingLedger(Document): def validate_vouchers(self): if self.vouchers: - # Validate voucher types - voucher_types = set([x.voucher_type for x in self.vouchers]) - if disallowed_types := voucher_types.difference(self._allowed_types): - frappe.throw( - _("{0} types are not allowed. Only {1} are.").format( - frappe.bold(comma_and(list(disallowed_types))), - frappe.bold(comma_and(list(self._allowed_types))), - ) - ) + validate_docs_for_voucher_types([x.voucher_type for x in self.vouchers]) def get_existing_ledger_entries(self): vouchers = [x.voucher_no for x in self.vouchers] @@ -168,6 +155,15 @@ def start_repost(account_repost_doc=str) -> None: frappe.db.commit() +def get_allowed_types_from_settings(): + return [ + x.document_type + for x in frappe.db.get_all( + "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"] + ) + ] + + def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): docs_with_deferred_revenue = frappe.db.get_all( "Sales Invoice Item", @@ -191,6 +187,25 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): ) +def validate_docs_for_voucher_types(doc_voucher_types): + allowed_types = get_allowed_types_from_settings() + # Validate voucher types + voucher_types = set(doc_voucher_types) + if disallowed_types := voucher_types.difference(allowed_types): + message = "are" if len(disallowed_types) > 1 else "is" + frappe.throw( + _("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format( + frappe.bold(comma_and(list(disallowed_types))), + message, + frappe.bold( + frappe.utils.get_link_to_form( + "Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings" + ) + ), + ) + ) + + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters): From ad5edbb1de95befa1b6f312dcb7df6d8c5a8ce6c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 20:25:26 +0530 Subject: [PATCH 092/163] fix: do not set repost flag without validating voucher --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e1f0f1932e..d7c2361b4d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -13,6 +13,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( validate_docs_for_deferred_accounting, + validate_docs_for_voucher_types, ) from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, @@ -491,6 +492,7 @@ class PurchaseInvoice(BuyingController): def validate_for_repost(self): self.validate_write_off_account() self.validate_expense_account() + validate_docs_for_voucher_types(["Purchase Invoice"]) validate_docs_for_deferred_accounting([], [self.name]) def on_submit(self): From 5fae2f6d57bac332263d8c73e3d090f485b5844d Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 20:27:09 +0530 Subject: [PATCH 093/163] fix: allow on submit for child table fields --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 424e942990..6987a8faf0 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -497,6 +497,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -504,6 +505,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "default": ":Company", "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "cost_center", @@ -915,7 +917,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-03 21:01:01.824892", + "modified": "2023-11-13 20:26:18.329983", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", From 894ae1fe0f8da1931f705d433c15b19410486186 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 20:28:44 +0530 Subject: [PATCH 094/163] fix: check reposting settings before allowing editable si --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fa95ccdc57..adb8286be7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -17,6 +17,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( ) from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( validate_docs_for_deferred_accounting, + validate_docs_for_voucher_types, ) from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, @@ -172,6 +173,7 @@ class SalesInvoice(SellingController): self.validate_write_off_account() self.validate_account_for_change_amount() self.validate_income_account() + validate_docs_for_voucher_types(["Sales Invoice"]) validate_docs_for_deferred_accounting([self.name], []) def validate_fixed_asset(self): From c31ee8ea33afcc8128270f098c40cc0b0e3f1a49 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 14 Nov 2023 06:44:49 +0530 Subject: [PATCH 095/163] refactor: use 'boolean' parameter while fetching FY year --- erpnext/accounts/utils.py | 3 +++ erpnext/public/js/financial_statements.js | 10 ++++++++-- erpnext/public/js/utils.js | 5 +++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 31bc6fdef8..7d91309fcc 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -53,6 +53,9 @@ GL_REPOSTING_CHUNK = 100 def get_fiscal_year( date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False ): + if isinstance(boolean, str): + boolean = frappe.json.loads(boolean) + fiscal_years = get_fiscal_years( date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean ) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 907a775bfa..1b10d8ad3a 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -139,7 +139,6 @@ function get_filters() { "label": __("Start Year"), "fieldtype": "Link", "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), "reqd": 1, "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, @@ -148,7 +147,6 @@ function get_filters() { "label": __("End Year"), "fieldtype": "Link", "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), "reqd": 1, "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, @@ -197,5 +195,13 @@ function get_filters() { } ] + // Dynamically set 'default' values for fiscal year filters + let fy_filters = filters.filter(x=>{return ["from_fiscal_year", "to_fiscal_year"].includes(x.fieldname);}) + let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, true); + if (fiscal_year) { + let fy = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, false); + fy_filters.forEach(x=>{x.default = fy;}) + } + return filters; } diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index d435711cf5..25fc754b9a 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -404,7 +404,7 @@ $.extend(erpnext.utils, { }); }, - get_fiscal_year: function(date, with_dates=false) { + get_fiscal_year: function(date, with_dates=false, boolean=false) { if(!date) { date = frappe.datetime.get_today(); } @@ -413,7 +413,8 @@ $.extend(erpnext.utils, { frappe.call({ method: "erpnext.accounts.utils.get_fiscal_year", args: { - date: date + date: date, + boolean: boolean }, async: false, callback: function(r) { From e769e750ec712872a8fd28c533d1f02444e351a9 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 14 Nov 2023 15:08:37 +0530 Subject: [PATCH 096/163] fix: Not able to save subcontracting receipt (#38085) --- .../serial_and_batch_bundle.py | 3 + .../subcontracting_receipt.py | 2 + .../test_subcontracting_receipt.py | 71 ++++++++++++++++++- 3 files changed, 75 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 f2bbf2b211..0a4cae7b34 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 @@ -1604,6 +1604,9 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]: ) for key, val in kwargs.items(): + if not val: + continue + if key in ["get_subcontracted_item"]: continue diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 7e06444e1e..8d705aa97d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -148,6 +148,8 @@ class SubcontractingReceipt(SubcontractingController): if ( frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") == "BOM" + and self.supplied_items + and not any(item.serial_and_batch_bundle for item in self.supplied_items) ): self.supplied_items = [] diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 1828f6960f..96babf2b32 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -6,7 +6,7 @@ import copy import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, cint, cstr, flt, today +from frappe.utils import add_days, cint, cstr, flt, nowtime, today import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account @@ -26,6 +26,10 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries +from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( + get_batch_from_bundle, + make_serial_batch_bundle, +) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, @@ -507,6 +511,71 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertNotEqual(scr.supplied_items[0].rate, prev_cost) self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate) + def test_subcontracting_receipt_for_batch_raw_materials_without_material_transfer(self): + set_backflush_based_on("BOM") + + fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name + rm_item1 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + } + ).name + + bom = make_bom(item=fg_item, raw_materials=[rm_item1]) + + rm_batch_no = None + for row in bom.items: + se = make_stock_entry( + item_code=row.item_code, + qty=1, + target="_Test Warehouse 1 - _TC", + rate=300, + ) + + se.reload() + rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 1, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.reload() + + bundle_doc = make_serial_batch_bundle( + { + "item_code": scr.supplied_items[0].rm_item_code, + "warehouse": "_Test Warehouse 1 - _TC", + "voucher_type": "Subcontracting Receipt", + "posting_date": today(), + "posting_time": nowtime(), + "qty": -1, + "batches": frappe._dict({rm_batch_no: 1}), + "type_of_transaction": "Outward", + "do_not_submit": True, + } + ) + + scr.supplied_items[0].serial_and_batch_bundle = bundle_doc.name + scr.submit() + scr.reload() + + batch_no = get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, rm_batch_no) + self.assertEqual(scr.items[0].rm_cost_per_qty, 300) + self.assertEqual(scr.items[0].service_cost_per_qty, 100) + def test_subcontracting_receipt_raw_material_rate(self): # Step - 1: Set Backflush Based On as "BOM" set_backflush_based_on("BOM") From 3e77c0b5644c28ed9b4eef22b228fdc4a1283020 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 14 Nov 2023 19:27:41 +0530 Subject: [PATCH 097/163] fix: valuation rate for the subcontracting receipt supplied items with Serial and Batch Bundle (#38094) fix: valuation rate for the subcontracting receipt supplied items with batch --- .../controllers/subcontracting_controller.py | 13 +++ .../stock_ledger_entry.json | 10 +- erpnext/stock/serial_batch_bundle.py | 10 +- erpnext/stock/stock_ledger.py | 4 +- .../subcontracting_receipt.js | 10 ++ .../test_subcontracting_receipt.py | 91 +++++++++++++++++++ 6 files changed, 135 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 5fa66b1a87..3d55a087bd 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -626,6 +626,18 @@ class SubcontractingController(StockController): (row.item_code, row.get(self.subcontract_data.order_field)) ] -= row.qty + def __set_rate_for_serial_and_batch_bundle(self): + if self.doctype != "Subcontracting Receipt": + return + + for row in self.get(self.raw_material_table): + if not row.get("serial_and_batch_bundle"): + continue + + row.rate = frappe.get_cached_value( + "Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate" + ) + def __modify_serial_and_batch_bundle(self): if self.is_new(): return @@ -681,6 +693,7 @@ class SubcontractingController(StockController): self.__remove_changed_rows() self.__set_supplied_items() self.__modify_serial_and_batch_bundle() + self.__set_rate_for_serial_and_batch_bundle() def __validate_batch_no(self, row, key): if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get( diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index dcbd9b2d06..be379940ca 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -12,6 +12,7 @@ "posting_date", "posting_time", "is_adjustment_entry", + "auto_created_serial_and_batch_bundle", "column_break_6", "voucher_type", "voucher_no", @@ -340,6 +341,13 @@ "fieldname": "is_adjustment_entry", "fieldtype": "Check", "label": "Is Adjustment Entry" + }, + { + "default": "0", + "depends_on": "serial_and_batch_bundle", + "fieldname": "auto_created_serial_and_batch_bundle", + "fieldtype": "Check", + "label": "Auto Created Serial and Batch Bundle" } ], "hide_toolbar": 1, @@ -348,7 +356,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-10-23 18:07:42.063615", + "modified": "2023-11-14 16:47:39.791967", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 5998274bb7..da98455b5c 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -129,7 +129,9 @@ class SerialBatchBundle: frappe.throw(_(error_msg)) def set_serial_and_batch_bundle(self, sn_doc): - self.sle.db_set("serial_and_batch_bundle", sn_doc.name) + self.sle.db_set( + {"serial_and_batch_bundle": sn_doc.name, "auto_created_serial_and_batch_bundle": 1} + ) if sn_doc.is_rejected: frappe.db.set_value( @@ -143,6 +145,12 @@ class SerialBatchBundle: @property def child_doctype(self): child_doctype = self.sle.voucher_type + " Item" + + if ( + self.sle.voucher_type == "Subcontracting Receipt" and self.sle.dependant_sle_voucher_detail_no + ): + child_doctype = "Subcontracting Receipt Supplied Item" + if self.sle.voucher_type == "Stock Entry": child_doctype = "Stock Entry Detail" diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 63908945c3..9142a27f4c 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -766,7 +766,9 @@ class update_entries_after(object): sle.doctype = "Stock Ledger Entry" frappe.get_doc(sle).db_update() - if not self.args.get("sle_id"): + if not self.args.get("sle_id") or ( + sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle + ): self.update_outgoing_rate_on_transaction(sle) def reset_actual_qty_for_stock_reco(self, sle): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 19a1c939c3..36001eb78f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -13,6 +13,16 @@ frappe.ui.form.on('Subcontracting Receipt', { frm.trigger('set_queries'); }, + on_submit(frm) { + frm.events.refresh_serial_batch_bundle_field(frm); + }, + + refresh_serial_batch_bundle_field(frm) { + frappe.route_hooks.after_submit = (frm_obj) => { + frm_obj.reload_doc(); + } + }, + refresh: (frm) => { if (frm.doc.docstatus > 0) { frm.add_custom_button(__('Stock Ledger'), () => { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 96babf2b32..6191a8ca94 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -576,6 +576,97 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(scr.items[0].rm_cost_per_qty, 300) self.assertEqual(scr.items[0].service_cost_per_qty, 100) + def test_subcontracting_receipt_valuation_with_auto_created_serial_batch_bundle(self): + set_backflush_based_on("BOM") + + fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name + rm_item1 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + } + ).name + + rm_item2 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "has_serial_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + "serial_no_series": "BNSS-.####", + } + ).name + + rm_item3 = make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "BSSSS-.####", + } + ).name + + bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3]) + + rm_batch_no = None + for row in bom.items: + make_stock_entry( + item_code=row.item_code, + qty=1, + target="_Test Warehouse 1 - _TC", + rate=300, + ) + + make_stock_entry( + item_code=row.item_code, + qty=1, + target="_Test Warehouse 1 - _TC", + rate=400, + ) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 1, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + + frappe.db.set_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1 + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + for row in scr.supplied_items: + self.assertNotEqual(row.rate, 300.00) + self.assertFalse(row.serial_and_batch_bundle) + + scr.submit() + scr.reload() + + for row in scr.supplied_items: + self.assertEqual(row.rate, 300.00) + self.assertTrue(row.serial_and_batch_bundle) + auto_created_serial_batch = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": scr.name, "voucher_detail_no": row.name}, + "auto_created_serial_and_batch_bundle", + ) + + self.assertTrue(auto_created_serial_batch) + + self.assertEqual(scr.items[0].rm_cost_per_qty, 900) + self.assertEqual(scr.items[0].service_cost_per_qty, 100) + frappe.db.set_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0 + ) + def test_subcontracting_receipt_raw_material_rate(self): # Step - 1: Set Backflush Based On as "BOM" set_backflush_based_on("BOM") From e93a19ffb5d45a3bf0136ca3fc24a828b008aeee Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 14 Nov 2023 19:38:15 +0530 Subject: [PATCH 098/163] chore: refetch item images on transaction save (#38095) chore: re fetch item images on transaction save --- .../doctype/pos_invoice_item/pos_invoice_item.json | 3 ++- .../purchase_invoice_item/purchase_invoice_item.json | 3 ++- .../doctype/sales_invoice_item/sales_invoice_item.json | 5 +++-- .../purchase_order_item/purchase_order_item.json | 3 ++- .../request_for_quotation_item.json | 7 +++++-- .../supplier_quotation_item.json | 7 +++++-- .../crm/doctype/opportunity_item/opportunity_item.json | 4 +++- .../doctype/bom_explosion_item/bom_explosion_item.json | 3 ++- erpnext/manufacturing/doctype/bom_item/bom_item.json | 3 ++- .../selling/doctype/quotation_item/quotation_item.json | 5 +++-- .../doctype/sales_order_item/sales_order_item.json | 10 ++-------- .../doctype/delivery_note_item/delivery_note_item.json | 3 ++- .../material_request_item/material_request_item.json | 3 ++- .../purchase_receipt_item/purchase_receipt_item.json | 3 ++- .../subcontracting_order_item.json | 3 ++- .../subcontracting_receipt_item.json | 3 ++- 16 files changed, 41 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index cb0ed3d6aa..5a281aaa4f 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -186,6 +186,7 @@ "label": "Image" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -833,7 +834,7 @@ ], "istable": 1, "links": [], - "modified": "2023-03-12 13:36:40.160468", + "modified": "2023-11-14 18:33:22.585715", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", 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 424e942990..bcedb7c943 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -158,6 +158,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -915,7 +916,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-03 21:01:01.824892", + "modified": "2023-11-14 18:33:48.547297", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 5d2764b669..a403b14c54 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -167,6 +167,7 @@ "print_hide": 1 }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -901,7 +902,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-26 12:53:22.404057", + "modified": "2023-11-14 18:34:10.479329", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", @@ -911,4 +912,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file 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 2b6ffb752f..2d706f41e5 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -189,6 +189,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -916,7 +917,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-06 11:00:53.596417", + "modified": "2023-11-14 18:34:27.267382", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json index 82fcfa2713..6cdd2bac0d 100644 --- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json +++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json @@ -87,6 +87,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -260,13 +261,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-24 17:26:46.276934", + "modified": "2023-11-14 18:34:48.327224", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index 8d491fbc84..4bbcacfef3 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -133,6 +133,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -559,13 +560,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-19 12:36:26.913211", + "modified": "2023-11-14 18:35:03.435817", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity_item/opportunity_item.json b/erpnext/crm/doctype/opportunity_item/opportunity_item.json index 1b4973c1b2..732f80d01c 100644 --- a/erpnext/crm/doctype/opportunity_item/opportunity_item.json +++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.json @@ -103,6 +103,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -165,7 +166,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-07-30 16:39:09.775720", + "modified": "2023-11-14 18:35:30.887278", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity Item", @@ -173,5 +174,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json index 9b1db63494..c75ac32cd1 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json @@ -85,6 +85,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -169,7 +170,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-05-27 13:42:23.305455", + "modified": "2023-11-14 18:35:40.856895", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Explosion Item", diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index c5266119dc..cb58af1f29 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -111,6 +111,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -289,7 +290,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-07-28 10:20:51.559010", + "modified": "2023-11-14 18:35:51.378513", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 5016f1f1fd..0e25313f76 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -135,6 +135,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -666,7 +667,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-09-26 13:42:11.410294", + "modified": "2023-11-14 18:24:24.619832", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", @@ -676,4 +677,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index f82047f511..b4f73003ae 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -68,7 +68,6 @@ "total_weight", "column_break_21", "weight_uom", - "accounting_dimensions_section", "warehouse_and_reference", "warehouse", "target_warehouse", @@ -177,6 +176,7 @@ "print_hide": 1 }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -890,18 +890,12 @@ "label": "Production Plan Qty", "no_copy": 1, "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-17 18:18:26.475259", + "modified": "2023-11-14 18:37:12.787893", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 6148950462..a44b9ac44b 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -168,6 +168,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -893,7 +894,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-16 16:18:18.013379", + "modified": "2023-11-14 18:37:38.638144", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 9912be145f..5dc07c99f6 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -110,6 +110,7 @@ "width": "250px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach Image", "label": "Image", @@ -478,7 +479,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-27 15:53:41.444236", + "modified": "2023-11-14 18:37:59.599115", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", 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 718f007577..ce2e5d7f84 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -192,6 +192,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -1090,7 +1091,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-30 17:32:24.560337", + "modified": "2023-11-14 18:38:15.251994", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index d77e77440e..46c229bfd3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -112,6 +112,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -337,7 +338,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-01-20 23:25:45.363281", + "modified": "2023-11-14 18:38:37.640677", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 38432beb44..26a29dd811 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -109,6 +109,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -521,7 +522,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-09-03 17:04:21.214316", + "modified": "2023-11-14 18:38:26.459669", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 908b21f7fd8d0c62ffd51eb2ca5238225d0f52ad Mon Sep 17 00:00:00 2001 From: Arjun Date: Wed, 15 Nov 2023 12:34:38 +0530 Subject: [PATCH 099/163] fix: duplicate field in `Closing Stock Balance` (#38105) --- .../closing_stock_balance/closing_stock_balance.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json index 225da6d15e..0c4757ffad 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json @@ -103,15 +103,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Closing Stock Balance", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "include_uom", "fieldtype": "Link", @@ -145,4 +136,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From 2499675ad121ab370f672e6f9ffdcda3c8c76b8e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 15 Nov 2023 13:32:23 +0530 Subject: [PATCH 100/163] fix: test total unallocated amount in payment --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 5af37b6a5e..f4b0c55313 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1347,6 +1347,8 @@ class TestPaymentEntry(FrappeTestCase): ) pe.save() pe.submit() + self.assertEqual(pe.total_allocated_amount, 60) + self.assertEqual(pe.unallocated_amount, 940) self.voucher_no = pe.name self.expected_gle = [ {"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0}, From 6e0362dee85811267edff53f23a24b48ebd91c11 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 15 Nov 2023 17:58:37 +0530 Subject: [PATCH 101/163] chore: change read only condition of asset quantity field (#38111) chore: change read only condition of asset quantity --- erpnext/assets/doctype/asset/asset.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 40f51ab570..d6b9c461cb 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -481,11 +481,10 @@ "read_only": 1 }, { - "depends_on": "eval.doc.asset_quantity", "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", - "read_only": 1 + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset" }, { "fieldname": "depr_entry_posting_status", @@ -572,7 +571,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-10-27 17:03:46.629617", + "modified": "2023-11-15 17:40:17.315203", "modified_by": "Administrator", "module": "Assets", "name": "Asset", From 426c245032bccc93b79d0505f853bc25ae51f37e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 14 Nov 2023 11:13:05 +0100 Subject: [PATCH 102/163] fix(dn): regression from bulk transaction fix --- erpnext/selling/doctype/sales_order/sales_order.py | 7 +++++-- erpnext/utilities/bulk_transaction.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e4f1a28316..a97198aa78 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -767,8 +767,11 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): if target.company_address: target.update(get_fetch_values("Delivery Note", "company_address", target.company_address)) - # set target items names to ensure proper linking with packed_items - target.set_new_name() + # if invoked in bulk creation, validations are ignored and thus this method is nerver invoked + if frappe.flags.bulk_transaction: + # set target items names to ensure proper linking with packed_items + target.set_new_name() + make_packing_list(target) def condition(doc): diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index fcee265644..56f3b41c2e 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -98,6 +98,7 @@ def task(doc_name, from_doctype, to_doctype): }, "Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice}, } + frappe.flags.bulk_transaction = True if to_doctype in ["Payment Entry"]: obj = mapper[from_doctype][to_doctype](from_doctype, doc_name) else: @@ -106,6 +107,7 @@ def task(doc_name, from_doctype, to_doctype): obj.flags.ignore_validate = True obj.set_title_field() obj.insert(ignore_mandatory=True) + del frappe.flags.bulk_transaction def check_logger_doc_exists(log_date): From da80e4dbce07707d57e3739b7bb2138adf199905 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 16 Nov 2023 13:05:49 +0530 Subject: [PATCH 103/163] feat: add `Supplier Delivery Note` field in SCR --- .../subcontracting_receipt/subcontracting_receipt.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 8be1c1ba97..383a83b3fc 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -11,6 +11,7 @@ "naming_series", "supplier", "supplier_name", + "supplier_delivery_note", "column_break1", "company", "posting_date", @@ -634,12 +635,17 @@ "fieldtype": "Button", "label": "Get Scrap Items", "options": "get_scrap_items" + }, + { + "fieldname": "supplier_delivery_note", + "fieldtype": "Data", + "label": "Supplier Delivery Note" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-26 10:52:04.050829", + "modified": "2023-11-16 13:04:00.710534", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From 2df767f596417143ae3b855b2252585bf2bc96f9 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 16 Nov 2023 16:01:35 +0530 Subject: [PATCH 104/163] fix: bom creator not able to amend / duplicate (#38128) fix: bom creator not able to amend --- .../doctype/bom_creator/bom_creator.js | 2 +- .../doctype/bom_creator/bom_creator.py | 14 +++++++++++--- .../doctype/bom_creator_item/bom_creator_item.json | 3 +-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js index 0cf2b51df2..243e52df5b 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js @@ -15,7 +15,7 @@ frappe.ui.form.on("BOM Creator", { || frappe.bom_configurator.bom_configurator !== frm.doc.name)) { frm.trigger("build_tree"); } - } else { + } else if (!frm.doc.items?.length ) { let $parent = $(frm.fields_dict["bom_creator"].wrapper); $parent.empty(); frm.trigger("make_new_entry"); diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 058caa3686..49041a0929 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -6,7 +6,7 @@ from collections import OrderedDict import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt +from frappe.utils import cint, flt from erpnext.manufacturing.doctype.bom.bom import get_bom_item_rate @@ -91,11 +91,19 @@ class BOMCreator(Document): parent_reference = {row.idx: row.name for row in self.items} for row in self.items: - if row.fg_reference_id: + ref_id = "" + + if row.parent_row_no: + ref_id = parent_reference.get(cint(row.parent_row_no)) + + # Check whether the reference id of the FG Item has correct or not + if row.fg_reference_id and row.fg_reference_id == ref_id: continue if row.parent_row_no: - row.fg_reference_id = parent_reference.get(row.parent_row_no) + row.fg_reference_id = ref_id + elif row.fg_item == self.item_code: + row.fg_reference_id = self.name @frappe.whitelist() def add_boms(self): diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json index fdb5d3ad33..56acd8a1a6 100644 --- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json +++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json @@ -215,7 +215,6 @@ "fieldname": "parent_row_no", "fieldtype": "Data", "label": "Parent Row No", - "no_copy": 1, "print_hide": 1 }, { @@ -231,7 +230,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-07 11:52:30.492233", + "modified": "2023-11-16 13:34:06.321061", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator Item", From 134201794ace2863c02458b3e1797da816802d06 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Nov 2023 10:04:50 +0530 Subject: [PATCH 105/163] fix: add revaluation journal filter in Payable report --- erpnext/accounts/report/accounts_payable/accounts_payable.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 10362dba3d..b608ebc395 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -149,6 +149,11 @@ frappe.query_reports["Accounts Payable"] = { "label": __("In Party Currency"), "fieldtype": "Check", }, + { + "fieldname": "for_revaluation_journals", + "label": __("Revaluation Journals"), + "fieldtype": "Check", + }, { "fieldname": "ignore_accounts", "label": __("Group by Voucher"), From b27af6b5c85a99d22060ac85b9ba37893317bfbb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 17 Nov 2023 12:36:57 +0530 Subject: [PATCH 106/163] fix: show party instead of party name where naming series not set --- .../tax_withholding_details/tax_withholding_details.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 06c9e44b45..f6c7bd3db7 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -184,6 +184,16 @@ def get_columns(filters): "width": 180, } ) + else: + columns.append( + { + "label": _(filters.get("party_type")), + "fieldname": "party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "width": 180, + } + ) columns.extend( [ From 089da459f73791b86d33cf066e6879e77af200b5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 Nov 2023 12:37:14 +0530 Subject: [PATCH 107/163] feat: Add accounting dimensions to Supplier Quotation --- .../supplier_quotation.json | 27 ++++++++++++++++++- .../supplier_quotation_item.json | 15 +++++++++-- erpnext/hooks.py | 2 ++ erpnext/patches.txt | 1 + ...unting_dimensions_in_supplier_quotation.py | 8 ++++++ 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index ad1aa2b108..18912610ce 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -20,6 +20,10 @@ "valid_till", "quotation_number", "amended_from", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", "currency_and_price_list", "currency", "conversion_rate", @@ -896,6 +900,27 @@ "fieldtype": "Small Text", "label": "Billing Address Details", "read_only": 1 + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" } ], "icon": "fa fa-shopping-cart", @@ -903,7 +928,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-11-03 13:21:40.172508", + "modified": "2023-11-17 12:34:30.083077", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index 4bbcacfef3..a6229b5950 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -68,6 +68,8 @@ "column_break_15", "manufacturer_part_no", "ad_sec_break", + "cost_center", + "dimension_col_break", "project", "section_break_44", "page_break" @@ -133,7 +135,6 @@ "fieldtype": "Column Break" }, { - "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -554,13 +555,23 @@ "fieldname": "expected_delivery_date", "fieldtype": "Date", "label": "Expected Delivery Date" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:35:03.435817", + "modified": "2023-11-17 12:25:26.235367", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation Item", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5483a10b57..55ffaca419 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -539,6 +539,8 @@ accounting_dimension_doctypes = [ "Subcontracting Receipt", "Subcontracting Receipt Item", "Account Closing Balance", + "Supplier Quotation", + "Supplier Quotation Item", ] get_matching_queries = ( diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0aeadce6c8..6c10dd9bbb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -350,5 +350,6 @@ erpnext.patches.v14_0.add_default_for_repost_settings erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based erpnext.patches.v15_0.set_reserved_stock_in_bin +erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py new file mode 100644 index 0000000000..6966db1fd7 --- /dev/null +++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_supplier_quotation.py @@ -0,0 +1,8 @@ +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + create_accounting_dimensions_for_doctype, +) + + +def execute(): + create_accounting_dimensions_for_doctype(doctype="Supplier Quotation") + create_accounting_dimensions_for_doctype(doctype="Supplier Quotation Item") From 7e43d7b1312fd665bdcde7db7c67d192f746e60a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 17 Nov 2023 12:38:08 +0530 Subject: [PATCH 108/163] fix: allow regional gl in payment entry for gl preview (#38136) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 448224b3a7..ef304bc110 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1022,6 +1022,7 @@ class PaymentEntry(AccountsController): self.add_bank_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries) + add_regional_gl_entries(gl_entries, self) return gl_entries def make_gl_entries(self, cancel=0, adv_adj=0): @@ -2621,3 +2622,8 @@ def make_payment_order(source_name, target_doc=None): ) return doclist + + +@erpnext.allow_regional +def add_regional_gl_entries(gl_entries, doc): + return From 7842c9fba8b521b9ba7f65281c4f2384f62b06e1 Mon Sep 17 00:00:00 2001 From: kunhi Date: Fri, 17 Nov 2023 11:22:08 +0400 Subject: [PATCH 109/163] fix: issue occured when creating supplier with contact details --- erpnext/selling/doctype/customer/customer.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 459fc9fcff..d4702e8fb9 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -629,8 +629,12 @@ def make_contact(args, is_primary_contact=1): "is_primary_contact": is_primary_contact, "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], } - if args.customer_type == "Individual": - first, middle, last = parse_full_name(args.get("customer_name")) + + party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type + party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" + + if party_type == "Individual": + first, middle, last = parse_full_name(args.get(party_name_key)) values.update( { "first_name": first, @@ -641,9 +645,10 @@ def make_contact(args, is_primary_contact=1): else: values.update( { - "company_name": args.get("customer_name"), + "company_name": args.get(party_name_key), } ) + contact = frappe.get_doc(values) if args.get("email_id"): From 545ef3c23491e895bf288ddf7666ea80251b41e7 Mon Sep 17 00:00:00 2001 From: Kunhi Date: Fri, 17 Nov 2023 11:43:36 +0400 Subject: [PATCH 110/163] fix: Suppier name was not taken when creating address from supplier --- erpnext/selling/doctype/customer/customer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d4702e8fb9..1db07972bb 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -677,10 +677,12 @@ def make_address(args, is_primary_address=1, is_shipping_address=1): title=_("Missing Values Required"), ) + party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" + address = frappe.get_doc( { "doctype": "Address", - "address_title": args.get("customer_name"), + "address_title": args.get(party_name_key), "address_line1": args.get("address_line1"), "address_line2": args.get("address_line2"), "city": args.get("city"), From 20c6e9fca29d80016cefb83deee4e44ac9bc1d47 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 17 Nov 2023 18:08:55 +0530 Subject: [PATCH 111/163] fix(Timesheet): reset billing hours equal to hours if they exceed actual hours (#38134) --- erpnext/projects/doctype/timesheet/timesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 11156f4b50..8e464b5c6b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -69,7 +69,7 @@ class Timesheet(Document): def update_billing_hours(self, args): if args.is_billable: - if flt(args.billing_hours) == 0.0: + if flt(args.billing_hours) == 0.0 or flt(args.billing_hours) > flt(args.hours): args.billing_hours = args.hours else: args.billing_hours = 0 From 3a51a3f37ecd70c13cee558a0b882d06e812a21c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Nov 2023 17:34:21 +0530 Subject: [PATCH 112/163] refactor: convert payment reconciliation tool to virtual doctype --- .../payment_reconciliation/payment_reconciliation.json | 6 +++--- .../payment_reconciliation_allocation.json | 3 ++- .../payment_reconciliation_invoice.json | 3 ++- .../payment_reconciliation_payment.json | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index b88791d3f9..ccb9e648cb 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -212,9 +212,10 @@ ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", + "is_virtual": 1, "issingle": 1, "links": [], - "modified": "2023-08-15 05:35:50.109290", + "modified": "2023-11-17 17:33:55.701726", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", @@ -239,6 +240,5 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [], - "track_changes": 1 + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 5b8556e7c8..491c67818d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -159,9 +159,10 @@ "label": "Difference Posting Date" } ], + "is_virtual": 1, "istable": 1, "links": [], - "modified": "2023-10-23 10:44:56.066303", + "modified": "2023-11-17 17:33:38.612615", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json index c4dbd7e844..7c9d49e773 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json @@ -71,9 +71,10 @@ "label": "Exchange Rate" } ], + "is_virtual": 1, "istable": 1, "links": [], - "modified": "2022-11-08 18:18:02.502149", + "modified": "2023-11-17 17:33:45.455166", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Invoice", diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json index 17f3900880..d199236ae9 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -107,9 +107,10 @@ "options": "Cost Center" } ], + "is_virtual": 1, "istable": 1, "links": [], - "modified": "2023-09-03 07:43:29.965353", + "modified": "2023-11-17 17:33:34.818530", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Payment", From 9c7b19e0b7732189c9db12e2c67b12a67fe562b3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 18 Nov 2023 07:48:15 +0530 Subject: [PATCH 113/163] refactor: virtual doctype methods --- .../payment_reconciliation.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 43167be15a..6673e8de28 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -29,6 +29,58 @@ class PaymentReconciliation(Document): self.accounting_dimension_filter_conditions = [] self.ple_posting_date_filter = [] + def load_from_db(self): + # 'modified' attribute is required for `run_doc_method` to work properly. + doc_dict = frappe._dict( + { + "modified": None, + "company": None, + "party": None, + "party_type": None, + "receivable_payable_account": None, + "default_advance_account": None, + "from_invoice_date": None, + "to_invoice_date": None, + "invoice_limit": 50, + "from_payment_date": None, + "to_payment_date": None, + "payment_limit": 50, + "minimum_invoice_amount": None, + "minimum_payment_amount": None, + "maximum_invoice_amount": None, + "maximum_payment_amount": None, + "bank_cash_account": None, + "cost_center": None, + "payment_name": None, + "invoice_name": None, + } + ) + super(Document, self).__init__(doc_dict) + + def save(self): + return + + @staticmethod + def get_list(args): + pass + + @staticmethod + def get_count(args): + pass + + @staticmethod + def get_stats(args): + pass + + def db_insert(self, *args, **kwargs): + pass + + def db_update(self, *args, **kwargs): + pass + + def delete(self): + pass + @frappe.whitelist() def get_unreconciled_entries(self): self.get_nonreconciled_payment_entries() From b5dd0c8630c59cc38011039e7c7a21976b53ebd8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 18 Nov 2023 08:07:36 +0530 Subject: [PATCH 114/163] chore: remove reconciliation defaults from patch --- erpnext/patches.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0aeadce6c8..2935a16d8e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -344,8 +344,6 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item erpnext.patches.v15_0.update_sre_from_voucher_details erpnext.patches.v14_0.rename_over_order_allowance_field erpnext.patches.v14_0.migrate_delivery_stop_lock_field -execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50) -execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50) erpnext.patches.v14_0.add_default_for_repost_settings erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based From f31002636bf21a3599ee5a7372db4d0cb3dbfad9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 18 Nov 2023 08:10:09 +0530 Subject: [PATCH 115/163] chore: clear singles table and reconciliation related tables --- erpnext/patches.txt | 1 + .../clear_reconciliation_values_from_singles.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2935a16d8e..1e988c5a00 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -345,6 +345,7 @@ erpnext.patches.v15_0.update_sre_from_voucher_details erpnext.patches.v14_0.rename_over_order_allowance_field erpnext.patches.v14_0.migrate_delivery_stop_lock_field erpnext.patches.v14_0.add_default_for_repost_settings +erpnext.patches.v14_0.clear_reconciliation_values_from_singles erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based erpnext.patches.v15_0.set_reserved_stock_in_bin diff --git a/erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py b/erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py new file mode 100644 index 0000000000..c1f5b60a40 --- /dev/null +++ b/erpnext/patches/v14_0/clear_reconciliation_values_from_singles.py @@ -0,0 +1,17 @@ +from frappe import qb + + +def execute(): + """ + Clear `tabSingles` and Payment Reconciliation tables of values + """ + singles = qb.DocType("Singles") + qb.from_(singles).delete().where(singles.doctype == "Payment Reconciliation").run() + doctypes = [ + "Payment Reconciliation Invoice", + "Payment Reconciliation Payment", + "Payment Reconciliation Allocation", + ] + for x in doctypes: + dt = qb.DocType(x) + qb.from_(dt).delete().run() From 434c2a1815f27c59de5cd94528bc371593bdd402 Mon Sep 17 00:00:00 2001 From: PatrickDenis-stack <77415730+PatrickDenis-stack@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:16:06 +0100 Subject: [PATCH 116/163] fix: attributes were mandatory for manufacturers --- erpnext/stock/doctype/item/item.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index c13d3ebe0f..6e93f5624f 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -504,7 +504,7 @@ "fieldtype": "Table", "hidden": 1, "label": "Variant Attributes", - "mandatory_depends_on": "has_variants", + "mandatory_depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", "options": "Item Variant Attribute" }, { From 45299fe4b3e23774920d7c35f7fb66e786cf5aa5 Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Sat, 18 Nov 2023 17:10:01 +0530 Subject: [PATCH 117/163] fix: pass check permission in render_address --- erpnext/accounts/party.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 371474e0a6..5c18e506f5 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -236,7 +236,9 @@ def set_address_details( if shipping_address: party_details.update( shipping_address=shipping_address, - shipping_address_display=render_address(shipping_address), + shipping_address_display=render_address( + shipping_address, check_permissions=not ignore_permissions + ), **get_fetch_values(doctype, "shipping_address", shipping_address) ) From 3a487bd33af1972d9ee8b7bb2f6277775c8e018e Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 18 Nov 2023 13:32:04 +0000 Subject: [PATCH 118/163] fix: wrong round off and rounded total --- erpnext/controllers/taxes_and_totals.py | 1 + erpnext/public/js/controllers/taxes_and_totals.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 96284d612f..f9f68a119b 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -54,6 +54,7 @@ class calculate_taxes_and_totals(object): if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): self.doc.grand_total -= self.doc.discount_amount self.doc.base_grand_total -= self.doc.base_discount_amount + self.doc.rounding_adjustment = self.doc.base_rounding_adjustment = 0.0 self.set_rounded_total() self.calculate_shipping_charges() diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 6b613ce9ec..d24c4e6075 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -43,6 +43,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { this.frm.doc.grand_total -= this.frm.doc.discount_amount; this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount; + this.frm.doc.rounding_adjustment = 0; + this.frm.doc.base_rounding_adjustment = 0; + this.set_rounded_total(); } await this.calculate_shipping_charges(); From 3d1e3a9cded735c420cb6c0ceb17c62034983076 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 18 Nov 2023 14:36:20 +0000 Subject: [PATCH 119/163] fix: payment entry rounding error --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index b3ae627c6e..9a6f8ec8ac 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -853,6 +853,7 @@ frappe.ui.form.on('Payment Entry', { var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding; } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { + total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount")) if(paid_amount > total_negative_outstanding) { if(total_negative_outstanding == 0) { frappe.msgprint( From 969616ed095ccf74693c66729c015852fb0e8f5e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:43:23 +0100 Subject: [PATCH 120/163] fix: update modified timestamp Was missing in 434c2a1815f27c59de5cd94528bc371593bdd402 --- erpnext/stock/doctype/item/item.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 6e93f5624f..13f3be8c36 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -888,7 +888,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-09-11 13:46:32.688051", + "modified": "2023-09-18 15:41:32.688051", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 32f622ef8061f7db50b4c8f63134f947afdcb306 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Sat, 18 Nov 2023 15:54:34 +0100 Subject: [PATCH 121/163] fix: valuation rate in report Item Prices (#38161) Co-authored-by: PatrickDenis-stack <77415730+PatrickDenis-stack@users.noreply.github.com> --- erpnext/stock/report/item_prices/item_prices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py index ab47b4a8b9..a53a9f24f5 100644 --- a/erpnext/stock/report/item_prices/item_prices.py +++ b/erpnext/stock/report/item_prices/item_prices.py @@ -202,7 +202,7 @@ def get_valuation_rate(): bin_data = ( frappe.qb.from_(bin) .select( - bin.item_code, Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty).as_("val_rate") + bin.item_code, (Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty)).as_("val_rate") ) .where(bin.actual_qty > 0) .groupby(bin.item_code) From ff5b1b7dedccc23ed7ba71776e5d3d9c385fd222 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sat, 18 Nov 2023 18:45:14 +0100 Subject: [PATCH 122/163] style: remove trailing whitespace (#38183) --- erpnext/selling/doctype/customer/customer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 1db07972bb..88ed1c6667 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -633,7 +633,7 @@ def make_contact(args, is_primary_contact=1): party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" - if party_type == "Individual": + if party_type == "Individual": first, middle, last = parse_full_name(args.get(party_name_key)) values.update( { @@ -648,7 +648,7 @@ def make_contact(args, is_primary_contact=1): "company_name": args.get(party_name_key), } ) - + contact = frappe.get_doc(values) if args.get("email_id"): From cc60c328fe349f7d53c77799e8e91973c2790746 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sun, 19 Nov 2023 02:53:09 +0000 Subject: [PATCH 123/163] fix: test case for rounded total with cash disc --- .../sales_invoice/test_sales_invoice.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 5a41336b2f..017bfa9654 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -789,6 +789,28 @@ class TestSalesInvoice(FrappeTestCase): w = self.make() self.assertEqual(w.outstanding_amount, w.base_rounded_total) + def test_rounded_total_with_cash_discount(self): + si = frappe.copy_doc(test_records[2]) + + item = copy.deepcopy(si.get("items")[0]) + item.update( + { + "qty": 1, + "rate": 14960.66, + } + ) + + si.set("items", [item]) + si.set("taxes", []) + si.apply_discount_on = "Grand Total" + si.is_cash_or_non_trade_discount = 1 + si.discount_amount = 1 + si.insert() + + self.assertEqual(si.grand_total, 14959.66) + self.assertEqual(si.rounded_total, 14960) + self.assertEqual(si.rounding_adjustment, 0.34) + def test_payment(self): w = self.make() From 97090ff3679104d77a031f29d4acafb8b7ac1580 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 18 Nov 2023 09:04:36 +0530 Subject: [PATCH 124/163] refactor: provision to set `remarks` length in accounts settings --- .../accounts_settings/accounts_settings.json | 37 ++++++++++++++++++- .../report/general_ledger/general_ledger.py | 7 +++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 061bab320e..fd052d0476 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -66,7 +66,12 @@ "show_balance_in_coa", "banking_tab", "enable_party_matching", - "enable_fuzzy_matching" + "enable_fuzzy_matching", + "reports_tab", + "remarks_section", + "general_ledger_remarks_length", + "column_break_lvjk", + "receivable_payable_remarks_length" ], "fields": [ { @@ -422,6 +427,34 @@ "fieldname": "round_row_wise_tax", "fieldtype": "Check", "label": "Round Tax Amount Row-wise" + }, + { + "fieldname": "reports_tab", + "fieldtype": "Tab Break", + "label": "Reports" + }, + { + "default": "0", + "description": "Truncates 'Remarks' column to set character length", + "fieldname": "general_ledger_remarks_length", + "fieldtype": "Int", + "label": "General Ledger" + }, + { + "default": "0", + "description": "Truncates 'Remarks' column to set character length", + "fieldname": "receivable_payable_remarks_length", + "fieldtype": "Int", + "label": "Accounts Receivable/Payable" + }, + { + "fieldname": "column_break_lvjk", + "fieldtype": "Column Break" + }, + { + "fieldname": "remarks_section", + "fieldtype": "Section Break", + "label": "Remarks Column Length" } ], "icon": "icon-cog", @@ -429,7 +462,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-28 00:12:02.740633", + "modified": "2023-11-20 09:37:47.650347", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 94cd293615..fa557a133f 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -164,7 +164,12 @@ def get_gl_entries(filters, accounting_dimensions): credit_in_account_currency """ if filters.get("show_remarks"): - select_fields += """,remarks""" + if remarks_length := frappe.db.get_single_value( + "Accounts Settings", "general_ledger_remarks_length" + ): + select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'" + else: + select_fields += """,remarks""" order_by_statement = "order by posting_date, account, creation" From a9bf906545dc7c89613c7f6211c35e62b8d7b989 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 18 Nov 2023 10:35:56 +0530 Subject: [PATCH 125/163] refactor: add substring logic in ar/ap report --- .../report/accounts_receivable/accounts_receivable.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 706d743849..0e62ad61cc 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -7,7 +7,7 @@ from collections import OrderedDict import frappe from frappe import _, qb, scrub from frappe.query_builder import Criterion -from frappe.query_builder.functions import Date, Sum +from frappe.query_builder.functions import Date, Substring, Sum from frappe.utils import cint, cstr, flt, getdate, nowdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -764,7 +764,12 @@ class ReceivablePayableReport(object): ) if self.filters.get("show_remarks"): - query = query.select(ple.remarks) + if remarks_length := frappe.db.get_single_value( + "Accounts Settings", "receivable_payable_remarks_length" + ): + query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks")) + else: + query = query.select(ple.remarks) if self.filters.get("group_by_party"): query = query.orderby(self.ple.party, self.ple.posting_date) From f6e93f084aadf82f444edb112cc9832b53294b15 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 20 Nov 2023 10:23:35 +0530 Subject: [PATCH 126/163] fix: TypeError in Subcontracting Receipt --- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 2 +- 1 file changed, 1 insertion(+), 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 0a4cae7b34..4b8d9657af 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 @@ -406,7 +406,7 @@ class SerialandBatchBundle(Document): if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01: self.throw_error_message( - f"Total quantity {abs(self.total_qty)} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(row.get(qty_field))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}" + f"Total quantity {abs(flt(self.total_qty))} in the Serial and Batch Bundle {bold(self.name)} does not match with the quantity {abs(flt(row.get(qty_field)))} for the Item {bold(self.item_code)} in the {self.voucher_type} # {self.voucher_no}" ) def set_is_outward(self): From 331ad62f3c27a54e9d6bb49f1b70b169b633b3a6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 20 Nov 2023 12:03:54 +0530 Subject: [PATCH 127/163] fix(ux): enable `Quick Entry` for `Task` --- erpnext/projects/doctype/task/task.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 25a5455ac1..4d2d225242 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -57,6 +57,7 @@ ], "fields": [ { + "allow_in_quick_entry": 1, "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, @@ -66,6 +67,7 @@ "search_index": 1 }, { + "allow_in_quick_entry": 1, "bold": 1, "fieldname": "project", "fieldtype": "Link", @@ -396,7 +398,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2023-09-28 13:52:05.861175", + "modified": "2023-11-20 11:42:41.884069", "modified_by": "Administrator", "module": "Projects", "name": "Task", @@ -416,6 +418,7 @@ "write": 1 } ], + "quick_entry": 1, "search_fields": "subject", "show_name_in_global_search": 1, "show_preview_popup": 1, From 2f3fc12c08218ca06fbb911781090e619a88cf22 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 20 Nov 2023 12:05:25 +0530 Subject: [PATCH 128/163] fix: add route options for new `Task` --- erpnext/projects/doctype/timesheet/timesheet.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index d1d07a79d6..eb7a97e615 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -111,6 +111,7 @@ frappe.ui.form.on("Timesheet", { frm.trigger('setup_filters'); frm.trigger('set_dynamic_field_label'); + frm.trigger('set_route_options_for_new_task'); }, customer: function(frm) { @@ -172,6 +173,14 @@ frappe.ui.form.on("Timesheet", { frm.refresh_fields(); }, + set_route_options_for_new_task: (frm) => { + let task_field = frm.get_docfield('time_logs', 'task'); + + if (task_field) { + task_field.get_route_options_for_new_doc = (row) => ({'project': row.doc.project}); + } + }, + make_invoice: function(frm) { let fields = [{ "fieldtype": "Link", From ee0c64215d160f0bd493bcefc9e94a5a90d318d2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Nov 2023 11:52:26 +0530 Subject: [PATCH 129/163] refactor: set default for 'update_billed_amount_in_delivery_note' --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 4 ++-- erpnext/controllers/sales_and_purchase_return.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index cd725b9862..d1677832ed 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2156,7 +2156,7 @@ "label": "Use Company default Cost Center for Round off" }, { - "default": "0", + "default": "1", "depends_on": "eval: doc.is_return", "fieldname": "update_billed_amount_in_delivery_note", "fieldtype": "Check", @@ -2173,7 +2173,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-11-03 14:39:38.012346", + "modified": "2023-11-20 11:51:43.555197", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 165e17b2d7..e91212b031 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -356,6 +356,7 @@ def make_return_doc( if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice": doc.consolidated_invoice = "" doc.set("payments", []) + doc.update_billed_amount_in_delivery_note = True for data in source.payments: paid_amount = 0.00 base_paid_amount = 0.00 From 83a13e22b7f4aa8ba2c406389af4e7fa8aad55d0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Nov 2023 12:27:43 +0530 Subject: [PATCH 130/163] refactor: add flag to POS Invoice --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index f6047079ff..955b66a1b8 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -18,6 +18,7 @@ "is_pos", "is_return", "update_billed_amount_in_sales_order", + "update_billed_amount_in_delivery_note", "column_break1", "company", "posting_date", @@ -1550,12 +1551,19 @@ "fieldtype": "Currency", "label": "Amount Eligible for Commission", "read_only": 1 + }, + { + "default": "1", + "depends_on": "eval: doc.is_return && doc.return_against", + "fieldname": "update_billed_amount_in_delivery_note", + "fieldtype": "Check", + "label": "Update Billed Amount in Delivery Note" } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:23:41.083409", + "modified": "2023-11-20 12:27:12.848149", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", From fb06ad7330fd31bf1def7b87a6c3b787650f1555 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Nov 2023 13:26:02 +0530 Subject: [PATCH 131/163] refactor: update scheduled job for bulk transaction --- erpnext/hooks.py | 2 +- erpnext/utilities/bulk_transaction.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5483a10b57..b22a3f76cb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -421,7 +421,7 @@ scheduler_events = { "hourly_long": [ "erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process", "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", - "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", + "erpnext.utilities.bulk_transaction.retry", ], "daily": [ "erpnext.support.doctype.issue.issue.auto_close_tickets", diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 57c2f9d787..df21b61139 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -30,7 +30,10 @@ def transaction_processing(data, from_doctype, to_doctype): @frappe.whitelist() -def retry(date: str | None): +def retry(date: str | None = None): + if not date: + date = today() + if date: failed_docs = frappe.db.get_all( "Bulk Transaction Log Detail", From 9903049c7ac7310ca9e3f69c30dc355c2e13bfd5 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 20 Nov 2023 22:27:16 +0530 Subject: [PATCH 132/163] fix: set default asset quantity as 1 [dev] (#38223) * fix: make default asset quantity as 1 * fix: get rate_of_depreciation from asset category for asset auto-creation * chore: create asset depr schedules on PR submit, not asset submit * fix: set default asset quantity as 1 * chore: move patch from v15 to v14 --- erpnext/assets/doctype/asset/asset.json | 3 +- erpnext/assets/doctype/asset/asset.py | 35 +++++++++++-------- erpnext/controllers/buying_controller.py | 2 +- erpnext/patches.txt | 1 + .../v14_0/update_zero_asset_quantity_field.py | 6 ++++ 5 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 erpnext/patches/v14_0/update_zero_asset_quantity_field.py diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index d6b9c461cb..540a4f5549 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -481,6 +481,7 @@ "read_only": 1 }, { + "default": "1", "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", @@ -571,7 +572,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-11-15 17:40:17.315203", + "modified": "2023-11-20 20:57:37.010467", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3c570d1af0..7b7953b6ce 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -46,12 +46,28 @@ class Asset(AccountsController): self.validate_item() self.validate_cost_center() self.set_missing_values() - self.validate_finance_books() - if not self.split_from: - self.prepare_depreciation_data() - update_draft_asset_depr_schedules(self) self.validate_gross_and_purchase_amount() self.validate_expected_value_after_useful_life() + self.validate_finance_books() + + if not self.split_from: + self.prepare_depreciation_data() + + if self.calculate_depreciation: + update_draft_asset_depr_schedules(self) + + if frappe.db.exists("Asset", self.name): + asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self) + + if asset_depr_schedules_names: + asset_depr_schedules_links = get_comma_separated_links( + asset_depr_schedules_names, "Asset Depreciation Schedule" + ) + frappe.msgprint( + _( + "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." + ).format(asset_depr_schedules_links) + ) self.status = self.get_status() @@ -61,17 +77,7 @@ class Asset(AccountsController): if not self.booked_fixed_asset and self.validate_make_gl_entry(): self.make_gl_entries() if self.calculate_depreciation and not self.split_from: - asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self) convert_draft_asset_depr_schedules_into_active(self) - if asset_depr_schedules_names: - asset_depr_schedules_links = get_comma_separated_links( - asset_depr_schedules_names, "Asset Depreciation Schedule" - ) - frappe.msgprint( - _( - "Asset Depreciation Schedules created:
{0}

Please check, edit if needed, and submit the Asset." - ).format(asset_depr_schedules_links) - ) self.set_status() add_asset_activity(self.name, _("Asset submitted")) @@ -823,6 +829,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount): "expected_value_after_useful_life": flt(gross_purchase_amount) * flt(d.salvage_value_percentage / 100), "depreciation_start_date": d.depreciation_start_date or nowdate(), + "rate_of_depreciation": d.rate_of_depreciation, } ) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a470b47a45..68ad97d7ba 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -758,7 +758,7 @@ class BuyingController(SubcontractingController): "calculate_depreciation": 0, "purchase_receipt_amount": purchase_amount, "gross_purchase_amount": purchase_amount, - "asset_quantity": row.qty if is_grouped_asset else 0, + "asset_quantity": row.qty if is_grouped_asset else 1, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None, } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6c10dd9bbb..0badab56ea 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -351,5 +351,6 @@ erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_ erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based erpnext.patches.v15_0.set_reserved_stock_in_bin erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation +erpnext.patches.v14_0.update_zero_asset_quantity_field # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/update_zero_asset_quantity_field.py b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py new file mode 100644 index 0000000000..0480f9b7aa --- /dev/null +++ b/erpnext/patches/v14_0/update_zero_asset_quantity_field.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + asset = frappe.qb.DocType("Asset") + frappe.qb.update(asset).set(asset.asset_quantity, 1).where(asset.asset_quantity == 0).run() From 56e991b7f49e4677605f74b1de7d00ab40bf09cc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 21 Nov 2023 11:46:45 +0530 Subject: [PATCH 133/163] test: prevent rounding loss based validation error --- .../test_payment_reconciliation.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 71bc498b49..d7a73f0ce7 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1137,6 +1137,40 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pay.unallocated_amount, 1000) self.assertEqual(pay.difference_amount, 0) + def test_rounding_of_unallocated_amount(self): + self.supplier = "_Test Supplier USD" + pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True) + pi.supplier = self.supplier + pi.currency = "USD" + pi.conversion_rate = 80 + pi.credit_to = self.creditors_usd + pi.save().submit() + + pe = get_payment_entry(pi.doctype, pi.name) + pe.target_exchange_rate = 78.726500000 + pe.received_amount = 26.75 + pe.paid_amount = 2105.93 + pe.references = [] + pe.save().submit() + + # unallocated_amount will have some rounding loss - 26.749950 + self.assertNotEqual(pe.unallocated_amount, 26.75) + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Supplier" + pr.party = self.supplier + pr.receivable_payable_account = self.creditors_usd + pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() + pr.get_unreconciled_entries() + + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + + # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again. + pr.reconcile() + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From ac91030b31f1108b7e32844f12a3a6c916c0120f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 21 Nov 2023 13:18:34 +0530 Subject: [PATCH 134/163] fix(Timesheet): warn user if billing hours > actual hours instead of resetting (#38239) * revert: "fix(Timesheet): reset billing hours equal to hours if they exceed actual hours" This reverts commit 0ec8034507996f06eaf8ca13a414d10b34038c6c. * fix: warn user if billing hours > actual hours --- erpnext/projects/doctype/timesheet/timesheet.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 8e464b5c6b..b9d801ce90 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -69,8 +69,14 @@ class Timesheet(Document): def update_billing_hours(self, args): if args.is_billable: - if flt(args.billing_hours) == 0.0 or flt(args.billing_hours) > flt(args.hours): + if flt(args.billing_hours) == 0.0: args.billing_hours = args.hours + elif flt(args.billing_hours) > flt(args.hours): + frappe.msgprint( + _("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx), + indicator="orange", + alert=True, + ) else: args.billing_hours = 0 From 5c308a4f9aa1a27cbd216addbe495b9bf69735b1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 21 Nov 2023 14:42:26 +0530 Subject: [PATCH 135/163] fix: valuation rate for FG item for subcontracting receipt (#38244) --- .../serial_and_batch_bundle.py | 2 +- .../test_subcontracting_receipt.py | 98 +++++++++++++++++++ 2 files changed, 99 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 4b8d9657af..abdbeb496b 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 @@ -246,7 +246,7 @@ class SerialandBatchBundle(Document): valuation_field = "rate" child_table = "Subcontracting Receipt Supplied Item" else: - valuation_field = "rm_supp_cost" + valuation_field = "rate" child_table = "Subcontracting Receipt Item" precision = frappe.get_precision(child_table, valuation_field) or 2 diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 6191a8ca94..f0e4e00074 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -667,6 +667,104 @@ class TestSubcontractingReceipt(FrappeTestCase): "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0 ) + def test_subcontracting_receipt_valuation_for_fg_with_auto_created_serial_batch_bundle(self): + set_backflush_based_on("BOM") + + fg_item = make_item( + properties={ + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BSSNGS-.####", + } + ).name + + rm_item1 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + } + ).name + + rm_item2 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "has_serial_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + "serial_no_series": "BNSS-.####", + } + ).name + + rm_item3 = make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "BSSSS-.####", + } + ).name + + bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3]) + + rm_batch_no = None + for row in bom.items: + make_stock_entry( + item_code=row.item_code, + qty=1, + target="_Test Warehouse 1 - _TC", + rate=300, + ) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 1, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + + frappe.db.set_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1 + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + scr.submit() + scr.reload() + + for row in scr.supplied_items: + self.assertEqual(row.rate, 300.00) + self.assertTrue(row.serial_and_batch_bundle) + auto_created_serial_batch = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": scr.name, "voucher_detail_no": row.name}, + "auto_created_serial_and_batch_bundle", + ) + + self.assertTrue(auto_created_serial_batch) + + self.assertEqual(scr.items[0].rm_cost_per_qty, 900) + self.assertEqual(scr.items[0].service_cost_per_qty, 100) + self.assertEqual(scr.items[0].rate, 1000) + valuation_rate = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": scr.name, "voucher_detail_no": scr.items[0].name}, + "valuation_rate", + ) + + self.assertEqual(flt(valuation_rate), flt(1000)) + + frappe.db.set_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0 + ) + def test_subcontracting_receipt_raw_material_rate(self): # Step - 1: Set Backflush Based On as "BOM" set_backflush_based_on("BOM") From 9006c9b7478ceee5f7662d62e8eb02a08a309906 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 21 Nov 2023 16:44:01 +0530 Subject: [PATCH 136/163] chore: rename 'unreconcile payments' to 'unreconcile payment' --- .../{unreconcile_payments => unreconcile_payment}/__init__.py | 0 .../test_unreconcile_payment.py} | 2 +- .../unreconcile_payment.js} | 2 +- .../unreconcile_payment.json} | 2 +- .../unreconcile_payment.py} | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename erpnext/accounts/doctype/{unreconcile_payments => unreconcile_payment}/__init__.py (100%) rename erpnext/accounts/doctype/{unreconcile_payments/test_unreconcile_payments.py => unreconcile_payment/test_unreconcile_payment.py} (99%) rename erpnext/accounts/doctype/{unreconcile_payments/unreconcile_payments.js => unreconcile_payment/unreconcile_payment.js} (94%) rename erpnext/accounts/doctype/{unreconcile_payments/unreconcile_payments.json => unreconcile_payment/unreconcile_payment.json} (98%) rename erpnext/accounts/doctype/{unreconcile_payments/unreconcile_payments.py => unreconcile_payment/unreconcile_payment.py} (99%) diff --git a/erpnext/accounts/doctype/unreconcile_payments/__init__.py b/erpnext/accounts/doctype/unreconcile_payment/__init__.py similarity index 100% rename from erpnext/accounts/doctype/unreconcile_payments/__init__.py rename to erpnext/accounts/doctype/unreconcile_payment/__init__.py diff --git a/erpnext/accounts/doctype/unreconcile_payments/test_unreconcile_payments.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py similarity index 99% rename from erpnext/accounts/doctype/unreconcile_payments/test_unreconcile_payments.py rename to erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 78e04bff81..0f1a351176 100644 --- a/erpnext/accounts/doctype/unreconcile_payments/test_unreconcile_payments.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -10,7 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase): +class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): def setUp(self): self.create_company() self.create_customer() diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.js b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.js similarity index 94% rename from erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.js rename to erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.js index c522567637..70cefb13b5 100644 --- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.js +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.js @@ -1,7 +1,7 @@ // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on("Unreconcile Payments", { +frappe.ui.form.on("Unreconcile Payment", { refresh(frm) { frm.set_query("voucher_type", function() { return { diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json similarity index 98% rename from erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.json rename to erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json index f29e61b6ef..17e0b4ddd3 100644 --- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.json +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json @@ -61,7 +61,7 @@ "modified": "2023-08-28 17:42:50.261377", "modified_by": "Administrator", "module": "Accounts", - "name": "Unreconcile Payments", + "name": "Unreconcile Payment", "naming_rule": "Expression", "owner": "Administrator", "permissions": [ diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py similarity index 99% rename from erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py rename to erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 4f9fb50d46..9abd752350 100644 --- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -15,7 +15,7 @@ from erpnext.accounts.utils import ( ) -class UnreconcilePayments(Document): +class UnreconcilePayment(Document): def validate(self): self.supported_types = ["Payment Entry", "Journal Entry"] if not self.voucher_type in self.supported_types: From e2bb4e2baa9059afb27f685beb88172a615097a4 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 21 Nov 2023 22:32:43 +0530 Subject: [PATCH 137/163] fix: set asset's valuation_rate according to asset quantity (#38254) --- .../doctype/purchase_receipt/purchase_receipt.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d905fe5ea6..a5940f07d6 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -731,12 +731,18 @@ class PurchaseReceipt(BuyingController): def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( - "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code} + "Asset", + filters={"purchase_receipt": self.name, "item_code": item.item_code}, + fields=["name", "asset_quantity"], ) for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(valuation_rate)) - frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate)) + frappe.db.set_value( + "Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity + ) + frappe.db.set_value( + "Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity + ) def update_status(self, status): self.set_status(update=True, status=status) From 74f9e34182563b5dd3ef71d93b66596a12be5e91 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 21 Nov 2023 16:51:06 +0530 Subject: [PATCH 138/163] chore: update new unreconcile doctype name in JS and PY files --- .../doctype/journal_entry/journal_entry.js | 2 +- .../doctype/payment_entry/payment_entry.js | 4 ++-- .../doctype/payment_entry/payment_entry.py | 2 +- .../doctype/purchase_invoice/purchase_invoice.js | 2 +- .../doctype/sales_invoice/sales_invoice.js | 4 ++-- .../doctype/sales_invoice/sales_invoice.py | 2 +- .../test_unreconcile_payment.py | 8 ++++---- .../unreconcile_payment/unreconcile_payment.json | 4 ++-- .../unreconcile_payment/unreconcile_payment.py | 2 +- erpnext/controllers/accounts_controller.py | 6 +++--- erpnext/public/js/utils/unreconcile.js | 14 +++++++------- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 22b6880ad5..9684a0d9d1 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -51,7 +51,7 @@ frappe.ui.form.on("Journal Entry", { }, __('Make')); } - erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm); + erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm); }, before_save: function(frm) { if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) { diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 9a6f8ec8ac..26112409b7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges"); frappe.ui.form.on('Payment Entry', { onload: function(frm) { - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries']; + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries']; if(frm.doc.__islocal) { if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); @@ -160,7 +160,7 @@ frappe.ui.form.on('Payment Entry', { }, __('Actions')); } - erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm); + erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm); }, validate_company: (frm) => { diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ef304bc110..0344e3de9f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -148,7 +148,7 @@ class PaymentEntry(AccountsController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", - "Unreconcile Payments", + "Unreconcile Payment", "Unreconcile Payment Entries", ) super(PaymentEntry, self).on_cancel() diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 2eaa33767c..4b0df12f45 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -180,7 +180,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. } this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1); - erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm); + erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm); } unblock_invoice() { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index b1a7b10eea..6763e446a5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -37,7 +37,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e super.onload(); this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', - 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"]; + 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { // show debit_to in print format @@ -184,7 +184,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e } } - erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm); + erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm); } make_maintenance_schedule() { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fa95ccdc57..20b7382138 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -395,7 +395,7 @@ class SalesInvoice(SellingController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", - "Unreconcile Payments", + "Unreconcile Payment", "Unreconcile Payment Entries", "Payment Ledger Entry", "Serial and Batch Bundle", diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 0f1a351176..f404d9981a 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -73,7 +73,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): unreconcile = frappe.get_doc( { - "doctype": "Unreconcile Payments", + "doctype": "Unreconcile Payment", "company": self.company, "voucher_type": pe.doctype, "voucher_no": pe.name, @@ -138,7 +138,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): unreconcile = frappe.get_doc( { - "doctype": "Unreconcile Payments", + "doctype": "Unreconcile Payment", "company": self.company, "voucher_type": pe2.doctype, "voucher_no": pe2.name, @@ -196,7 +196,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): unreconcile = frappe.get_doc( { - "doctype": "Unreconcile Payments", + "doctype": "Unreconcile Payment", "company": self.company, "voucher_type": pe.doctype, "voucher_no": pe.name, @@ -281,7 +281,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): unreconcile = frappe.get_doc( { - "doctype": "Unreconcile Payments", + "doctype": "Unreconcile Payment", "company": self.company, "voucher_type": pe2.doctype, "voucher_no": pe2.name, diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json index 17e0b4ddd3..f906dc6cec 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json @@ -21,7 +21,7 @@ "fieldtype": "Link", "label": "Amended From", "no_copy": 1, - "options": "Unreconcile Payments", + "options": "Unreconcile Payment", "print_hide": 1, "read_only": 1 }, @@ -90,4 +90,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 9abd752350..77906a7833 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -142,7 +142,7 @@ def create_unreconcile_doc_for_selection(selections=None): selections = frappe.json.loads(selections) # assuming each row is a unique voucher for row in selections: - unrecon = frappe.new_doc("Unreconcile Payments") + unrecon = frappe.new_doc("Unreconcile Payment") unrecon.company = row.get("company") unrecon.voucher_type = row.get("voucher_type") unrecon.voucher_no = row.get("voucher_no") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 38c1e820d2..d12d50d2e7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -239,7 +239,7 @@ class AccountsController(TransactionBase): references_map.setdefault(x.parent, []).append(x.name) for doc, rows in references_map.items(): - unreconcile_doc = frappe.get_doc("Unreconcile Payments", doc) + unreconcile_doc = frappe.get_doc("Unreconcile Payment", doc) for row in rows: unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0]) @@ -248,9 +248,9 @@ class AccountsController(TransactionBase): unreconcile_doc.save(ignore_permissions=True) # delete docs upon parent doc deletion - unreconcile_docs = frappe.db.get_all("Unreconcile Payments", filters={"voucher_no": self.name}) + unreconcile_docs = frappe.db.get_all("Unreconcile Payment", filters={"voucher_no": self.name}) for x in unreconcile_docs: - _doc = frappe.get_doc("Unreconcile Payments", x.name) + _doc = frappe.get_doc("Unreconcile Payment", x.name) if _doc.docstatus == 1: _doc.cancel() _doc.delete() diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js index fa00ed2362..79490a162d 100644 --- a/erpnext/public/js/utils/unreconcile.js +++ b/erpnext/public/js/utils/unreconcile.js @@ -1,6 +1,6 @@ frappe.provide('erpnext.accounts'); -erpnext.accounts.unreconcile_payments = { +erpnext.accounts.unreconcile_payment = { add_unreconcile_btn(frm) { if (frm.doc.docstatus == 1) { if(((frm.doc.doctype == "Journal Entry") && (frm.doc.voucher_type != "Journal Entry")) @@ -10,7 +10,7 @@ erpnext.accounts.unreconcile_payments = { } frappe.call({ - "method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.doc_has_references", + "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.doc_has_references", "args": { "doctype": frm.doc.doctype, "docname": frm.doc.name @@ -18,7 +18,7 @@ erpnext.accounts.unreconcile_payments = { callback: function(r) { if (r.message) { frm.add_custom_button(__("UnReconcile"), function() { - erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm); + erpnext.accounts.unreconcile_payment.build_unreconcile_dialog(frm); }, __('Actions')); } } @@ -74,7 +74,7 @@ erpnext.accounts.unreconcile_payments = { // get linked payments frappe.call({ - "method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.get_linked_payments_for_doc", + "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.get_linked_payments_for_doc", "args": { "company": frm.doc.company, "doctype": frm.doc.doctype, @@ -96,8 +96,8 @@ erpnext.accounts.unreconcile_payments = { let selected_allocations = values.allocations.filter(x=>x.__checked); if (selected_allocations.length > 0) { - let selection_map = erpnext.accounts.unreconcile_payments.build_selection_map(frm, selected_allocations); - erpnext.accounts.unreconcile_payments.create_unreconcile_docs(selection_map); + let selection_map = erpnext.accounts.unreconcile_payment.build_selection_map(frm, selected_allocations); + erpnext.accounts.unreconcile_payment.create_unreconcile_docs(selection_map); d.hide(); } else { @@ -115,7 +115,7 @@ erpnext.accounts.unreconcile_payments = { create_unreconcile_docs(selection_map) { frappe.call({ - "method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.create_unreconcile_doc_for_selection", + "method": "erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment.create_unreconcile_doc_for_selection", "args": { "selections": selection_map }, From 8b04c1d4f6356bc332c5dd8f6f8711d778e030cd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 22 Nov 2023 09:59:32 +0530 Subject: [PATCH 139/163] refactor: optimize outstanding vouchers query --- erpnext/accounts/utils.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7d91309fcc..6d2a47f73e 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1833,6 +1833,28 @@ class QueryPaymentLedger(object): Table("outstanding").amount_in_account_currency >= self.max_outstanding ) + if self.limit and self.get_invoices: + outstanding_vouchers = ( + qb.from_(ple) + .select( + ple.against_voucher_no.as_("voucher_no"), + Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"), + ) + .where(ple.delinked == 0) + .where(Criterion.all(filter_on_against_voucher_no)) + .where(Criterion.all(self.common_filter)) + .groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party) + .orderby(ple.posting_date, ple.voucher_no) + .having(qb.Field("amount_in_account_currency") > 0) + .limit(self.limit) + .run() + ) + if outstanding_vouchers: + filter_on_voucher_no.append(ple.voucher_no.isin([x[0] for x in outstanding_vouchers])) + filter_on_against_voucher_no.append( + ple.against_voucher_no.isin([x[0] for x in outstanding_vouchers]) + ) + # build query for voucher amount query_voucher_amount = ( qb.from_(ple) From b11ae4b54c42cd7f09db65c576ac75f7faf2f0c8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 10:26:50 +0530 Subject: [PATCH 140/163] chore: remove dead link --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 44bd729688..710187ad2f 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,6 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB 1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines) 1. [Report Security Vulnerabilities](https://erpnext.com/security) 1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines) -1. [Translations](https://translate.erpnext.com) - ## License From 0ca7527f7abddcab59edce434d0e68e1deebfc33 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 22 Nov 2023 14:02:01 +0530 Subject: [PATCH 141/163] fix: patch - Duplicate entry quality inspection parameter (#38262) --- erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py index e53bdf8f19..08ddbbf337 100644 --- a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py +++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py @@ -21,6 +21,9 @@ def execute(): params = set({x.casefold(): x for x in params}.values()) for parameter in params: + if frappe.db.exists("Quality Inspection Parameter", parameter): + continue + frappe.get_doc( {"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter} ).insert(ignore_permissions=True) From f9713eeb5690f9e71e01c6c570012f561a633c73 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 15:22:42 +0530 Subject: [PATCH 142/163] fix: skip fixed assets in parent --- .../doctype/product_bundle/product_bundle.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index ac83c0f046..4b401e7e67 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -59,6 +59,8 @@ class ProductBundle(Document): """Validates, main Item is not a stock item""" if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"): frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code)) + if frappe.db.get_value("Item", self.new_item_code, "is_fixed_asset"): + frappe.throw(_("Parent Item {0} must not be a Fixed Asset").format(self.new_item_code)) def validate_child_items(self): for item in self.items: @@ -73,12 +75,17 @@ class ProductBundle(Document): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): - from erpnext.controllers.queries import get_match_cond - - return frappe.db.sql( - """select name, item_name, description from tabItem - where is_stock_item=0 and name not in (select name from `tabProduct Bundle`) - and %s like %s %s limit %s offset %s""" - % (searchfield, "%s", get_match_cond(doctype), "%s", "%s"), - ("%%%s%%" % txt, page_len, start), - ) + product_bundles = frappe.db.get_list("Product Bundle", pluck="name") + item = frappe.qb.DocType("Item") + return ( + frappe.qb.from_(item) + .select("*") + .where( + (item.is_stock_item == 0) + & (item.is_fixed_asset == 0) + & (item.name.notin(product_bundles)) + & (item[searchfield].like(f"%{txt}%")) + ) + .limit(page_len) + .offset(start) + ).run() From ee76af76812fe04d86653fd1955ad133c8cb5df8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 15:23:17 +0530 Subject: [PATCH 143/163] feat: add disabled field in product bundle --- .../product_bundle/product_bundle.json | 398 +++++------------- 1 file changed, 101 insertions(+), 297 deletions(-) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json index 56155fb750..c4f21b61b9 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.json +++ b/erpnext/selling/doctype/product_bundle/product_bundle.json @@ -1,315 +1,119 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "beta": 0, - "creation": "2013-06-20 11:53:21", - "custom": 0, - "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials", - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, + "actions": [], + "allow_import": 1, + "creation": "2013-06-20 11:53:21", + "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "basic_section", + "new_item_code", + "description", + "column_break_eonk", + "disabled", + "item_section", + "items", + "section_break_4", + "about" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "basic_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "basic_section", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "new_item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Parent Item", - "length": 0, - "no_copy": 1, - "oldfieldname": "new_item_code", - "oldfieldtype": "Data", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "new_item_code", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Parent Item", + "no_copy": 1, + "oldfieldname": "new_item_code", + "oldfieldtype": "Data", + "options": "Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "description", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Description" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "List items that form the package.", - "fieldname": "item_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Items", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "description": "List items that form the package.", + "fieldname": "item_section", + "fieldtype": "Section Break", + "label": "Items" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_bom_items", - "oldfieldtype": "Table", - "options": "Product Bundle Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "sales_bom_items", + "oldfieldtype": "Table", + "options": "Product Bundle Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "about", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "

About Product Bundle

\n\n

Aggregate group of Items into another Item. This is useful if you are bundling a certain Items into a package and you maintain stock of the packed Items and not the aggregate Item.

\n

The package Item will have Is Stock Item as No and Is Sales Item as Yes.

\n

Example:

\n

If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.

", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "about", + "fieldtype": "HTML", + "options": "

About Product Bundle

\n\n

Aggregate group of Items into another Item. This is useful if you are bundling a certain Items into a package and you maintain stock of the packed Items and not the aggregate Item.

\n

The package Item will have Is Stock Item as No and Is Sales Item as Yes.

\n

Example:

\n

If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.

" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "column_break_eonk", + "fieldtype": "Column Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-sitemap", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Selling", - "name": "Product Bundle", - "owner": "Administrator", + ], + "icon": "fa fa-sitemap", + "idx": 1, + "links": [], + "modified": "2023-11-22 15:20:46.805114", + "modified_by": "Administrator", + "module": "Selling", + "name": "Product Bundle", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User" + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "ASC", + "states": [] } \ No newline at end of file From 874774fe6c81c854fb3ade03e93f003c0fc8dd8a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 15:24:56 +0530 Subject: [PATCH 144/163] fix: filter bundle items based on disabled check --- erpnext/stock/doctype/packed_item/packed_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index a9e9ad1a63..8b6f7158e0 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -111,7 +111,7 @@ def get_product_bundle_items(item_code): product_bundle_item.uom, product_bundle_item.description, ) - .where(product_bundle.new_item_code == item_code) + .where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0)) .orderby(product_bundle_item.idx) ) return query.run(as_dict=True) From 627165dc7c6c1cd83d746b97c82210abeb9a2e29 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 22 Nov 2023 15:26:45 +0530 Subject: [PATCH 145/163] fix: Supplier `Primary Contact` --- erpnext/buying/doctype/supplier/supplier.py | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 31bf439dbb..b052f564a4 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -165,16 +165,17 @@ class Supplier(TransactionBase): @frappe.validate_and_sanitize_search_inputs def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters): supplier = filters.get("supplier") - return frappe.db.sql( - """ - SELECT - `tabContact`.name from `tabContact`, - `tabDynamic Link` - WHERE - `tabContact`.name = `tabDynamic Link`.parent - and `tabDynamic Link`.link_name = %(supplier)s - and `tabDynamic Link`.link_doctype = 'Supplier' - and `tabContact`.name like %(txt)s - """, - {"supplier": supplier, "txt": "%%%s%%" % txt}, - ) + contact = frappe.qb.DocType("Contact") + dynamic_link = frappe.qb.DocType("Dynamic Link") + + return ( + frappe.qb.from_(contact) + .join(dynamic_link) + .on(contact.name == dynamic_link.parent) + .select(contact.name, contact.email_id) + .where( + (dynamic_link.link_name == supplier) + & (dynamic_link.link_doctype == "Supplier") + & (contact.name.like("%{0}%".format(txt))) + ) + ).run(as_dict=False) From 3543f86c639cf7383c37a5ed9a61d5787c9ebad4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 16:04:05 +0530 Subject: [PATCH 146/163] fix: has_product_bundle util to only check for enabled bundles --- erpnext/controllers/selling_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index d34fbeb0da..c3211b1d1c 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -352,7 +352,7 @@ class SellingController(StockController): def has_product_bundle(self, item_code): return frappe.db.sql( """select name from `tabProduct Bundle` - where new_item_code=%s and docstatus != 2""", + where new_item_code=%s and disabled=0""", item_code, ) From 67f43d37df9e77c3e59562117fa44ea76273a536 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 16:10:49 +0530 Subject: [PATCH 147/163] fix: validation for existing bundles --- erpnext/selling/doctype/product_bundle/product_bundle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index 4b401e7e67..2fd9cc1301 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -64,7 +64,7 @@ class ProductBundle(Document): def validate_child_items(self): for item in self.items: - if frappe.db.exists("Product Bundle", item.item_code): + if frappe.db.exists("Product Bundle", {"name": item.item_code, "disabled": 0}): frappe.throw( _( "Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save" @@ -75,7 +75,7 @@ class ProductBundle(Document): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): - product_bundles = frappe.db.get_list("Product Bundle", pluck="name") + product_bundles = frappe.db.get_list("Product Bundle", {"disabled": 0}, pluck="name") item = frappe.qb.DocType("Item") return ( frappe.qb.from_(item) From 8bdb61cb87802723a0db1aaa29c30c15c740ec6b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 16:17:23 +0530 Subject: [PATCH 148/163] fix: condition in other bundle utils --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- erpnext/selling/doctype/sales_order/sales_order.py | 8 +++++--- erpnext/stock/get_item_details.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index e36e97bc4b..9091a77f99 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -556,7 +556,7 @@ def get_stock_availability(item_code, warehouse): return bin_qty - pos_sales_qty, is_stock_item else: is_stock_item = True - if frappe.db.exists("Product Bundle", item_code): + if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}): return get_bundle_availability(item_code, warehouse), is_stock_item else: is_stock_item = False diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a97198aa78..a23599b180 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -688,7 +688,9 @@ def make_material_request(source_name, target_doc=None): "Sales Order Item": { "doctype": "Material Request Item", "field_map": {"name": "sales_order_item", "parent": "sales_order"}, - "condition": lambda item: not frappe.db.exists("Product Bundle", item.item_code) + "condition": lambda item: not frappe.db.exists( + "Product Bundle", {"name": item.item_code, "disabled": 0} + ) and get_remaining_qty(item) > 0, "postprocess": update_item, }, @@ -1309,7 +1311,7 @@ def set_delivery_date(items, sales_order): def is_product_bundle(item_code): - return frappe.db.exists("Product Bundle", item_code) + return frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}) @frappe.whitelist() @@ -1521,7 +1523,7 @@ def get_work_order_items(sales_order, for_raw_material_request=0): product_bundle_parents = [ pb.new_item_code for pb in frappe.get_all( - "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"] + "Product Bundle", {"new_item_code": ["in", item_codes], "disabled": 0}, ["new_item_code"] ) ] diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c766cab0a1..d1a9cf26ac 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -149,7 +149,7 @@ def remove_standard_fields(details): def set_valuation_rate(out, args): - if frappe.db.exists("Product Bundle", args.item_code, cache=True): + if frappe.db.exists("Product Bundle", {"name": args.item_code, "disabled": 0}, cache=True): valuation_rate = 0.0 bundled_items = frappe.get_doc("Product Bundle", args.item_code) From 362f377f6127e9262ab7829a427d0a2051a77b2f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 16:26:09 +0530 Subject: [PATCH 149/163] fix: skip disabled bundles for non-report utils --- erpnext/stock/doctype/delivery_note/delivery_note.py | 4 ++-- erpnext/stock/doctype/item/item.py | 8 ++++++-- erpnext/stock/doctype/packed_item/packed_item.py | 2 +- erpnext/stock/doctype/pick_list/pick_list.py | 8 ++++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 66dd33a400..f240136e9c 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -615,7 +615,7 @@ class DeliveryNote(SellingController): items_list = [item.item_code for item in self.items] return frappe.db.get_all( "Product Bundle", - filters={"new_item_code": ["in", items_list]}, + filters={"new_item_code": ["in", items_list], "disabled": 0}, pluck="name", ) @@ -938,7 +938,7 @@ def make_packing_slip(source_name, target_doc=None): }, "postprocess": update_item, "condition": lambda item: ( - not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code}) + not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0}) and flt(item.packed_qty) < flt(item.qty) ), }, diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d8935fe203..cb34497f28 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -512,8 +512,12 @@ class Item(Document): def validate_duplicate_product_bundles_before_merge(self, old_name, new_name): "Block merge if both old and new items have product bundles." - old_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": old_name}) - new_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": new_name}) + old_bundle = frappe.get_value( + "Product Bundle", filters={"new_item_code": old_name, "disabled": 0} + ) + new_bundle = frappe.get_value( + "Product Bundle", filters={"new_item_code": new_name, "disabled": 0} + ) if old_bundle and new_bundle: bundle_link = get_link_to_form("Product Bundle", old_bundle) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 8b6f7158e0..35701c90de 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -55,7 +55,7 @@ def make_packing_list(doc): def is_product_bundle(item_code: str) -> bool: - return bool(frappe.db.exists("Product Bundle", {"new_item_code": item_code})) + return bool(frappe.db.exists("Product Bundle", {"new_item_code": item_code, "disabled": 0})) def get_indexed_packed_items_table(doc): diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index ed20209577..644df3d29a 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -368,7 +368,9 @@ class PickList(Document): frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx)) if not cint( frappe.get_cached_value("Item", item.item_code, "is_stock_item") - ) and not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code}): + ) and not frappe.db.exists( + "Product Bundle", {"new_item_code": item.item_code, "disabled": 0} + ): continue item_code = item.item_code reference = item.sales_order_item or item.material_request_item @@ -507,7 +509,9 @@ class PickList(Document): # bundle_item_code: Dict[component, qty] product_bundle_qty_map = {} for bundle_item_code in bundles: - bundle = frappe.get_last_doc("Product Bundle", {"new_item_code": bundle_item_code}) + bundle = frappe.get_last_doc( + "Product Bundle", {"new_item_code": bundle_item_code, "disabled": 0} + ) product_bundle_qty_map[bundle_item_code] = {item.item_code: item.qty for item in bundle.items} return product_bundle_qty_map From 16573378870bb9c6e80f39e80fa43e5b2cc0a69f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 16:45:14 +0530 Subject: [PATCH 150/163] chore: linting issues --- erpnext/controllers/selling_controller.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c3211b1d1c..5575a24b35 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -350,11 +350,12 @@ class SellingController(StockController): return il def has_product_bundle(self, item_code): - return frappe.db.sql( - """select name from `tabProduct Bundle` - where new_item_code=%s and disabled=0""", - item_code, - ) + product_bundle = frappe.qb.DocType("Product Bundle") + return ( + frappe.qb.from_(product_bundle) + .select(product_bundle.name) + .where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0)) + ).run() def get_already_delivered_qty(self, current_docname, so, so_detail): delivered_via_dn = frappe.db.sql( From 52305e3000decb84aad1a99557e13a0bb2b68ec4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 18:38:33 +0530 Subject: [PATCH 151/163] fix: annual income and expenses in digest --- erpnext/accounts/utils.py | 3 +++ erpnext/setup/doctype/email_digest/email_digest.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7d91309fcc..dbad2bacb6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -183,6 +183,7 @@ def get_balance_on( cost_center=None, ignore_account_permission=False, account_type=None, + start_date=None, ): if not account and frappe.form_dict.get("account"): account = frappe.form_dict.get("account") @@ -196,6 +197,8 @@ def get_balance_on( cost_center = frappe.form_dict.get("cost_center") cond = ["is_cancelled=0"] + if start_date: + cond.append("posting_date >= %s" % frappe.db.escape(cstr(start_date))) if date: cond.append("posting_date <= %s" % frappe.db.escape(cstr(date))) else: diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 4fc20e6103..b9e225728e 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -382,9 +382,10 @@ class EmailDigest(Document): """Get income to date""" balance = 0.0 count = 0 + fy_start_date = get_fiscal_year().get("year_start_date") for account in self.get_root_type_accounts(root_type): - balance += get_balance_on(account, date=self.future_to_date) + balance += get_balance_on(account, date=self.future_to_date, start_date=fy_start_date) count += get_count_on(account, fieldname, date=self.future_to_date) if fieldname == "income": From 728cc9f725264e057d9331362b256ea8d0f80b83 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 22 Nov 2023 19:28:57 +0530 Subject: [PATCH 152/163] fix: fiscal year using future date --- erpnext/setup/doctype/email_digest/email_digest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index b9e225728e..6ed44fff68 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -382,7 +382,7 @@ class EmailDigest(Document): """Get income to date""" balance = 0.0 count = 0 - fy_start_date = get_fiscal_year().get("year_start_date") + fy_start_date = get_fiscal_year(self.future_to_date)[1] for account in self.get_root_type_accounts(root_type): balance += get_balance_on(account, date=self.future_to_date, start_date=fy_start_date) From f258ab5e98f323303276aeca98dcc644f6611a70 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 10:39:41 +0530 Subject: [PATCH 153/163] chore: move reconciliation cleanup patch to pre-model --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a71d71b83a..8e091d4a5d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -259,6 +259,7 @@ erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v15_0.saudi_depreciation_warning erpnext.patches.v15_0.delete_saudi_doctypes erpnext.patches.v14_0.show_loan_management_deprecation_warning +erpnext.patches.v14_0.clear_reconciliation_values_from_singles execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Details", force=True) [post_model_sync] @@ -345,7 +346,6 @@ erpnext.patches.v15_0.update_sre_from_voucher_details erpnext.patches.v14_0.rename_over_order_allowance_field erpnext.patches.v14_0.migrate_delivery_stop_lock_field erpnext.patches.v14_0.add_default_for_repost_settings -erpnext.patches.v14_0.clear_reconciliation_values_from_singles erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based erpnext.patches.v15_0.set_reserved_stock_in_bin From 24fcd67f8bcf8cc8b387856983b97d8778eac084 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 11:44:34 +0530 Subject: [PATCH 154/163] chore: index to speed up queries on JE child table reference --- .../journal_entry_account/journal_entry_account.json | 11 +++++++---- 1 file changed, 7 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 3ba8cea94b..3132fe9b12 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -203,7 +203,8 @@ "fieldtype": "Select", "label": "Reference Type", "no_copy": 1, - "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry" + "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry", + "search_index": 1 }, { "fieldname": "reference_name", @@ -211,7 +212,8 @@ "in_list_view": 1, "label": "Reference Name", "no_copy": 1, - "options": "reference_type" + "options": "reference_type", + "search_index": 1 }, { "depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])", @@ -278,13 +280,14 @@ "fieldtype": "Data", "hidden": 1, "label": "Reference Detail No", - "no_copy": 1 + "no_copy": 1, + "search_index": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-06-16 14:11:13.507807", + "modified": "2023-11-23 11:44:25.841187", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From 816b1b6bd52b4f6adcb35d4039ad75892b0976ff Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 23 Nov 2023 13:48:46 +0530 Subject: [PATCH 155/163] fix: don't depreciate assets with no schedule on scrapping (#38276) fix: don't depreciate non-depreciable assets on scrapping --- erpnext/assets/doctype/asset/depreciation.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 84a428ca54..66930c0e7c 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -509,6 +509,9 @@ def restore_asset(asset_name): def depreciate_asset(asset_doc, date, notes): + if not asset_doc.calculate_depreciation: + return + asset_doc.flags.ignore_validate_update_after_submit = True make_new_active_asset_depr_schedules_and_cancel_current_ones( @@ -521,6 +524,9 @@ def depreciate_asset(asset_doc, date, notes): def reset_depreciation_schedule(asset_doc, date, notes): + if not asset_doc.calculate_depreciation: + return + asset_doc.flags.ignore_validate_update_after_submit = True make_new_active_asset_depr_schedules_and_cancel_current_ones( From 1efff268b036dd27f7472bc7ec494aafac9edbc8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 16:56:35 +0530 Subject: [PATCH 156/163] chore: speed up Purchase Invoice cancellation --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index d1677832ed..f2094874e0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1615,7 +1615,8 @@ "hide_seconds": 1, "label": "Inter Company Invoice Reference", "options": "Purchase Invoice", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "customer_group", @@ -2173,7 +2174,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-11-20 11:51:43.555197", + "modified": "2023-11-23 16:56:29.679499", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 3be345e6056f36baa6eef75818a7cdc63826b5f8 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:02:27 +0100 Subject: [PATCH 157/163] feat: add Bank Transaction to connections in Journal and Payment Entry (#38297) * feat(Payment Entry): Bank Transaction connection * feat(Journal Entry): Bank Transaction connection --- .../doctype/journal_entry/journal_entry.json | 12 ++++++++++-- .../doctype/payment_entry/payment_entry.json | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 2eb54a54d5..906760ec31 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -548,8 +548,16 @@ "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, - "links": [], - "modified": "2023-08-10 14:32:22.366895", + "links": [ + { + "is_child_table": 1, + "link_doctype": "Bank Transaction Payments", + "link_fieldname": "payment_entry", + "parent_doctype": "Bank Transaction", + "table_fieldname": "payment_entries" + } + ], + "modified": "2023-11-23 12:11:04.128015", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 4d50a35ed4..aa181564b0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -750,8 +750,16 @@ ], "index_web_pages_for_search": 1, "is_submittable": 1, - "links": [], - "modified": "2023-11-08 21:51:03.482709", + "links": [ + { + "is_child_table": 1, + "link_doctype": "Bank Transaction Payments", + "link_fieldname": "payment_entry", + "parent_doctype": "Bank Transaction", + "table_fieldname": "payment_entries" + } + ], + "modified": "2023-11-23 12:07:20.887885", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From 64b44a360af8c2d3a6a9cb99e03358b97cdf9fa5 Mon Sep 17 00:00:00 2001 From: NandhiniDevi <95607404+Nandhinidevi123@users.noreply.github.com> Date: Thu, 23 Nov 2023 22:40:17 +0530 Subject: [PATCH 158/163] add flt() for None type error (#38299) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 85ef6f76d2..1cff4c7f2d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -868,7 +868,7 @@ class JournalEntry(AccountsController): party_account_currency = d.account_currency elif frappe.get_cached_value("Account", d.account, "account_type") in ["Bank", "Cash"]: - bank_amount += d.debit_in_account_currency or d.credit_in_account_currency + bank_amount += flt(d.debit_in_account_currency) or flt(d.credit_in_account_currency) bank_account_currency = d.account_currency if party_type and pay_to_recd_from: From 3f6d80503335a33bf2bff53a19870a6cdba00044 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 23 Nov 2023 12:12:12 -0500 Subject: [PATCH 159/163] fix: display all item rate stop messages (#38289) --- erpnext/utilities/transaction_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 7eba35dedd..b083614a5f 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -98,6 +98,7 @@ class TransactionBase(StatusUpdater): "Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"] ) + stop_actions = [] for ref_dt, ref_dn_field, ref_link_field in ref_details: reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)] reference_details = self.get_reference_details(reference_names, ref_dt + " Item") @@ -108,7 +109,7 @@ class TransactionBase(StatusUpdater): if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01: if action == "Stop": if role_allowed_to_override not in frappe.get_roles(): - frappe.throw( + stop_actions.append( _("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format( d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate ) @@ -121,6 +122,8 @@ class TransactionBase(StatusUpdater): title=_("Warning"), indicator="orange", ) + if stop_actions: + frappe.throw(stop_actions, as_list=True) def get_reference_details(self, reference_names, reference_doctype): return frappe._dict( From aa17110bde44603574d4532afaa6cf1181423ac5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 17:39:48 +0530 Subject: [PATCH 160/163] perf: optimize total_purchase_cost update --- .../purchase_invoice/purchase_invoice.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d7c2361b4d..e83525dbac 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1281,13 +1281,21 @@ class PurchaseInvoice(BuyingController): self.update_advance_tax_references(cancel=1) def update_project(self): - project_list = [] + projects = frappe._dict() for d in self.items: - if d.project and d.project not in project_list: - project = frappe.get_doc("Project", d.project) - project.update_purchase_costing() - project.db_update() - project_list.append(d.project) + if d.project: + if self.docstatus == 1: + projects[d.project] = projects.get(d.project, 0) + d.base_net_amount + elif self.docstatus == 2: + projects[d.project] = projects.get(d.project, 0) - d.base_net_amount + + pj = frappe.qb.DocType("Project") + for proj, value in projects.items(): + res = ( + frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run() + ) + current_purchase_cost = res and res[0][0] or 0 + frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value) def validate_supplier_invoice(self): if self.bill_date: From bcbe6c4a531986836a0a2497e05bdbe1a327407a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 17:53:42 +0530 Subject: [PATCH 161/163] refactor: provide UI button to recalculate when needed --- erpnext/projects/doctype/project/project.js | 20 +++++++++++ erpnext/projects/doctype/project/project.py | 37 ++++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index f366f77556..2dac399d88 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -68,6 +68,10 @@ frappe.ui.form.on("Project", { frm.events.create_duplicate(frm); }, __("Actions")); + frm.add_custom_button(__('Update Total Purchase Cost'), () => { + frm.events.update_total_purchase_cost(frm); + }, __("Actions")); + frm.trigger("set_project_status_button"); @@ -92,6 +96,22 @@ frappe.ui.form.on("Project", { }, + update_total_purchase_cost: function(frm) { + frappe.call({ + method: "erpnext.projects.doctype.project.project.recalculate_project_total_purchase_cost", + args: {project: frm.doc.name}, + freeze: true, + freeze_message: __('Recalculating Purchase Cost against this Project...'), + callback: function(r) { + if (r && !r.exc) { + frappe.msgprint(__('Total Purchase Cost has been updated')); + frm.refresh(); + } + } + + }); + }, + set_project_status_button: function(frm) { frm.add_custom_button(__('Set Project Status'), () => { let d = new frappe.ui.Dialog({ diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index e9aed1afb4..4f2e39539d 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -4,11 +4,11 @@ import frappe from email_reply_parser import EmailReplyParser -from frappe import _ +from frappe import _, qb from frappe.desk.reportview import get_match_cond from frappe.model.document import Document from frappe.query_builder import Interval -from frappe.query_builder.functions import Count, CurDate, Date, UnixTimestamp +from frappe.query_builder.functions import Count, CurDate, Date, Sum, UnixTimestamp from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today from frappe.utils.user import is_website_user @@ -249,12 +249,7 @@ class Project(Document): self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100 def update_purchase_costing(self): - total_purchase_cost = frappe.db.sql( - """select sum(base_net_amount) - from `tabPurchase Invoice Item` where project = %s and docstatus=1""", - self.name, - ) - + total_purchase_cost = calculate_total_purchase_cost(self.name) self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0 def update_sales_amount(self): @@ -695,3 +690,29 @@ def get_holiday_list(company=None): def get_users_email(doc): return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")] + + +def calculate_total_purchase_cost(project: str | None = None): + if project: + pitem = qb.DocType("Purchase Invoice Item") + frappe.qb.DocType("Purchase Invoice Item") + total_purchase_cost = ( + qb.from_(pitem) + .select(Sum(pitem.base_net_amount)) + .where((pitem.project == project) & (pitem.docstatus == 1)) + .run(as_list=True) + ) + return total_purchase_cost + return None + + +@frappe.whitelist() +def recalculate_project_total_purchase_cost(project: str | None = None): + if project: + total_purchase_cost = calculate_total_purchase_cost(project) + frappe.db.set_value( + "Project", + project, + "total_purchase_cost", + (total_purchase_cost and total_purchase_cost[0][0] or 0), + ) From 0fe6dcd74288d5f2df1ffc37485e22ee96329b9e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Nov 2023 10:53:00 +0530 Subject: [PATCH 162/163] refactor: make update_project_cost optional through Buying Settings --- .../doctype/buying_settings/buying_settings.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 059999245d..0af93bfc90 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -17,6 +17,7 @@ "po_required", "pr_required", "blanket_order_allowance", + "project_update_frequency", "column_break_12", "maintain_same_rate", "set_landed_cost_based_on_purchase_invoice_rate", @@ -172,6 +173,14 @@ "fieldname": "blanket_order_allowance", "fieldtype": "Float", "label": "Blanket Order Allowance (%)" + }, + { + "default": "Each Transaction", + "description": "How often should Project be updated of Total Purchase Cost ?", + "fieldname": "project_update_frequency", + "fieldtype": "Select", + "label": "Update frequency of Project", + "options": "Each Transaction\nManual" } ], "icon": "fa fa-cog", @@ -179,7 +188,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-25 14:03:32.520418", + "modified": "2023-11-24 10:55:51.287327", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", From dd016e6ced432b6f8754042fbad808b1cba56cec Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Nov 2023 10:54:31 +0530 Subject: [PATCH 163/163] refactor: update project costing based on frequency --- .../doctype/purchase_invoice/purchase_invoice.py | 11 +++++++++-- erpnext/patches.txt | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e83525dbac..c6ae9377a0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -527,7 +527,11 @@ class PurchaseInvoice(BuyingController): if self.update_stock == 1: self.repost_future_sle_and_gle() - self.update_project() + if ( + frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction" + ): + self.update_project() + update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) self.update_advance_tax_references() @@ -1262,7 +1266,10 @@ class PurchaseInvoice(BuyingController): if self.update_stock == 1: self.repost_future_sle_and_gle() - self.update_project() + if ( + frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction" + ): + self.update_project() self.db_set("status", "Cancelled") unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a71d71b83a..4991b48216 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -351,5 +351,6 @@ erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_d erpnext.patches.v15_0.set_reserved_stock_in_bin erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation erpnext.patches.v14_0.update_zero_asset_quantity_field +execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger