From d0106f2af5967e24051457d44b62c2d67a82672b Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 11:28:03 +0100 Subject: [PATCH 001/155] feat: New fields to handle incorrectly formatted files --- .../doctype/fec_import/fec_import.json | 31 +++++++++++++++++-- .../regional/doctype/fec_import/fec_import.py | 2 ++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/doctype/fec_import/fec_import.json b/erpnext/regional/doctype/fec_import/fec_import.json index 3341281c205..517c2cb52fc 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.json +++ b/erpnext/regional/doctype/fec_import/fec_import.json @@ -21,7 +21,11 @@ "column_break_7", "to_date", "section_break_9", - "import_journal" + "import_journal", + "additional_settings_section", + "field_delimiter", + "column_break_lbqk", + "date_format" ], "fields": [ { @@ -98,6 +102,29 @@ "fieldtype": "Link", "label": "Import Setttings", "options": "FEC Import Settings" + }, + { + "collapsible": 1, + "fieldname": "additional_settings_section", + "fieldtype": "Section Break", + "label": "Additional Settings" + }, + { + "default": "Tab", + "fieldname": "field_delimiter", + "fieldtype": "Select", + "label": "Field Delimiter", + "options": "Tab\nComma\nSemicolon\nSpace" + }, + { + "fieldname": "column_break_lbqk", + "fieldtype": "Column Break" + }, + { + "description": "Leave empty for default format", + "fieldname": "date_format", + "fieldtype": "Data", + "label": "Date Format" } ], "index_web_pages_for_search": 1, @@ -108,7 +135,7 @@ "link_fieldname": "fec_import" } ], - "modified": "2023-08-28 14:27:25.146981", + "modified": "2023-12-01 09:44:01.197446", "modified_by": "Administrator", "module": "Regional", "name": "FEC Import", diff --git a/erpnext/regional/doctype/fec_import/fec_import.py b/erpnext/regional/doctype/fec_import/fec_import.py index 38f5294a6dd..1c57c8f4cb0 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.py +++ b/erpnext/regional/doctype/fec_import/fec_import.py @@ -23,7 +23,9 @@ class FECImport(Document): from frappe.types import DF company: DF.Link | None + date_format: DF.Data | None fec_file: DF.Attach | None + field_delimiter: DF.Literal["Tab", "Comma", "Semicolon", "Space"] from_date: DF.Date | None import_journal: DF.Link | None import_settings: DF.Link | None -- GitLab From 818346e1e4d17afd338fd99714566b5deac3929e Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Sat, 2 Dec 2023 22:18:54 +0100 Subject: [PATCH 002/155] feat: New options for custom delimiter and date format in FEC Import --- .../doctype/fec_import/fec_import.json | 4 +- .../regional/doctype/fec_import/fec_import.py | 18 ++++++--- .../fec_import_document.py | 22 +++++----- .../fec_import_line/fec_import_line.json | 11 ++++- .../fec_import_line/fec_import_line.py | 40 +++++++++++++++++++ 5 files changed, 75 insertions(+), 20 deletions(-) diff --git a/erpnext/regional/doctype/fec_import/fec_import.json b/erpnext/regional/doctype/fec_import/fec_import.json index 517c2cb52fc..606234b89bb 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.json +++ b/erpnext/regional/doctype/fec_import/fec_import.json @@ -121,7 +121,7 @@ "fieldtype": "Column Break" }, { - "description": "Leave empty for default format", + "description": "Leave empty for default format.
For available formats please read the Python documentation", "fieldname": "date_format", "fieldtype": "Data", "label": "Date Format" @@ -135,7 +135,7 @@ "link_fieldname": "fec_import" } ], - "modified": "2023-12-01 09:44:01.197446", + "modified": "2023-12-02 22:18:33.685975", "modified_by": "Administrator", "module": "Regional", "name": "FEC Import", diff --git a/erpnext/regional/doctype/fec_import/fec_import.py b/erpnext/regional/doctype/fec_import/fec_import.py index 1c57c8f4cb0..92735168dc2 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.py +++ b/erpnext/regional/doctype/fec_import/fec_import.py @@ -12,6 +12,8 @@ from frappe.model.document import Document from frappe.utils import cint, flt, get_year_start, getdate from frappe.utils.csvutils import read_csv_content +FIELD_DELIMITERS = {"Semicolon": ";", "Comma": ",", "Tab": "\t", "Space": " "} + class FECImport(Document): # begin: auto-generated types @@ -69,9 +71,9 @@ class FECImport(Document): try: if closing_date := file_name.split("FEC")[1]: closing_date = closing_date.split(".txt")[0] - company_and_dates["to_date"] = datetime.datetime.strptime(closing_date, "%Y%m%d").strftime( - "%Y-%m-%d" - ) + company_and_dates["to_date"] = datetime.datetime.strptime( + closing_date, self.date_format or "%Y%m%d" + ).strftime("%Y-%m-%d") company_and_dates["from_date"] = get_year_start(closing_date, as_str=True) if siren := file_name.split("FEC")[0]: @@ -105,7 +107,7 @@ class FECImport(Document): _file = frappe.get_doc("File", fileid) fcontent = _file.get_content() - rows = read_csv_content(fcontent, delimiter="\t") + rows = read_csv_content(fcontent, delimiter=FIELD_DELIMITERS.get(self.field_delimiter) or "\t") header = rows[0] data = rows[1:] @@ -401,7 +403,9 @@ class FECImportDocumentCreator: doc = frappe.new_doc("FEC Import Document") doc.fec_import = self.settings.name doc.settings = self.settings.import_settings - doc.gl_entries_date = datetime.datetime.strptime(date, "%Y%m%d").strftime("%Y-%m-%d") + doc.gl_entries_date = datetime.datetime.strptime( + date, self.settings.date_format or "%Y%m%d" + ).strftime("%Y-%m-%d") doc.gl_entry_reference = piece for line in self.grouped_data[date][piece]: @@ -437,7 +441,9 @@ class FECImportDocumentCreator: frappe.get_doc("FEC Import Document", d).run_method("process_document_in_background") def is_within_date_range(self, line): - posting_date = datetime.datetime.strptime(line.EcritureDate, "%Y%m%d").strftime("%Y-%m-%d") + posting_date = datetime.datetime.strptime( + line.EcritureDate, self.settings.date_format or "%Y%m%d" + ).strftime("%Y-%m-%d") if self.settings.from_date and getdate(posting_date) < getdate(self.settings.from_date): return False diff --git a/erpnext/regional/doctype/fec_import_document/fec_import_document.py b/erpnext/regional/doctype/fec_import_document/fec_import_document.py index fb6cfd74c13..8f6f2702ce2 100644 --- a/erpnext/regional/doctype/fec_import_document/fec_import_document.py +++ b/erpnext/regional/doctype/fec_import_document/fec_import_document.py @@ -194,10 +194,17 @@ class FECImportDocument(Document): return supplier.name def parse_dates(self, row): - row.posting_date = parse_date(row.ecrituredate) - row.transaction_date = parse_date(row.piecedate) - row.validation_date = parse_date(row.validdate) - row.reconciliation_date = parse_date(row.datelet) + row.posting_date = self.parse_date(row.ecrituredate) + row.transaction_date = self.parse_date(row.piecedate) + row.validation_date = self.parse_date(row.validdate) + row.reconciliation_date = self.parse_date(row.datelet) + + def parse_date(self, date): + if not date: + return + + date_format = frappe.get_cached_value("FEC Import", self.fec_import, "date_format") + return datetime.datetime.strptime(date, date_format or "%Y%m%d").strftime("%Y-%m-%d") @frappe.whitelist() def create_linked_document(self): @@ -518,13 +525,6 @@ class FECImportDocument(Document): frappe.db.set_value("Account", account, "is_group", 0) -def parse_date(date): - if not date: - return - - return datetime.datetime.strptime(date, "%Y%m%d").strftime("%Y-%m-%d") - - @frappe.whitelist() def bulk_process(docnames): docnames = frappe.parse_json(docnames) diff --git a/erpnext/regional/doctype/fec_import_line/fec_import_line.json b/erpnext/regional/doctype/fec_import_line/fec_import_line.json index 6c66431d5ed..0443c323f66 100644 --- a/erpnext/regional/doctype/fec_import_line/fec_import_line.json +++ b/erpnext/regional/doctype/fec_import_line/fec_import_line.json @@ -44,6 +44,7 @@ ], "fields": [ { + "columns": 1, "fieldname": "journalcode", "fieldtype": "Data", "in_list_view": 1, @@ -69,6 +70,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "comptenum", "fieldtype": "Data", "in_list_view": 1, @@ -82,6 +84,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "compauxnum", "fieldtype": "Data", "in_list_view": 1, @@ -113,6 +116,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "debit", "fieldtype": "Float", "in_list_view": 1, @@ -120,6 +124,7 @@ "read_only": 1 }, { + "columns": 1, "fieldname": "credit", "fieldtype": "Float", "in_list_view": 1, @@ -196,14 +201,18 @@ "fieldtype": "Column Break" }, { + "columns": 1, "fieldname": "party_type", "fieldtype": "Link", + "in_list_view": 1, "label": "Party Type", "options": "DocType" }, { + "columns": 2, "fieldname": "party", "fieldtype": "Dynamic Link", + "in_list_view": 1, "label": "Party", "options": "party_type" }, @@ -246,7 +255,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-21 16:32:37.859603", + "modified": "2023-12-02 22:13:44.357581", "modified_by": "Administrator", "module": "Regional", "name": "FEC Import Line", diff --git a/erpnext/regional/doctype/fec_import_line/fec_import_line.py b/erpnext/regional/doctype/fec_import_line/fec_import_line.py index d48e0eec9c0..1761ce9a499 100644 --- a/erpnext/regional/doctype/fec_import_line/fec_import_line.py +++ b/erpnext/regional/doctype/fec_import_line/fec_import_line.py @@ -5,4 +5,44 @@ from frappe.model.document import Document class FECImportLine(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + account: DF.Link | None + accounting_journal: DF.Link | None + compauxlib: DF.SmallText | None + compauxnum: DF.Data | None + comptelib: DF.SmallText | None + comptenum: DF.Data | None + credit: DF.Float + datelet: DF.Data | None + debit: DF.Float + ecrituredate: DF.Data | None + ecriturelet: DF.Data | None + ecriturelib: DF.SmallText | None + ecriturenum: DF.Data | None + hashed_data: DF.Data | None + idevise: DF.Data | None + journalcode: DF.Data | None + journallib: DF.SmallText | None + montantdevise: DF.Float + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + party: DF.DynamicLink | None + party_type: DF.Link | None + piecedate: DF.Data | None + pieceref: DF.Data | None + posting_date: DF.Date | None + reconciliation_date: DF.Date | None + transaction_date: DF.Date | None + validation_date: DF.Date | None + validdate: DF.Data | None + # end: auto-generated types + pass -- GitLab From 981119e4696fff72d64442daa59d310459c6134b Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Sat, 2 Dec 2023 22:23:38 +0100 Subject: [PATCH 003/155] fix: Handle errors during import --- .../regional/doctype/fec_import/fec_import.js | 16 ++++++++++++---- .../regional/doctype/fec_import/fec_import.json | 15 ++++++++++++++- .../regional/doctype/fec_import/fec_import.py | 4 +++- erpnext/translations/fr.csv | 1 + 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/doctype/fec_import/fec_import.js b/erpnext/regional/doctype/fec_import/fec_import.js index 4365669585d..dea587cc449 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.js +++ b/erpnext/regional/doctype/fec_import/fec_import.js @@ -41,10 +41,18 @@ frappe.ui.form.on('FEC Import', { method: "upload_fec", doc: frm.doc, }).then(r => { - frappe.show_alert({ - message: __("Import finished. Please check the imported documents."), - indicator: "green" - }) + frm.reload_doc() + if (frm.doc.error) { + frappe.show_alert({ + message: __("An error occured while importing your FEC."), + indicator: "red" + }) + } else { + frappe.show_alert({ + message: __("Import finished. Please check the imported documents."), + indicator: "green" + }) + } }) }); diff --git a/erpnext/regional/doctype/fec_import/fec_import.json b/erpnext/regional/doctype/fec_import/fec_import.json index 606234b89bb..f797374c1ba 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.json +++ b/erpnext/regional/doctype/fec_import/fec_import.json @@ -12,6 +12,8 @@ "naming_series", "description", "fec_file", + "section_break_rdza", + "error", "section_break_3", "company", "column_break_lduy", @@ -125,6 +127,17 @@ "fieldname": "date_format", "fieldtype": "Data", "label": "Date Format" + }, + { + "fieldname": "section_break_rdza", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:doc.error", + "fieldname": "error", + "fieldtype": "Small Text", + "label": "Error", + "read_only": 1 } ], "index_web_pages_for_search": 1, @@ -135,7 +148,7 @@ "link_fieldname": "fec_import" } ], - "modified": "2023-12-02 22:18:33.685975", + "modified": "2023-12-02 22:20:22.136460", "modified_by": "Administrator", "module": "Regional", "name": "FEC Import", diff --git a/erpnext/regional/doctype/fec_import/fec_import.py b/erpnext/regional/doctype/fec_import/fec_import.py index 92735168dc2..086d7d9f135 100644 --- a/erpnext/regional/doctype/fec_import/fec_import.py +++ b/erpnext/regional/doctype/fec_import/fec_import.py @@ -26,6 +26,7 @@ class FECImport(Document): company: DF.Link | None date_format: DF.Data | None + error: DF.SmallText | None fec_file: DF.Attach | None field_delimiter: DF.Literal["Tab", "Comma", "Semicolon", "Space"] from_date: DF.Date | None @@ -91,13 +92,14 @@ class FECImport(Document): if not self.import_settings: frappe.throw(_("Please select an import settings document")) + self.db_set("error", "") data = self.get_data() try: import_in_progress = FECImportDocumentCreator(settings=self, data=data) import_in_progress.import_data() except Exception: - print(frappe.get_traceback()) + self.db_set("error", frappe.get_traceback()) def get_data(self): fileid = frappe.db.get_value( diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 09835ddef3f..72aa36001d5 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -5333,6 +5333,7 @@ Import Supplier Invoice,Importer des factures d'achat, Import To Date,Importer jusqu'au, Import a FEC,Import un FEC, Import finished. Please check the imported documents.,Import terminé. Veuillez vérifier les documents importés., +An error occured while importing your FEC.,Une erreur s'est produite lors de l'import de votre FEC., Import in Bulk,Importer en Masse, Import of goods,Import de biens, Import of services,Import de services, -- GitLab From f9e88c254c65d430cce6c6a432956e737e924ac7 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Sat, 2 Dec 2023 23:05:56 +0100 Subject: [PATCH 004/155] chore: french translation for description --- erpnext/translations/fr.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 72aa36001d5..5499c3cffe8 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -13601,3 +13601,4 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s 🗃️ CRM Data & Transactions,🗃️ Données CRM & Transactions, 🗃️ Manufacturing Data & Transactions,🗃️ Données de production & Transactions, 🗃️ Projects Data,🗃️ Données des projets, +Leave empty for default format.
For available formats please read the Python documentation,Laissez vide pour le format par défaut.
Pour connaître les formats disponibles veuillez lire la documentation Python, \ No newline at end of file -- GitLab From 759875925068a7c9b2b826abec0b34bba7f45683 Mon Sep 17 00:00:00 2001 From: Gughan Ravikumar Date: Fri, 1 Dec 2023 11:07:17 +0530 Subject: [PATCH 005/155] fix: show item name as title instead of item group in BOM (#38478) Item fields in BOM used to show Item Group when Items were set to show title as link fields. Now they show Item Name instead --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 229f8853fff..6c0c92b6481 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1330,7 +1330,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): order_by = "idx desc, name, item_name" - fields = ["name", "item_group", "item_name", "description"] + fields = ["name", "item_name", "item_group", "description"] fields.extend( [field for field in searchfields if not field in ["name", "item_group", "description"]] ) -- GitLab From e6a912c02661e893ea6abb2cdf491740ea3d90da Mon Sep 17 00:00:00 2001 From: NandhiniDevi <95607404+Nandhinidevi123@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:34:38 +0530 Subject: [PATCH 006/155] add supplier group filter in purchase register (#38421) * add supplier group field in purchase register * Update purchase_register.js --- .../purchase_invoice/purchase_invoice.json | 18 ++++++++++-------- .../purchase_register/purchase_register.js | 6 ++++++ .../purchase_register/purchase_register.py | 2 ++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index f2f1db2b454..27753067a55 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -191,6 +191,7 @@ "additional_info_section", "is_internal_supplier", "represents_company", + "supplier_group", "column_break_147", "inter_company_invoice_reference", "is_old_subcontracting_flow", @@ -1632,20 +1633,21 @@ "fieldtype": "Check", "label": "Use Transaction Date Exchange Rate", "read_only": 1 + }, + { + "fetch_from": "supplier.supplier_group", + "fieldname": "supplier_group", + "fieldtype": "Link", + "label": "Supplier Group", + "options": "Supplier Group" } ], "icon": "uil uil-file-alt", "idx": 204, "is_sealed": 1, "is_submittable": 1, - "links": [ - { - "group": "Payment Request", - "link_doctype": "Payment Request", - "link_fieldname": "reference_name" - } - ], - "modified": "2023-11-27 17:45:46.289627", + "links": [], + "modified": "2023-11-29 15:35:44.697496", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js index cad09b76698..3fd81c62dfc 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.js +++ b/erpnext/accounts/report/purchase_register/purchase_register.js @@ -22,6 +22,12 @@ frappe.query_reports["Purchase Register"] = { "fieldtype": "Link", "options": "Supplier" }, + { + "fieldname":"supplier_group", + "label": __("Supplier Group"), + "fieldtype": "Link", + "options": "Supplier Group" + }, { "fieldname":"company", "label": __("Company"), diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index ff8d5fc26a1..474c1ff5914 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -407,6 +407,8 @@ def get_invoices(filters, additional_query_columns): if filters.get("supplier"): query = query.where(pi.supplier == filters.supplier) + if filters.get("supplier_group"): + query = query.where(pi.supplier_group == filters.supplier_group) query = get_conditions(filters, query, "Purchase Invoice") -- GitLab From c105355511acfe04108e8d933dce448dafe16672 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Thu, 30 Nov 2023 06:20:15 +0000 Subject: [PATCH 007/155] fix(pe): show split alert only on splitting --- .../accounts/doctype/payment_entry/payment_entry.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0f628d239bb..dad23aa3907 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1660,12 +1660,13 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list if not split_rows: continue - frappe.msgprint( - _("Splitting {0} {1} into {2} rows as per Payment Terms").format( - _(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows) - ), - alert=True, - ) + if len(split_rows) > 1: + frappe.msgprint( + _("Splitting {0} {1} into {2} rows as per Payment Terms").format( + _(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows) + ), + alert=True, + ) outstanding_invoices_after_split += split_rows continue -- GitLab From 83846c6822c08985ab01d819b4cdff1fb3673197 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 1 Dec 2023 11:27:54 +0530 Subject: [PATCH 008/155] fix: `AttributeError` while saving Purchase Invoice --- 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 817ef5cd86a..041623986d8 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -440,7 +440,7 @@ class BuyingController(SubcontractingController): if allow_to_edit_stock_qty: d.stock_qty = flt(d.stock_qty, d.precision("stock_qty")) - if d.get("received_stock_qty"): + if d.get("received_stock_qty") and d.meta.get_field("received_stock_qty"): d.received_stock_qty = flt(d.received_stock_qty, d.precision("received_stock_qty")) def validate_purchase_return(self): -- GitLab From e5c0df61f4be36ab986e72fd8be4fb29088a201c Mon Sep 17 00:00:00 2001 From: Sherin KR Date: Fri, 1 Dec 2023 19:07:28 +0530 Subject: [PATCH 009/155] chore: changed sort_order to DESC for customer (#38498) --- erpnext/selling/doctype/customer/customer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 567a85063af..b31feb599da 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -688,7 +688,7 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "customer_name", "track_changes": 1 -- GitLab From 61e65c2692324c7720df15ee61b27f0faaaae99c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 1 Dec 2023 21:42:22 +0530 Subject: [PATCH 010/155] fix: validation error has not throw for the batch (#38494) * fix: validation error has not throw for the batch * chore: fix test cases --- .../sales_invoice/test_sales_invoice.py | 1 + erpnext/controllers/buying_controller.py | 2 ++ erpnext/controllers/selling_controller.py | 1 + .../controllers/subcontracting_controller.py | 1 + .../serial_and_batch_bundle.py | 1 + .../stock/doctype/stock_entry/stock_entry.py | 1 + .../doctype/stock_entry/test_stock_entry.py | 36 +++++++++++++++++++ .../test_stock_ledger_entry.py | 4 +-- erpnext/stock/serial_batch_bundle.py | 8 +++-- 9 files changed, 50 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 3b9a46d2727..330a24599ba 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2637,6 +2637,7 @@ class TestSalesInvoice(FrappeTestCase): "voucher_type": "Sales Invoice", "voucher_no": si.name, "allow_zero_valuation": d.get("allow_zero_valuation"), + "voucher_detail_no": d.name, }, raise_error_if_no_rate=False, ) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 817ef5cd86a..d3563979d3c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -118,6 +118,7 @@ class BuyingController(SubcontractingController): "company": self.company, "voucher_type": self.doctype, "voucher_no": self.name, + "voucher_detail_no": row.name, }, raise_error_if_no_rate=False, ) @@ -371,6 +372,7 @@ class BuyingController(SubcontractingController): "voucher_type": self.doctype, "voucher_no": self.name, "allow_zero_valuation": d.get("allow_zero_valuation"), + "voucher_detail_no": d.name, }, raise_error_if_no_rate=False, ) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 16ae6135023..51b123b747c 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -444,6 +444,7 @@ class SellingController(StockController): "company": self.company, "voucher_type": self.doctype, "voucher_no": self.name, + "voucher_detail_no": d.name, "allow_zero_valuation": d.get("allow_zero_valuation"), }, raise_error_if_no_rate=False, diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 3d55a087bd8..f88e3ebf534 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -880,6 +880,7 @@ class SubcontractingController(StockController): "posting_date": self.posting_date, "posting_time": self.posting_time, "qty": -1 * item.consumed_qty, + "voucher_detail_no": item.name, "serial_and_batch_bundle": item.serial_and_batch_bundle, } ) 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 3c824fd590a..2f8a7f8ae6e 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 @@ -216,6 +216,7 @@ class SerialandBatchBundle(Document): "serial_nos": [row.serial_no for row in self.entries if row.serial_no], "batch_nos": {row.batch_no: row for row in self.entries if row.batch_no}, "voucher_type": self.voucher_type, + "voucher_detail_no": self.voucher_detail_no, } ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b06de2e2fce..8934f8e4ccd 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -807,6 +807,7 @@ class StockEntry(StockController): "company": self.company, "allow_zero_valuation": item.allow_zero_valuation_rate, "serial_and_batch_bundle": item.serial_and_batch_bundle, + "voucher_detail_no": item.name, } ) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index d6fb9a16595..025cd761be0 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -936,6 +936,42 @@ class TestStockEntry(FrappeTestCase): stock_entry.insert() self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items]) + def test_nagative_stock_for_batch(self): + item = make_item( + "_Test Batch Negative Item", + { + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B-BATCH-.##", + "is_stock_item": 1, + }, + ) + + make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100) + + ste = frappe.new_doc("Stock Entry") + ste.purpose = "Material Issue" + ste.company = "_Test Company" + for qty in [50, 20, 30]: + ste.append( + "items", + { + "item_code": item.name, + "s_warehouse": "_Test Warehouse - _TC", + "qty": qty, + "uom": item.stock_uom, + "stock_uom": item.stock_uom, + "conversion_factor": 1, + "transfer_qty": qty, + }, + ) + + ste.set_stock_entry_type() + ste.insert() + make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=50, basic_rate=100) + + self.assertRaises(frappe.ValidationError, ste.submit) + def test_same_serial_nos_in_repack_or_manufacture_entries(self): s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos_from_bundle(s1.get("items")[0].serial_and_batch_bundle) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 975cc3d456a..37ce5ed1eff 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -483,9 +483,9 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list) sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"]) svd_list = [-1 * d["stock_value_difference"] for d in sle_details] - expected_incoming_rates = expected_abs_svd = sorted([75.0, 125.0, 75.0, 125.0]) + expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0] - self.assertEqual(expected_abs_svd, sorted(svd_list), "Incorrect 'Stock Value Difference' values") + self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values") for dn, incoming_rate in zip(dns, expected_incoming_rates): self.assertTrue( dn.items[0].incoming_rate in expected_abs_svd, diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index f2215f57c97..d1b9ea6c2f4 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -416,7 +416,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation): .orderby(bundle.posting_date, bundle.posting_time, bundle.creation) ) - # Important to exclude the current voucher + # Important to exclude the current voucher to calculate correct the stock value difference if self.sle.voucher_no: query = query.where(bundle.voucher_no != self.sle.voucher_no) @@ -549,8 +549,10 @@ class BatchNoValuation(DeprecatedBatchNoValuation): .groupby(child.batch_no) ) - # Important to exclude the current voucher - if self.sle.voucher_no: + # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference + if self.sle.voucher_detail_no: + query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no) + elif self.sle.voucher_no: query = query.where(parent.voucher_no != self.sle.voucher_no) if timestamp_condition: -- GitLab From 93379e67f44753adf15d19c95b8397723298fd05 Mon Sep 17 00:00:00 2001 From: Corin Wenger <119770691+strongjaw15@users.noreply.github.com> Date: Sat, 2 Dec 2023 08:54:46 -0500 Subject: [PATCH 011/155] fix: Move SMS Log module from ERPNext to Frappe (#38506) Move SMS Log module from ERPNext to Frappe SMS Log module moved to Frappe Core from ERPNext Utilities so that Frappe send_sms() works sans-ERPNext. --- erpnext/utilities/doctype/sms_log/README.md | 1 - erpnext/utilities/doctype/sms_log/sms_log.js | 8 -------- 2 files changed, 9 deletions(-) delete mode 100644 erpnext/utilities/doctype/sms_log/README.md delete mode 100644 erpnext/utilities/doctype/sms_log/sms_log.js diff --git a/erpnext/utilities/doctype/sms_log/README.md b/erpnext/utilities/doctype/sms_log/README.md deleted file mode 100644 index 9ee2b79ef07..00000000000 --- a/erpnext/utilities/doctype/sms_log/README.md +++ /dev/null @@ -1 +0,0 @@ -Log of SMS sent via SMS Center. \ No newline at end of file diff --git a/erpnext/utilities/doctype/sms_log/sms_log.js b/erpnext/utilities/doctype/sms_log/sms_log.js deleted file mode 100644 index f5358e84093..00000000000 --- a/erpnext/utilities/doctype/sms_log/sms_log.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('SMS Log', { - refresh: function(frm) { - - } -}); -- GitLab From 371267c824b15b724206c1a85f85cfbb86bb86b3 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Tue, 28 Nov 2023 13:19:49 -0500 Subject: [PATCH 012/155] fix: don't consider cancelled entries --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0f628d239bb..c7fedaa55ce 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1957,7 +1957,8 @@ def get_outstanding_on_journal_entry(name): "END as outstanding_amount " "FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) " "AND party_type IS NOT NULL " - 'AND party_type != ""', + 'AND party_type != ""' + "AND is_cancelled = 0", (name, name), as_dict=1, ) -- GitLab From 98a632f88f4f2453ee5c2e5d397aef37b854fa52 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 29 Nov 2023 20:59:23 +0000 Subject: [PATCH 013/155] refactor: get outstanding journal entry using query builder --- .../doctype/payment_entry/payment_entry.py | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c7fedaa55ce..99ff209292c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -7,7 +7,10 @@ from functools import reduce import frappe from frappe import ValidationError, _, qb, scrub, throw -from frappe.utils import cint, comma_and, comma_or, flt, fmt_money, getdate, nowdate +from frappe.utils import cint, comma_or, flt, getdate, nowdate +from frappe.utils.data import comma_and, fmt_money +from pypika import Case +from pypika.functions import Coalesce, Sum import erpnext from erpnext.accounts.doctype.bank_account.bank_account import ( @@ -1949,19 +1952,24 @@ def get_company_defaults(company): def get_outstanding_on_journal_entry(name): - res = frappe.db.sql( - "SELECT " - 'CASE WHEN party_type IN ("Customer") ' - "THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) " - "ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) " - "END as outstanding_amount " - "FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) " - "AND party_type IS NOT NULL " - 'AND party_type != ""' - "AND is_cancelled = 0", - (name, name), - as_dict=1, - ) + gl = frappe.qb.DocType("GL Entry") + res = ( + frappe.qb.from_(gl) + .select( + Case() + .when( + gl.party_type == "Customer", + Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0), + ) + .else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0)) + .as_("outstanding_amount") + ) + .where( + (Coalesce(gl.party_type, "") != "") + & (gl.is_cancelled == 0) + & ((gl.voucher_no == name) | (gl.against_voucher == name)) + ) + ).run(as_dict=True) outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0 -- GitLab From cdb910058f17b637b602f9491625f892b1b1881b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sat, 2 Dec 2023 15:22:11 +0100 Subject: [PATCH 014/155] fix: german translations for Lost Quotations (#38435) --- erpnext/translations/de.csv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 6bfeb5e3039..ac9528b0e0f 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -8967,3 +8967,7 @@ Book Tax Loss on Early Payment Discount,Umsatzsteueranteil bei Skonto berücksic Split Early Payment Discount Loss into Income and Tax Loss,"Skontobetrag in Aufwand und Umsatzsteuerkorrektur aufteilen", Approve,Genehmigen, Reject,Ablehnen, +Lost Quotations,Verlorene Angebote, +Lost Quotations %,Verlorene Angebote %, +Lost Value,Verlorener Wert, +Lost Value %,Verlorener Wert %, -- GitLab From 1bca2b208ad0e48f8d9d0e0c2902c444c45fb6c9 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Sat, 2 Dec 2023 19:53:00 +0530 Subject: [PATCH 015/155] fix: SO ordered qty on PO item removal (#38378) * fix: update ordered_qty for SO when PO items removed * refactor: use cached value --------- Co-authored-by: Deepesh Garg --- .../doctype/purchase_order/purchase_order.py | 14 ++++++++++++++ erpnext/controllers/accounts_controller.py | 3 +++ 2 files changed, 17 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 93437545e9d..734eeb65685 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -488,6 +488,20 @@ class PurchaseOrder(BuyingController): return result + def update_ordered_qty_in_so_for_removed_items(self, removed_items): + """ + Updates ordered_qty in linked SO when item rows are removed using Update Items + """ + if not self.is_against_so(): + return + for item in removed_items: + prev_ordered_qty = frappe.get_cached_value( + "Sales Order Item", item.get("sales_order_item"), "ordered_qty" + ) + frappe.db.set_value( + "Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty + ) + def auto_create_subcontracting_order(self): if self.is_subcontracted and not self.is_old_subcontracting_flow: if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b8f79ddfa56..4dc42860b8c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3024,6 +3024,9 @@ def validate_and_delete_children(parent, data) -> bool: d.cancel() d.delete() + if parent.doctype == "Purchase Order": + parent.update_ordered_qty_in_so_for_removed_items(deleted_children) + # need to update ordered qty in Material Request first # bin uses Material Request Items to recalculate & update parent.update_prevdoc_status() -- GitLab From f333a52f48e4f106d28b5400705d4566b3f735e9 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 2 Dec 2023 20:06:19 +0530 Subject: [PATCH 016/155] fix: use predefined onload property `load_after_mapping` (#38209) --- .../doctype/purchase_invoice/purchase_invoice.py | 2 -- .../doctype/sales_invoice/sales_invoice.py | 1 - .../doctype/purchase_order/purchase_order.py | 3 --- .../supplier_quotation/supplier_quotation.py | 1 - erpnext/controllers/sales_and_purchase_return.py | 2 -- erpnext/public/js/controllers/transaction.js | 15 ++++++++------- erpnext/selling/doctype/quotation/quotation.py | 5 ----- .../selling/doctype/sales_order/sales_order.py | 3 --- .../stock/doctype/delivery_note/delivery_note.py | 2 -- .../doctype/purchase_receipt/purchase_receipt.py | 1 - 10 files changed, 8 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 9b696df6d77..f217f758be1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1901,6 +1901,4 @@ def make_purchase_receipt(source_name, target_doc=None): target_doc, ) - doc.set_onload("ignore_price_list", True) - return doc diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 6a162d734a1..5dfe0410ce6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2035,7 +2035,6 @@ def make_delivery_note(source_name, target_doc=None): set_missing_values, ) - doclist.set_onload("ignore_price_list", True) return doclist diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 93437545e9d..af8e0c8b184 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -579,8 +579,6 @@ def make_purchase_receipt(source_name, target_doc=None): set_missing_values, ) - doc.set_onload("ignore_price_list", True) - return doc @@ -660,7 +658,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions postprocess, ignore_permissions=ignore_permissions, ) - doc.set_onload("ignore_price_list", True) return doc diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index c519635f518..ca4c5cdf018 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -168,7 +168,6 @@ def make_purchase_order(source_name, target_doc=None): set_missing_values, ) - doclist.set_onload("ignore_price_list", True) return doclist diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 8f79ae09efc..44cc8cf4b7d 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -586,8 +586,6 @@ def make_return_doc( set_missing_values, ) - doclist.set_onload("ignore_price_list", True) - return doclist diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0424df58db1..10cbcadda1c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -374,7 +374,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe onload_post_render() { if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length - && !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) { + && !this.frm.doc.__onload?.load_after_mapping) { frappe.after_ajax(() => this.apply_default_taxes()); } else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"] && !this.frm.doc.is_pos) { @@ -970,9 +970,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let me = this; this.set_dynamic_labels(); let company_currency = this.get_company_currency(); - // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc + // Added `load_after_mapping` to determine if document is loading after mapping from another doc if(this.frm.doc.currency && this.frm.doc.currency !== company_currency - && !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) { + && !this.frm.doc.__onload?.load_after_mapping) { + this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency, function(exchange_rate) { if(exchange_rate != me.frm.doc.conversion_rate) { @@ -1003,7 +1004,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } if(flt(this.frm.doc.conversion_rate)>0.0) { - if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) { + if(this.frm.doc.__onload?.load_after_mapping) { this.calculate_taxes_and_totals(); } else if (!this.in_apply_price_list){ this.apply_price_list(); @@ -1090,9 +1091,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.set_dynamic_labels(); var company_currency = this.get_company_currency(); - // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc + // Added `load_after_mapping` to determine if document is loading after mapping from another doc if(this.frm.doc.price_list_currency !== company_currency && - !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) { + !this.frm.doc.__onload?.load_after_mapping) { this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency, function(exchange_rate) { me.frm.set_value("plc_conversion_rate", exchange_rate); @@ -1485,7 +1486,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } // Target doc created from a mapped doc - if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) { + if (this.frm.doc.__onload?.load_after_mapping) { // Calculate totals even though pricing rule is not applied. // `apply_pricing_rule` is triggered due to change in data which most likely contributes to Total. if (calculate_taxes_and_totals) me.calculate_taxes_and_totals(); diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 10590a6db69..2c74f39e7fa 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -382,9 +382,6 @@ def _make_sales_order(source_name, target_doc=None, customer_group=None, ignore_ ignore_permissions=ignore_permissions, ) - # postprocess: fetch shipping address, set missing values - doclist.set_onload("ignore_price_list", True) - return doclist @@ -427,8 +424,6 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): ignore_permissions=ignore_permissions, ) - doclist.set_onload("ignore_price_list", True) - return doclist diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c1b3875e14d..c64eacbb2db 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -934,7 +934,6 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): # Should be called after mapping items. set_missing_values(so, target_doc) - target_doc.set_onload("ignore_price_list", True) return target_doc @@ -1021,8 +1020,6 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): if automatically_fetch_payment_terms: doclist.set_payment_schedule() - doclist.set_onload("ignore_price_list", True) - return doclist diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index cfcab9c29cd..1583ad91d9d 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -846,8 +846,6 @@ def make_sales_invoice(source_name, target_doc=None): if automatically_fetch_payment_terms: doc.set_payment_schedule() - doc.set_onload("ignore_price_list", True) - return doc diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 8647528ea50..6a379db1c32 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1096,7 +1096,6 @@ def make_purchase_invoice(source_name, target_doc=None): set_missing_values, ) - doclist.set_onload("ignore_price_list", True) return doclist -- GitLab From e2bae1b3ac7158f5e96a6ac835cb31b648c25e9e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 2 Dec 2023 23:12:12 +0530 Subject: [PATCH 017/155] fix: better overlap logic for job card (#38432) --- .../doctype/job_card/job_card.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index f303531aee1..ef30a3ce1f9 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -185,8 +185,7 @@ class JobCard(Document): # override capacity for employee production_capacity = 1 - overlap_count = self.get_overlap_count(time_logs) - if time_logs and production_capacity > overlap_count: + if not self.has_overlap(production_capacity, time_logs): return {} if self.workstation_type and time_logs: @@ -196,16 +195,15 @@ class JobCard(Document): return time_logs[-1] - @staticmethod - def get_overlap_count(time_logs): - count = 1 + def has_overlap(self, production_capacity, time_logs): + overlap = False + if production_capacity == 1 and len(time_logs) > 0: + return True # Check overlap exists or not between the overlapping time logs with the current Job Card - for idx, row in enumerate(time_logs): - next_idx = idx - if idx + 1 < len(time_logs): - next_idx = idx + 1 - next_row = time_logs[next_idx] + for row in time_logs: + count = 1 + for next_row in time_logs: if row.name == next_row.name: continue @@ -225,7 +223,10 @@ class JobCard(Document): ): count += 1 - return count + if count > production_capacity: + return True + + return overlap def get_time_logs(self, args, doctype, check_next_available_slot=False): jc = frappe.qb.DocType("Job Card") -- GitLab From ad9911b623547025057b104575c170c70cba0adf Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 1 Dec 2023 14:45:01 +0530 Subject: [PATCH 018/155] fix: don't update previous doc on rate change --- erpnext/controllers/accounts_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b8f79ddfa56..3f5c9df4f9e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3313,7 +3313,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if parent_doctype == "Purchase Order": update_last_purchase_rate(parent, is_submit=1) - parent.update_prevdoc_status() + + if any_qty_changed or items_added_or_removed or any_conversion_factor_changed: + parent.update_prevdoc_status() + parent.update_requested_qty() parent.update_ordered_qty() parent.update_ordered_and_reserved_qty() -- GitLab From aa23e404fa7b04acfe6698a68c0157c2db3d4681 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 3 Dec 2023 10:33:31 +0530 Subject: [PATCH 019/155] fix: item group filter in sales person wise report --- .../sales_person_wise_transaction_summary.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py index 4b95c8d8617..9f3ba0da8bd 100644 --- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py +++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py @@ -3,8 +3,8 @@ import frappe -from frappe import _, msgprint -from frappe.utils import flt +from frappe import _, msgprint, qb +from frappe.query_builder import Criterion from erpnext import get_company_currency @@ -215,24 +215,33 @@ def get_conditions(filters, date_field): if items: conditions.append("dt_item.item_code in (%s)" % ", ".join(["%s"] * len(items))) values += items + else: + # return empty result, if no items are fetched after filtering on 'item group' and 'brand' + conditions.append("dt_item.item_code = Null") return " and ".join(conditions), values def get_items(filters): - if filters.get("item_group"): - key = "item_group" - elif filters.get("brand"): - key = "brand" - else: - key = "" + item = qb.DocType("Item") - items = [] - if key: - items = frappe.db.sql_list( - """select name from tabItem where %s = %s""" % (key, "%s"), (filters[key]) + item_query_conditions = [] + if filters.get("item_group"): + # Handle 'Parent' nodes as well. + item_group = qb.DocType("Item Group") + lft, rgt = frappe.db.get_all( + "Item Group", filters={"name": filters.get("item_group")}, fields=["lft", "rgt"], as_list=True + )[0] + item_group_query = ( + qb.from_(item_group) + .select(item_group.name) + .where((item_group.lft >= lft) & (item_group.rgt <= rgt)) ) + item_query_conditions.append(item.item_group.isin(item_group_query)) + if filters.get("brand"): + item_query_conditions.append(item.brand == filters.get("brand")) + items = qb.from_(item).select(item.name).where(Criterion.all(item_query_conditions)).run() return items -- GitLab From 6fe225ac86e66cf686025aa9f31ac93ce9ed2461 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 3 Dec 2023 10:50:18 +0530 Subject: [PATCH 020/155] fix: remove hardcoded, implicit rounding loss allowance --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 3b5698b118a..977cfe94f8a 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -192,7 +192,7 @@ class ExchangeRateRevaluation(Document): # round off balance based on currency precision # and consider debit-credit difference allowance currency_precision = get_currency_precision() - rounding_loss_allowance = float(rounding_loss_allowance) or 0.05 + rounding_loss_allowance = float(rounding_loss_allowance) for acc in account_details: acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision) if abs(acc.balance_in_account_currency) <= rounding_loss_allowance: -- GitLab From 3c9f19ab8a367161241133a4b79cc1346d3eeffc Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Sun, 3 Dec 2023 23:22:04 +0530 Subject: [PATCH 021/155] fix: Group By in Item-wise Purchase Register (#38503) --- .../item_wise_purchase_register/item_wise_purchase_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index ad196a90328..9c6e2d0dc39 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -319,7 +319,7 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice`.unrealized_profit_loss_account, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, + `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, -- GitLab From 77a6f10ddf87d415723e252f6072546037aa4d50 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 20 Nov 2023 11:10:56 +0000 Subject: [PATCH 022/155] fix: exclude `invoice_doctypes` from party advance --- erpnext/accounts/party.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index ed729fcca01..3341b012651 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -1005,6 +1005,9 @@ def get_partywise_advanced_payment_amount( if party: query = query.where(ple.party == party) + if invoice_doctypes := frappe.get_hooks("invoice_doctypes"): + query = query.where(ple.voucher_type.notin(invoice_doctypes)) + data = query.run() if data: return frappe._dict(data) -- GitLab From f6beee4b95d401657c09222951ab85286c3df6c4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 11:58:44 +0530 Subject: [PATCH 023/155] fix(ux): stock-item filter for Item Code field --- erpnext/stock/report/stock_analytics/stock_analytics.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js index ea7bf5688e6..e033fd9a9df 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.js +++ b/erpnext/stock/report/stock_analytics/stock_analytics.js @@ -17,6 +17,7 @@ frappe.query_reports["Stock Analytics"] = { fieldtype: "Link", options:"Item", default: "", + get_query: () => ({filters: { 'is_stock_item': 1 }}), }, { fieldname: "value_quantity", -- GitLab From ea680bbb585643ff5903f01fc7b958464af5733f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 12:00:36 +0530 Subject: [PATCH 024/155] fix: don't show non-stock items in Stock Analytics report --- erpnext/stock/report/stock_analytics/stock_analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 6c5b58c6e45..ab48181c48d 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -270,7 +270,7 @@ def get_items(filters): if item_code := filters.get("item_code"): return [item_code] else: - item_filters = {} + item_filters = {"is_stock_item": 1} if item_group := filters.get("item_group"): children = get_descendants_of("Item Group", item_group, ignore_permissions=True) item_filters["item_group"] = ("in", children + [item_group]) -- GitLab From b580ac46059fce669bf2fbb8d973ab782ca2e44b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 12:05:27 +0530 Subject: [PATCH 025/155] fix: `linter` --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 23ed84c8918..77789721e6c 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1087,7 +1087,7 @@ class ReceivablePayableReport(object): ) if self.filters.show_remarks: - self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200), + self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200) def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120): if not fieldname: -- GitLab From 02bf273aad7d8f409a5c277c14898082a5ac1bb9 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 07:17:25 +0000 Subject: [PATCH 026/155] fix: delete all files related to SMS log --- erpnext/utilities/doctype/sms_log/__init__.py | 1 - .../utilities/doctype/sms_log/sms_log.json | 99 ------------------- erpnext/utilities/doctype/sms_log/sms_log.py | 10 -- .../utilities/doctype/sms_log/test_sms_log.py | 13 --- 4 files changed, 123 deletions(-) delete mode 100644 erpnext/utilities/doctype/sms_log/__init__.py delete mode 100644 erpnext/utilities/doctype/sms_log/sms_log.json delete mode 100644 erpnext/utilities/doctype/sms_log/sms_log.py delete mode 100644 erpnext/utilities/doctype/sms_log/test_sms_log.py diff --git a/erpnext/utilities/doctype/sms_log/__init__.py b/erpnext/utilities/doctype/sms_log/__init__.py deleted file mode 100644 index 8b137891791..00000000000 --- a/erpnext/utilities/doctype/sms_log/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/erpnext/utilities/doctype/sms_log/sms_log.json b/erpnext/utilities/doctype/sms_log/sms_log.json deleted file mode 100644 index 09ab4d1de8a..00000000000 --- a/erpnext/utilities/doctype/sms_log/sms_log.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "actions": [], - "autoname": "SYS-SMS-.#####", - "creation": "2012-03-27 14:36:47", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "sender_name", - "sent_on", - "column_break0", - "message", - "sec_break1", - "no_of_requested_sms", - "requested_numbers", - "column_break1", - "no_of_sent_sms", - "sent_to" - ], - "fields": [ - { - "fieldname": "sender_name", - "fieldtype": "Data", - "label": "Sender Name", - "read_only": 1 - }, - { - "fieldname": "sent_on", - "fieldtype": "Date", - "label": "Sent On", - "read_only": 1 - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "message", - "fieldtype": "Small Text", - "label": "Message", - "read_only": 1 - }, - { - "fieldname": "sec_break1", - "fieldtype": "Section Break", - "options": "Simple" - }, - { - "fieldname": "no_of_requested_sms", - "fieldtype": "Int", - "label": "No of Requested SMS", - "read_only": 1 - }, - { - "fieldname": "requested_numbers", - "fieldtype": "Code", - "label": "Requested Numbers", - "read_only": 1 - }, - { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "no_of_sent_sms", - "fieldtype": "Int", - "label": "No of Sent SMS", - "read_only": 1 - }, - { - "fieldname": "sent_to", - "fieldtype": "Code", - "label": "Sent To", - "read_only": 1 - } - ], - "icon": "uil uil-comments-alt", - "idx": 1, - "links": [], - "modified": "2023-02-20 16:33:50.446725", - "modified_by": "Administrator", - "module": "Utilities", - "name": "SMS Log", - "owner": "Administrator", - "permissions": [ - { - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager" - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/utilities/doctype/sms_log/sms_log.py b/erpnext/utilities/doctype/sms_log/sms_log.py deleted file mode 100644 index ff220d8bcb9..00000000000 --- a/erpnext/utilities/doctype/sms_log/sms_log.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe.model.document import Document - - -class SMSLog(Document): - pass diff --git a/erpnext/utilities/doctype/sms_log/test_sms_log.py b/erpnext/utilities/doctype/sms_log/test_sms_log.py deleted file mode 100644 index 8590d81616f..00000000000 --- a/erpnext/utilities/doctype/sms_log/test_sms_log.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - - -import unittest - -import frappe - -# test_records = frappe.get_test_records('SMS Log') - - -class TestSMSLog(unittest.TestCase): - pass -- GitLab From f322328556701b04a39f5c216edd28dba7144989 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 8 Nov 2023 20:56:44 +0100 Subject: [PATCH 027/155] feat: Get supplier cost information in item table --- erpnext/public/js/utils/sales_common.js | 27 +++++++ .../quotation_item/quotation_item.json | 40 +++++++++++ .../doctype/quotation_item/quotation_item.py | 71 ++++++++++++++++++- erpnext/stock/get_item_details.py | 32 +++++++++ 4 files changed, 168 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 5514963c966..df45ff97e76 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -374,6 +374,33 @@ erpnext.sales_common = { this.frm.set_value("discount_amount", 0); this.frm.set_value("additional_discount_percentage", 0); } + + supplier(doc, cdt, cdn) { + const item = frappe.get_doc(cdt, cdn); + + if (item.supplier) { + frappe.call({ + method: "erpnext.stock.get_item_details.get_supplier_cost", + args: { + out: { + item_code: item.item_code, + uom: item.uom, + supplier: item.supplier, + transaction_date: doc.transaction_date, + qty: item.qty + } + } + }).then(r => { + if(!r.exc) { + frappe.model.set_value(cdt, cdn, "unit_cost_price", r.message?.unit_cost_price || 0.0); + frappe.model.set_value(cdt, cdn, "cost_price", r.message?.unit_cost || 0.0); + } + }); + } else { + frappe.model.set_value(cdt, cdn, "unit_cost_price", 0.0); + frappe.model.set_value(cdt, cdn, "cost_price", 0.0); + } + } }; } } diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 4816d39a609..124d5ea4172 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -54,8 +54,14 @@ "has_alternative_item", "section_break_43", "valuation_rate", + "last_purchase_rate", "column_break_45", "gross_profit", + "supplier_price_section", + "supplier", + "column_break_uapk", + "unit_cost_price", + "cost_price", "item_weight_details", "weight_per_unit", "total_weight", @@ -687,6 +693,40 @@ "fieldtype": "Check", "label": "Is Recurring Item", "read_only": 1 + }, + { + "fieldname": "last_purchase_rate", + "fieldtype": "Currency", + "label": "Last Purchase Rate", + "read_only": 1 + }, + { + "fieldname": "column_break_uapk", + "fieldtype": "Column Break" + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "unit_cost_price", + "fieldtype": "Currency", + "label": "Unit Cost Price", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "supplier_price_section", + "fieldtype": "Section Break", + "label": "Supplier Price" + }, + { + "fieldname": "cost_price", + "fieldtype": "Currency", + "label": "Cost Price", + "read_only": 1 } ], "idx": 1, diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index 99f4cc8f085..19d6b3464f4 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -2,9 +2,76 @@ # License: GNU General Public License v3. See license.txt -import frappe +# import frappe from frappe.model.document import Document class QuotationItem(Document): - pass + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + actual_qty: DF.Float + additional_notes: DF.Text | None + against_blanket_order: DF.Check + amount: DF.Currency + base_amount: DF.Currency + base_net_amount: DF.Currency + base_net_rate: DF.Currency + base_price_list_rate: DF.Currency + base_rate: DF.Currency + base_rate_with_margin: DF.Currency + blanket_order: DF.Link | None + blanket_order_rate: DF.Currency + brand: DF.Link | None + conversion_factor: DF.Float + cost_price: DF.Currency + customer_item_code: DF.Data | None + description: DF.TextEditor | None + discount_amount: DF.Currency + discount_percentage: DF.Percent + gross_profit: DF.Currency + has_alternative_item: DF.Check + image: DF.Attach | None + is_alternative: DF.Check + is_free_item: DF.Check + is_recurring_item: DF.Check + item_booking: DF.Link | None + item_code: DF.Link | None + item_group: DF.Link | None + item_name: DF.Data + item_tax_rate: DF.Code | None + item_tax_template: DF.Link | None + last_purchase_rate: DF.Currency + margin_rate_or_amount: DF.Float + margin_type: DF.Literal["", "Percentage", "Amount"] + net_amount: DF.Currency + net_rate: DF.Currency + page_break: DF.Check + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + prevdoc_docname: DF.DynamicLink | None + prevdoc_doctype: DF.Link | None + price_list_rate: DF.Currency + pricing_rules: DF.SmallText | None + projected_qty: DF.Float + qty: DF.Float + rate: DF.Currency + rate_with_margin: DF.Currency + stock_qty: DF.Float + stock_uom: DF.Link | None + stock_uom_rate: DF.Currency + supplier: DF.Link | None + total_weight: DF.Float + unit_cost_price: DF.Currency + uom: DF.Link + valuation_rate: DF.Currency + warehouse: DF.Link | None + weight_per_unit: DF.Float + weight_uom: DF.Link | None + # end: auto-generated types diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 30493cd43a2..fc410dcd6a6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -136,6 +136,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.bom = args.get("bom") or get_default_bom(args.item_code) get_gross_profit(out) + get_supplier_cost(out) + if args.doctype == "Material Request": out.rate = args.rate or out.price_list_rate out.amount = flt(args.qty) * flt(out.rate) @@ -1413,3 +1415,33 @@ def get_blanket_order_details(args): blanket_order_details = blanket_order_details[0] if blanket_order_details else "" return blanket_order_details + + +@frappe.whitelist() +def get_supplier_cost(out): + if isinstance(out, str): + out = frappe.parse_json(out) + + price_list_args = out.copy() + price_list_args["customer"] = None + + if out.get("supplier"): + if not ( + buying_price_list := frappe.get_cached_value("Supplier", out["supplier"], "default_price_list") + ): + buying_price_list = frappe.db.get_single_value("Buying Settings", "buying_price_list") + + price_list_args["price_list"] = buying_price_list + + if not ( + unit_cost_price := get_price_list_rate_for(price_list_args, price_list_args.get("item_code")) + ): + price_list_args["supplier"] = None + unit_cost_price = get_price_list_rate_for(price_list_args, price_list_args.get("item_code")) + + if unit_cost_price: + out.update( + {"unit_cost_price": unit_cost_price, "cost_price": flt(unit_cost_price) * flt(out["qty"])} + ) + + return out -- GitLab From 4b5b8dd94a0255272fcd015fd89d49bde61f54cc Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 9 Nov 2023 21:14:24 +0100 Subject: [PATCH 028/155] fix: key for cost price --- erpnext/public/js/utils/sales_common.js | 2 +- erpnext/stock/get_item_details.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index df45ff97e76..a13e9ca68a1 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -393,7 +393,7 @@ erpnext.sales_common = { }).then(r => { if(!r.exc) { frappe.model.set_value(cdt, cdn, "unit_cost_price", r.message?.unit_cost_price || 0.0); - frappe.model.set_value(cdt, cdn, "cost_price", r.message?.unit_cost || 0.0); + frappe.model.set_value(cdt, cdn, "cost_price", r.message?.cost_price || 0.0); } }); } else { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index fc410dcd6a6..06cc215b90b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1443,5 +1443,5 @@ def get_supplier_cost(out): out.update( {"unit_cost_price": unit_cost_price, "cost_price": flt(unit_cost_price) * flt(out["qty"])} ) - + print(out) return out -- GitLab From f2610daa0ff771e2d1ead25cd8db885a75934e65 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 9 Nov 2023 21:15:05 +0100 Subject: [PATCH 029/155] chore: cleanup code --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 06cc215b90b..fc410dcd6a6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1443,5 +1443,5 @@ def get_supplier_cost(out): out.update( {"unit_cost_price": unit_cost_price, "cost_price": flt(unit_cost_price) * flt(out["qty"])} ) - print(out) + return out -- GitLab From c0c30ec912e32244652e9eaadaa7bfd35dd7dee9 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 9 Nov 2023 21:20:02 +0100 Subject: [PATCH 030/155] fix: indent --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index fc410dcd6a6..c6bb6b60a1f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1431,7 +1431,7 @@ def get_supplier_cost(out): ): buying_price_list = frappe.db.get_single_value("Buying Settings", "buying_price_list") - price_list_args["price_list"] = buying_price_list + price_list_args["price_list"] = buying_price_list if not ( unit_cost_price := get_price_list_rate_for(price_list_args, price_list_args.get("item_code")) -- GitLab From 42b80231a035ae88cc1f6a0d1a4ede7da6627ea5 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 09:45:58 +0100 Subject: [PATCH 031/155] fix: Add payment gateway to payment request if set in mode of payment --- .../doctype/subscription/subscription_transaction.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/subscription_transaction.py b/erpnext/accounts/doctype/subscription/subscription_transaction.py index 975de311988..e305add04ee 100644 --- a/erpnext/accounts/doctype/subscription/subscription_transaction.py +++ b/erpnext/accounts/doctype/subscription/subscription_transaction.py @@ -347,6 +347,13 @@ class SubscriptionPaymentRequestGenerator: ) ) + if self.subscription.mode_of_payment and ( + pg := frappe.db.get_value( + "Mode of Payment", self.subscription.mode_of_payment, "payment_gateway" + ) + ): + pr.payment_gateway = pg + if submit: pr.insert(ignore_permissions=True) self.subscription.set_state("payment_request", pr.name) -- GitLab From 515ed79974be29c008642ec448eb07bc7ba9d133 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 09:55:13 +0100 Subject: [PATCH 032/155] fix: Get payment gateway from mode of payment in payment requests --- .../doctype/payment_request/payment_request.py | 13 ++++++++++++- .../subscription/subscription_transaction.py | 7 ------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index a988dd865b9..0b137d63b84 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -655,6 +655,7 @@ def make_payment_request(*args, **kwargs): "print_format": args.print_format, "subscription": args.subscription, "payment_request_type": "Outward" if ref_doc.doctype == "Purchase Invoice" else "Inward", + "mode_of_payment": args.get("mode_of_payment"), } ) @@ -672,7 +673,9 @@ def make_payment_request(*args, **kwargs): if args.order_type == "Shopping Cart" or args.mute_email: pr.flags.mute_email = True - if args.get("payment_gateway") or args.order_type == "Shopping Cart": + if ( + args.get("payment_gateway") or args.get("mode_of_payment") or args.order_type == "Shopping Cart" + ): gateway_account = get_gateway_details(args) or frappe._dict() pr.update( { @@ -760,6 +763,14 @@ def get_gateway_details(args): # nosemgrep filters.update({"payment_gateway": args.get("payment_gateway")}) return get_payment_gateway_account(filters) + if args.get("mode_of_payment") and ( + payment_gateway := frappe.db.get_value( + "Mode of Payment", args.get("mode_of_payment"), "payment_gateway" + ) + ): + filters.update({"payment_gateway": payment_gateway}) + return get_payment_gateway_account(filters) + if args.order_type == "Shopping Cart": payment_gateway_account = _get_shopping_cart_settings().payment_gateway_account return get_payment_gateway_account(payment_gateway_account) diff --git a/erpnext/accounts/doctype/subscription/subscription_transaction.py b/erpnext/accounts/doctype/subscription/subscription_transaction.py index e305add04ee..975de311988 100644 --- a/erpnext/accounts/doctype/subscription/subscription_transaction.py +++ b/erpnext/accounts/doctype/subscription/subscription_transaction.py @@ -347,13 +347,6 @@ class SubscriptionPaymentRequestGenerator: ) ) - if self.subscription.mode_of_payment and ( - pg := frappe.db.get_value( - "Mode of Payment", self.subscription.mode_of_payment, "payment_gateway" - ) - ): - pr.payment_gateway = pg - if submit: pr.insert(ignore_permissions=True) self.subscription.set_state("payment_request", pr.name) -- GitLab From 15854d84d664c4a88656ca124d02b48823268c07 Mon Sep 17 00:00:00 2001 From: NIYAZ RAZAK <76736615+niyazrazak@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:09:49 +0300 Subject: [PATCH 033/155] fix: incorrect customer outstanding amount (#38475) * fix: incorrect customer outstanding amount * chore: resolve linting error Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/selling/doctype/customer/customer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 50af1b47883..a3c92836da9 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -636,7 +636,8 @@ def get_customer_outstanding( """ select sum(debit) - sum(credit) from `tabGL Entry` where party_type = 'Customer' - and party = %s and company=%s {0}""".format( + and is_cancelled = 0 and party = %s + and company=%s {0}""".format( cond ), (customer, company), -- GitLab From 29deeb61390f470c0bba670fd93a2a3d75adc3a7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 4 Dec 2023 15:02:47 +0530 Subject: [PATCH 034/155] fix: scan for serial or batch in bundle (#38534) --- .../js/utils/serial_no_batch_selector.js | 82 ++++++++++++++++--- .../serial_and_batch_bundle.py | 5 ++ 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 9267801839b..0de6774393a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -31,8 +31,19 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { secondary_action: () => this.edit_full_form(), }); - this.dialog.set_value("qty", this.item.qty); + this.dialog.set_value("qty", this.item.qty).then(() => { + if (this.item.serial_no) { + this.dialog.set_value("scan_serial_no", this.item.serial_no); + frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', ''); + } else if (this.item.batch_no) { + this.dialog.set_value("scan_batch_no", this.item.batch_no); + frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', ''); + } + }); + this.dialog.show(); + this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); + this.$scan_btn.css("display", "inline"); } get_serial_no_filters() { @@ -95,6 +106,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { if (this.item.has_serial_no) { fields.push({ fieldtype: 'Data', + options: 'Barcode', fieldname: 'scan_serial_no', label: __('Scan Serial No'), get_query: () => { @@ -106,15 +118,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }); } - if (this.item.has_batch_no && this.item.has_serial_no) { - fields.push({ - fieldtype: 'Column Break', - }); - } - - if (this.item.has_batch_no) { + if (this.item.has_batch_no && !this.item.has_serial_no) { fields.push({ fieldtype: 'Data', + options: 'Barcode', fieldname: 'scan_batch_no', label: __('Scan Batch No'), onchange: () => this.update_serial_batch_no() @@ -309,6 +316,14 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_auto_data() { + if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) { + return; + } + + if (this.item.serial_no || this.item.batch_no) { + return; + } + let { qty, based_on } = this.dialog.get_values(); if (!based_on) { @@ -340,16 +355,57 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { const { scan_serial_no, scan_batch_no } = this.dialog.get_values(); if (scan_serial_no) { - this.dialog.fields_dict.entries.df.data.push({ - serial_no: scan_serial_no + let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => { + if (d.serial_no === scan_serial_no) { + return d + } }); - this.dialog.fields_dict.scan_serial_no.set_value(''); + if (existing_row?.length) { + frappe.throw(__('Serial No {0} already exists', [scan_serial_no])); + } + + if (!this.item.has_batch_no) { + this.dialog.fields_dict.entries.df.data.push({ + serial_no: scan_serial_no + }); + + this.dialog.fields_dict.scan_serial_no.set_value(''); + } else { + frappe.call({ + method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_batch_no_from_serial_no', + args: { + serial_no: scan_serial_no, + }, + callback: (r) => { + if (r.message) { + this.dialog.fields_dict.entries.df.data.push({ + serial_no: scan_serial_no, + batch_no: r.message + }); + + this.dialog.fields_dict.scan_serial_no.set_value(''); + } + } + + }) + } } else if (scan_batch_no) { - this.dialog.fields_dict.entries.df.data.push({ - batch_no: scan_batch_no + let existing_row = this.dialog.fields_dict.entries.df.data.filter(d => { + if (d.batch_no === scan_batch_no) { + return d + } }); + if (existing_row?.length) { + existing_row[0].qty += 1; + } else { + this.dialog.fields_dict.entries.df.data.push({ + batch_no: scan_batch_no, + qty: 1 + }); + } + this.dialog.fields_dict.scan_batch_no.set_value(''); } 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 2f8a7f8ae6e..2d32eb346b1 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 @@ -1709,3 +1709,8 @@ def get_stock_ledgers_batches(kwargs): batches[key].qty += d.qty return batches + + +@frappe.whitelist() +def get_batch_no_from_serial_no(serial_no): + return frappe.get_cached_value("Serial No", serial_no, "batch_no") -- GitLab From c4a3c15cbbe795938f2df0b08a393e4ecec6c802 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 16:09:41 +0100 Subject: [PATCH 035/155] feat: Gross profit margin based on last purchase rate, supplier cost or valuation rate --- erpnext/public/js/controllers/transaction.js | 38 +++++++++++++++++-- erpnext/public/js/utils/sales_common.js | 7 +++- .../quotation_item/quotation_item.json | 18 ++++++++- .../doctype/quotation_item/quotation_item.py | 4 ++ .../selling_settings/selling_settings.json | 2 +- .../selling_settings/selling_settings.py | 32 ++++++++++++++++ erpnext/stock/doctype/item/item.json | 15 ++++++-- erpnext/stock/get_item_details.py | 17 +++++++-- erpnext/translations/fr.csv | 3 +- 9 files changed, 121 insertions(+), 15 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 10cbcadda1c..0a470b6641f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1,6 +1,12 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +const gross_profit_calculation_rules = { + "Valuation Rate": "valuation_rate", + "Last Purchase Rate": "last_purchase_rate", + "Supplier Cost Price": "cost_price" +} + erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals { setup() { super.setup() @@ -1951,9 +1957,35 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } set_gross_profit(item) { - if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype) && item.valuation_rate) { - var rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); - item.gross_profit = flt(((rate - item.valuation_rate) * item.stock_qty), precision("amount", item)); + if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype)) { + const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule || "valuation_rate"] + const gross_profit_basis = item[gross_profit_basis_field] + + if (gross_profit_basis) { + const rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); + item.gross_profit = flt(((rate - gross_profit_basis) * item.stock_qty), precision("amount", item)); + } + } + } + + gross_profit_percentage(doc, cdt, cdn) { + if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype)) { + const item = locals[cdt][cdn]; + console.log(item.gross_profit_percentage, item.gross_profit_calculation_rule) + if (item.gross_profit_percentage && item.gross_profit_calculation_rule) { + const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] + const rate = item[gross_profit_basis_field] * (1 + (item.gross_profit_percentage / 100)) + frappe.model.set_value(cdt, cdn, "rate", rate); + } + } + } + + gross_profit_calculation_rule(doc, cdt, cdn) { + const item = frappe.get_doc(cdt, cdn); + if (item.gross_profit_percentage) { + this.gross_profit_percentage(doc, cdt, cdn) + } else { + this.set_gross_profit(item); } } diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index a13e9ca68a1..4fa6231404a 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -376,7 +376,7 @@ erpnext.sales_common = { } supplier(doc, cdt, cdn) { - const item = frappe.get_doc(cdt, cdn); + const item = locals[cdt][cdn]; if (item.supplier) { frappe.call({ @@ -401,6 +401,11 @@ erpnext.sales_common = { frappe.model.set_value(cdt, cdn, "cost_price", 0.0); } } + + cost_price(doc, cdt, cdn) { + console.log("cost_price", doc, cdt, cdn) + this.gross_profit_calculation_rule(doc, cdt, cdn); + } }; } } diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 124d5ea4172..b903695d6c4 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -56,6 +56,8 @@ "valuation_rate", "last_purchase_rate", "column_break_45", + "gross_profit_calculation_rule", + "gross_profit_percentage", "gross_profit", "supplier_price_section", "supplier", @@ -145,7 +147,6 @@ "width": "300px" }, { - "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -718,6 +719,7 @@ }, { "collapsible": 1, + "depends_on": "eval:doc.gross_profit_calculation_rule == \"Supplier Cost Price\"", "fieldname": "supplier_price_section", "fieldtype": "Section Break", "label": "Supplier Price" @@ -727,12 +729,24 @@ "fieldtype": "Currency", "label": "Cost Price", "read_only": 1 + }, + { + "default": "Valuation Rate", + "fieldname": "gross_profit_calculation_rule", + "fieldtype": "Select", + "label": "Gross Profit Based On", + "options": "Valuation Rate\nLast Purchase Rate\nSupplier Cost Price" + }, + { + "fieldname": "gross_profit_percentage", + "fieldtype": "Percent", + "label": "Gross Profit Percentage" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:24:24.619832", + "modified": "2023-12-04 12:00:42.254013", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index 19d6b3464f4..e0367a572d1 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -35,6 +35,10 @@ class QuotationItem(Document): discount_amount: DF.Currency discount_percentage: DF.Percent gross_profit: DF.Currency + gross_profit_calculation_rule: DF.Literal[ + "Valuation Rate", "Last Purchase Rate", "Supplier Cost Price" + ] + gross_profit_percentage: DF.Percent has_alternative_item: DF.Check image: DF.Attach | None is_alternative: DF.Check diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index b4893c394d1..6a9212d264d 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -214,7 +214,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-25 14:03:03.966701", + "modified": "2023-12-04 12:03:59.034319", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index ad86e4e1ef5..52d8edfcf62 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -12,6 +12,38 @@ from frappe.utils import cint class SellingSettings(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + allow_against_multiple_purchase_orders: DF.Check + allow_multiple_items: DF.Check + allow_negative_rates_for_items: DF.Check + allow_sales_order_creation_for_expired_quotation: DF.Check + blanket_order_allowance: DF.Float + create_tasks_from_sales_order: DF.Check + cust_master_name: DF.Literal["Customer Name", "Naming Series", "Auto Name"] + customer_group: DF.Link | None + dn_required: DF.Literal["No", "Yes"] + dont_reserve_sales_order_qty_on_sales_return: DF.Check + editable_bundle_item_rates: DF.Check + editable_price_list_rate: DF.Check + enable_discount_accounting: DF.Check + hide_tax_id: DF.Check + maintain_same_rate_action: DF.Literal["Stop", "Warn"] + maintain_same_sales_rate: DF.Check + role_to_override_stop_action: DF.Link | None + sales_update_frequency: DF.Literal["Monthly", "Each Transaction", "Daily"] + selling_price_list: DF.Link | None + so_required: DF.Literal["No", "Yes"] + territory: DF.Link | None + validate_selling_price: DF.Check + # end: auto-generated types + def on_update(self): self.toggle_hide_tax_id() self.toggle_editable_rate_for_bundle_items() diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index da9f800177c..f20176fe229 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -76,10 +76,8 @@ "deferred_accounting_section", "enable_deferred_expense", "no_of_months_exp", - "column_break_9s9o", "enable_deferred_revenue", "no_of_months", - "section_break_avcp", "item_defaults", "purchasing_tab", "purchase_uom", @@ -105,6 +103,7 @@ "is_sales_item", "column_break3", "max_discount", + "gross_profit_calculation_rule", "customer_details", "customer_items", "item_tax_section_break", @@ -523,7 +522,7 @@ "fieldtype": "Table", "hidden": 1, "label": "Variant Attributes", - "mandatory_depends_on": "eval:(doc.has_variants || doc.variant_of) && doc.variant_based_on==='Item Attribute'", + "mandatory_depends_on": "has_variants", "no_copy": 1, "options": "Item Variant Attribute" }, @@ -986,6 +985,14 @@ "fieldtype": "Link", "label": "Booked Item", "options": "Item" + }, + { + "default": "Valuation Rate", + "fieldname": "gross_profit_calculation_rule", + "fieldtype": "Select", + "label": "Gross Profit Calculation in Quotations and Sales Orders Based On", + "options": "Valuation Rate\nLast Purchase Rate\nSupplier Cost Price", + "reqd": 1 } ], "icon": "fa fa-tag", @@ -994,7 +1001,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-10-23 13:46:32.688051", + "modified": "2023-12-04 15:40:01.820792", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index c6bb6b60a1f..2102b3e545e 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -32,6 +32,12 @@ purchase_doctypes = [ "Purchase Invoice", ] +GROSS_PROFIT_CALCULATION_RULES = { + "Valuation Rate": "valuation_rate", + "Last Purchase Rate": "last_purchase_rate", + "Supplier Cost Price": "cost_price", +} + @frappe.whitelist() def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): @@ -370,7 +376,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): else 0, "is_fixed_asset": item.is_fixed_asset, "last_purchase_rate": item.last_purchase_rate - if args.get("doctype") in ["Purchase Order"] + if args.get("doctype") in ["Purchase Order", "Quotation"] else 0, "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), @@ -380,6 +386,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "grant_commission": item.get("grant_commission"), "is_down_payment_item": item.get("is_down_payment_item"), "down_payment_rate": item.get("down_payment_percentage"), + "gross_profit_calculation_rule": item.get("gross_profit_calculation_rule"), } ) @@ -1361,8 +1368,12 @@ def get_valuation_rate(item_code, company, warehouse=None): def get_gross_profit(out): - if out.valuation_rate: - out.update({"gross_profit": ((out.base_rate - out.valuation_rate) * out.stock_qty)}) + gross_profit_calculation_rule = out.get("gross_profit_calculation_rule") + gross_profit_calculation_field = GROSS_PROFIT_CALCULATION_RULES.get(gross_profit_calculation_rule) + if out.get(gross_profit_calculation_field): + out.update( + {"gross_profit": ((out.base_rate - out.get(gross_profit_calculation_field)) * out.stock_qty)} + ) return out diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 5499c3cffe8..4312432291f 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -13601,4 +13601,5 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s 🗃️ CRM Data & Transactions,🗃️ Données CRM & Transactions, 🗃️ Manufacturing Data & Transactions,🗃️ Données de production & Transactions, 🗃️ Projects Data,🗃️ Données des projets, -Leave empty for default format.
For available formats please read the Python documentation,Laissez vide pour le format par défaut.
Pour connaître les formats disponibles veuillez lire la documentation Python, \ No newline at end of file +Leave empty for default format.
For available formats please read the Python documentation,Laissez vide pour le format par défaut.
Pour connaître les formats disponibles veuillez lire la documentation Python, +Gross Profit,Marge brute,Quotation Item \ No newline at end of file -- GitLab From db7c0ab4a8d9de08d9e290e20b8ad3ab9acd159a Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 16:10:40 +0100 Subject: [PATCH 036/155] fix: Handle documents without gross profit calculation rule --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2102b3e545e..94db0b40a63 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1368,7 +1368,7 @@ def get_valuation_rate(item_code, company, warehouse=None): def get_gross_profit(out): - gross_profit_calculation_rule = out.get("gross_profit_calculation_rule") + gross_profit_calculation_rule = out.get("gross_profit_calculation_rule") or "Valuation Rate" gross_profit_calculation_field = GROSS_PROFIT_CALCULATION_RULES.get(gross_profit_calculation_rule) if out.get(gross_profit_calculation_field): out.update( -- GitLab From 157e0631f6ca064fbad48e39832fc98eddb6784c Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 16:21:44 +0100 Subject: [PATCH 037/155] chore: add french translations --- .../sepa_direct_debit/sepa_direct_debit.py | 2 +- erpnext/translations/fr.csv | 40 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.py b/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.py index 4eb3217f159..e2f246f897f 100644 --- a/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.py +++ b/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.py @@ -54,7 +54,7 @@ class SepaDirectDebit(Document): if not (self.from_date and self.to_date) and not self.payment_order: frappe.throw( _( - "If your SEPA file is not generated from a payement order, From Date and To Date are mandatory" + "If your SEPA file is not generated from a payment order, From Date and To Date are mandatory" ) ) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 4312432291f..d6331d294fb 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -89,6 +89,7 @@ (K) Valuation = Value (D) ÷ Qty (A),(K) Valorisation = Valeur (D) ÷ Qté (A), (Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.,(N° de série: {0}) ne peut pas être consommé car il est réservé pour la commande client {1}, (including),(compris), +) and this invoice (,) et cette facture (, ) for {0},) pour {0}, * Will be calculated in the transaction.,* Sera calculé lors de la transaction., **Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.,**Exercice** représente un Exercice Financier. Toutes les écritures comptables et autres transactions majeures sont suivis en **Exercice**., @@ -1054,6 +1055,7 @@ Additional information regarding the customer.,Informations supplémentaires con Address & Contact,Adresse & Contact, Address & Contacts,Adresse & Contacts, Address Desc,Adresse Desc, +Address Display,Affichage de l'adresse, Address Form,Formulaire d'adresse, Address HTML,Adresse HTML, Address Line 1,Adresse, @@ -1376,6 +1378,7 @@ An error has been appeared while reposting item valuation via {0},Une erreur est An error has occurred during {0}. Check {1} for more details,Une erreur est survenue durant {0}. Veuillez vérifier les {0} pour plus de détails,Error Log An error occured during the payment flow. Please create the payment and the invoice manually.,Une erreur a eu lieu pendant le flux de paiement. Veuillez créer le paiement et la facture manuellement, An error occured for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :,Une erreur s'est produite pour certains articles lors de la création de demandes de matériel basées sur le niveau de réapprovisionnement. Veuillez rectifier ces problèmes :, +An error occured while importing your FEC.,Une erreur s'est produite lors de l'import de votre FEC., An error occured while trying to fetch your payment plan on Stripe.
Please check your error logs.,Une erreur a empếché la récupération de votre plan de paiement sur Stripe.
Veuillez regarder dans vos logs d'erreur., An error occured,Erreur lors de l'initialisation du paiement, An error occurred during the update process,Une erreur s'est produite lors du processus de mise à jour, @@ -2674,6 +2677,7 @@ Company Address is mandatory to fetch company GSTIN details.,L'adresse de la soc Company Address,Adresse de la Société, Company Bank Account,Compte bancaire de la société, Company Billing Address,Adresse de facturation de la société, +Company Creation,Création de la société, Company Description for website homepage,Description de la Société pour la page d'accueil du site web, Company Description,Description de l'entreprise, Company Details,Détails de la société, @@ -2686,6 +2690,7 @@ Company Name as per Imported Tally Data,Nom de la société dans les données im Company Name cannot be Company,Nom de la Société ne peut pas être Company, Company Name,Nom de la société, Company Not Linked,La société n'est pas référencée, +Company Search,Recherche de sociétés, Company Settings,des paramètres de l'entreprise, Company Shipping Address,Adresse d'expédition, Company Tagline for website homepage,Slogan de la Société pour la page d'accueil du site web, @@ -2919,6 +2924,7 @@ Cost Centers for Budgeting and Analysis,Centres de coûts pour la budgétisation Cost Centers,Centres de Coûts, Cost Configuration,Configuration des coûts, Cost Per Unit,Coût par unité, +Cost Price,Coût de revient, Cost Updated,Coût Mise à Jour, Cost as on,Coût à partir de, Cost center is mandatory for loans having rate of interest greater than 0,Le centre de coût est obligatoire pour les prêts dont le taux d'intérêt est supérieur à 0., @@ -4745,6 +4751,8 @@ From and To dates are required,Les dates de début et de fin sont requises, From and To dates required,Les date Du et Au sont requises, From date can not be greater than than To date,La date de début ne peut pas être supérieure à la date de fin, From date cannot be greater than To date,La date de début ne peut pas être après la date de fin, +From date must be before To date,La date de début doit être avant la date de fin, +From date must be within fiscal year {0},La date de début doit être dans l'exercice fiscal {0}, From duration (Mins),Durée minimum (Mins), From employee is required while receiving Asset {0} to a target location,L'employé source est nécessaire pour recevoir l'actif {0} dans le lieu cible, From value must be less than to value in row {0},De la Valeur doit être inférieure à la valeur de la ligne {0}, @@ -5012,7 +5020,11 @@ Gross Pay (Company Currency),Paie brute (Devise société), Gross Pay - Total Deduction - Loan Repayment,Salaire Brut - Déductions Totales - Remboursement de Prêt, Gross Profit %,Bénéfice Brut %, Gross Profit / Loss,Bénéfice/Perte Brut, +Gross Profit Based On,Marge brute basée sur, +Gross Profit Calculation in Quotations and Sales Orders Based On,Calcul de la marge brute dans les devis et les commandes basée sur, Gross Profit Percent,Pourcentage de bénéfice brut, +Gross Profit Percentage,Pourcentage de marge brute, +Gross Profit,Marge brute,Quotation Item Gross Profit,Bénéfice Brut, Gross Purchase Amount is mandatory,Montant d'Achat Brut est obligatoire, Gross Purchase Amount should be equal to purchase amount of one single Asset.,Le montant d'achat brut doit être égal au montant d'achat d'un seul actif., @@ -5089,6 +5101,7 @@ Has Variants,A des variantes, Hashed Data,Hash des données, Have Default Naming Series for Batch ID?,Vous avez une série de noms par défaut pour l'ID du lot ?, Head of Marketing and Sales,Responsable du Marketing et des Ventes, +Headcount,Nombre d'employés, Header Image,Image d'en-tête, Heads (or groups) against which Accounting Entries are made and balances are maintained.,Titres (ou groupes) sur lequel les entrées comptables sont faites et les soldes sont maintenus., Health Care,Soins de Santé, @@ -5291,6 +5304,7 @@ If this is undesirable please cancel the corresponding Payment Entry.,"Si ce n'e ","Si vous {0} {1} d'article {2}, le schéma {3} sera appliqué sur l'article.", "If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.","Si vous {0} {1} de l'article {2}, la règle {3} sera appliquée sur l'article", +"If your SEPA file is not generated from a payment order, From Date and To Date are mandatory","Si votre fichier SEPA n'est pas généré via un ordre de paiement, les dates de début et de fin sont obligatoies.", Ignore Account Closing Balance,Ignorer le solde comptable de clôture, Ignore Available Stock,Ignorer le stock disponible, Ignore Closing Balance,Ignore le solde de clôture, @@ -5333,7 +5347,6 @@ Import Supplier Invoice,Importer des factures d'achat, Import To Date,Importer jusqu'au, Import a FEC,Import un FEC, Import finished. Please check the imported documents.,Import terminé. Veuillez vérifier les documents importés., -An error occured while importing your FEC.,Une erreur s'est produite lors de l'import de votre FEC., Import in Bulk,Importer en Masse, Import of goods,Import de biens, Import of services,Import de services, @@ -5585,6 +5598,7 @@ Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN H Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.,GSTIN invalide! Le numéro fourni ne correspond pas au format d'un n° GSTIN, Invalid GSTIN,GSTIN invalide, Invalid Gross Purchase Amount,Montant d'achat brut invalide, +Invalid Group By,Fonction de groupement invalide, Invalid Invoice,Facture non valide, Invalid Item Defaults,Défauts d'éléments non valides, Invalid Item,Article invalide, @@ -5602,6 +5616,7 @@ Invalid Process Loss Configuration,Configuration des pertes du processus invalid Invalid Purchase Invoice,Facture d'achat invalide, Invalid Purchase Receipt,Reçu d'achat invalide, Invalid Qty,Qté non valide, +Invalid Quantity,Quantité invalide, Invalid Schedule,Programme non valide, Invalid Scheduled Time,Heure planifiée invalide, Invalid Selling Price,Prix de vente invalide, @@ -6035,6 +6050,7 @@ Journal Entry {0} does not have account {1} or already matched against other vou Journal Entry,Écriture de journal, Journal Name,Nom du journal, Journal code,Code journal, +Journal entries for one supplier,Ecritures de journal pour un fournisseur, Journal name,Nom du journal, Journal type,Type de journal, JournalCode,JournalCode, @@ -6097,6 +6113,7 @@ Last Purchase Rate,Dernier Prix d'Achat, Last Stock Transaction for item {0} under warehouse {1} was on {2}.,"La dernière transaction de stock pour l'article {0}, dans l'entrepôt {1}, a eu lieu le {2}", Last Stock Transaction for item {0} was on {1}.,La dernière transation de stock pour l'article {0} était le {1}, Last Sync Datetime,Dernière date de synchronisation, +Last Update via Pappers,Dernière mise à jour via Pappers, Last Woocommerce Sync,Dernière synchronisation WooCommerce, Last carbon check date cannot be a future date,La date de dernière vérification carbone ne peut être dans le futur, Last day of the month,Dernier jour du mois, @@ -6150,6 +6167,7 @@ Leave Encashed?,Congés encaissés ?, "Leave blank for home.
This is relative to site URL, for example ""about"" will redirect to ""https://yoursitename.com/about""","Laisser vide pour ""home"".
Il s'agit d'une URL relative, par exemple ""about"" sera redirigé ver ""https://votresite.com/about""", Leave blank if the Supplier is blocked indefinitely,Laisser vide si le fournisseur est bloqué indéfiniment, Leave blank to use the standard Delivery Note format,Laissez vide pour utiliser le format de bon de livraison standard, +"Leave empty for default format.
For available formats please read the Python documentation","Laissez vide pour le format par défaut.
Pour connaître les formats disponibles veuillez lire la documentation Python", Leave empty if credits have a no validity end date,Laisser vide si les crédits n'ont pas de date de fin de validité, Leave empty if no expiration,Laisser vide si aucune durée d'expiration, Leave the field empty to make purchase orders for all suppliers,Laissez le champ vide pour passer des commandes pour tous les fournisseurs, @@ -6161,6 +6179,7 @@ Left Index,Index gauche, Left,A quitté l'entreprise,Employee Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.,Entité Juridique / Filiale avec un Plan de Comptes différent appartenant à l'Organisation., Legal Expenses,Frais Juridiques, +Legal Form,Forme juridique, Legal,Juridique, Legend,Légende, Length (cm),Longueur (cm), @@ -6536,6 +6555,7 @@ Manufacturing module is set up !,Le module de production est configuré, Manufacturing,Production, Map Custom Field to DocType,Mapper le champ personnalisé au type de document, Map Shopify Taxes / Shipping Charges to Dokos Account,Correspondance entre les taxes / frais de livraison de Shopify et les comptes dans Dokos, +Mapping Purchase Receipt ...,Création des reçus d'achat ..., Mapping Subcontracting Order ...,Mapping de la commande de sous-traitance ..., Mapping {0} ...,Création d'une correspondance avec {0}..., Mapping,Mapping, @@ -7776,6 +7796,7 @@ Payment Unlink Error,Erreur de déliaison de paiement, Payment against {0} {1} cannot be greater than Outstanding Amount {2},Paiement pour {0} {1} ne peut pas être supérieur à Encours {2}, Payment amount cannot be less than or equal to 0,Le montant du paiement ne peut pas être inférieur ou égal à 0, Payment cannot be processed immediately for this payment request.,Le paiement ne peut pas être récolté automatiquement pour cette demande de paiement., +Payment entries in batch,Ecritures de paiement en lot, Payment entry cannot be created from payment request for doctype {0},Une écriture de paiement ne peut pas être créée depuis une demande de paiement pour le type de document {0}, Payment entry created,Ecriture de paiement créée, Payment entry with reference {0} not found,Ecriture de paiement avec la référence {0} introuvable, @@ -7799,6 +7820,7 @@ Payment succeeded. Refresh the page to pay again.,Paiement réussi. Veuillez raf Payment successfully initialized,Paiement initialisé avec succès, Payment term {0} not used in {1},Les conditions de paiement {0} ne sont pas utilisées dans {1}., Payment,Paiement, +Payments created,Paiements créés, Payments,Paiements, Payroll Cost Centers,Centres de coûts de la paie, Payroll Entry,Ecriture de paie, @@ -7979,8 +8001,10 @@ Please add Root Account for - {0},Veuillez ajouter le compte racine pour - {0}, Please add a Swift Number in bank account {0} for customer {1},Veuillez ajouter un BIC dans le compte bancaire {0} pour le client {1}, Please add a Temporary Opening account in Chart of Accounts,Veuillez ajouter un compte d'ouverture temporaire dans le plan comptable, Please add a bank account in mandate {0} for customer {1},Veuillez ajouter un compte bancaire dans le mandat {0} pour le client {1}, +Please add a bank account to supplier {0},Veuillez ajouter un compte bancaire au fournisseur {0}, Please add a price for the following billing intervals:,Veuillez ajouter un prix pour les intervalles de facturation suivants:, Please add an IBAN in bank account {0} for customer {1},Veuillez ajouter un IBAN dans le compte bancaire {0} pour le client {1}, +Please add an IBAN to bank account {0},Veuillez associer un IBAN au compte bancaire {0}, Please add at least one company bank account. 'Is company account' must be checked.,Veuillez ajouter au moins un compte bancaire. 'Est le compte de la société' doit être coché., Please add at least one payment gateway,Veuillez ajouter au moins une passerelle de paiement, Please add atleast one Serial No / Batch No,Veuillez ajouter au moins un numéro de série / lot, @@ -8032,6 +8056,7 @@ Please create Landed Cost Vouchers against Invoices that have 'Update Stock' ena Please create a new Accounting Dimension if required.,Veuillez créer une nouvelle dimension comptable si nécessaire., Please create a new invoice for this event with external reference {0},Veuillez créer une nouvelle facture pour cet événement avec la référence externe {0}, Please create a payment gateway account for currency {0},Veuillez créer un compte de passerelle de paiement pour la devise {0}, +Please create a {0} document to configure the generation of your XML file,Veuillez créer un document {0} pour configurer la génération de votre fichier XML, Please create adjustment Journal Entry for amount {0} ,Veuillez créer une écriture de journal pour le montant {0}, Please create an adjustment Journal Entry for amount {0} on {1},Veuillez créer une écriture de journal d'ajustement pour le montant {0} à la date du {1}, Please create at least one mode of payment for this bank account first,Veuillez créer au moins un mode de paiement pour ce compte bancaire, @@ -8711,6 +8736,7 @@ Publish in Website,Publier sur le site Web, Publish on website,Publier sur le site web, Publishable Key,Clé publiable, Published Date,Date de publication, +Published In Website,Publié sur le site web, Published in Website,Publié sur le site Web, Publishing Item ...,Élément de publication ..., Publishing,Édition, @@ -8771,6 +8797,7 @@ Purchase Receipt Required for item {},Reçu d'achat requis pour l'article {}, Purchase Receipt Required,Reçu d'achat requis, Purchase Receipt Trends,Evolution des reçus d'achat, Purchase Receipt doesn't have any Item for which Retain Sample is enabled.,Le reçu d'achat n'a aucun article pour lequel 'Garder un échantillon' est activé., +Purchase Receipt {0} created.,Reçu d'achats créé, Purchase Receipt {0} is not submitted,Le Reçu d'Achat {0} n'est pas soumis, Purchase Receipt,Reçu d'Achat, Purchase Receipts,Reçus d'Achats, @@ -8945,6 +8972,7 @@ Quote Status,Statut du Devis, Quoted Amount,Montant du devis, Quoted Item Comparison,Comparaison de articles des devis fournisseurs, Quotes to Leads or Customers.,Devis de Prospects ou Clients., +RCS Information,Informations du RCS, RFQs are not allowed for {0} due to a scorecard standing of {1},Les Appels d'Offres ne sont pas autorisés pour {0} en raison d'une note de {1} sur la fiche d'évaluation, RRULE,RRULE, RTT,RTT, @@ -11263,6 +11291,7 @@ Supplier Address,Adresse du Fournisseur, Supplier Addresses And Contacts,Adresses et contacts des fournisseurs, Supplier Analytics,Analyse des fournisseurs, Supplier Contact,Contact du fournisseur, +Supplier Cost Price,Coût de revient du fournisseur, Supplier Delivery Note,Bon de livraison du Fournisseur, Supplier Detail,Détails du Fournisseur, Supplier Details,Détails du Fournisseur, @@ -11287,6 +11316,7 @@ Supplier Naming By,Nommage du fournisseur selon, Supplier Part No,N° de Pièce du Fournisseur, Supplier Part Number,Numéro de Pièce du Fournisseur, Supplier Portal Users,Utilisateurs du portail fournisseur, +Supplier Price,Prix du fournisseur, Supplier Primary Address,Adresse principale du fournisseur, Supplier Primary Contact,Contact principal du fournisseur, Supplier Quotation Comparison,Comparaison de devis fournisseur, @@ -12418,6 +12448,7 @@ Under Review,En cours de révision, Under Warranty,Sous Garantie, Undue Booked Interest,Intérêt comptabilisé non dû, Unfulfilled,Non-rempli, +Unit Cost Price,Coût de revient unitaire, Unit Of Measure,Unité de mesure, Unit of Measure (UOM),Unité de mesure, Unit of Measure Field,Champ Unité de mesure, @@ -12517,6 +12548,7 @@ Update Series,Mettre à Jour les Séries, Update Stock Opening Balance,Mise à jour du solde d'ouverture des stocks, Update Stock,Mettre à jour le stock, Update Taxes for Items,Mettre à jour les taxes pour les articles, +Update Total Purchase Cost,Mettre à jour le coût total des achats, Update Woocommerce products from Dokos items,Mettre à jour les produits WooCommerce à partir des articles Dokos, Update bank payment dates with journals.,Mettre à jour les dates de paiement bancaires avec les journaux., Update data every (Days),Mettre à jour tous les (Jours), @@ -12727,6 +12759,7 @@ View General Ledger,Afficher le grand livre, View Journals,Voir les journaux, View Leads,Voir Prospects, View Ledger,Grand livre, +View Ledgers,Voir les livres comptables, View More,Voir plus, View Now,Voir Maintenant, View Orders,Afficher les commandes, @@ -13116,6 +13149,7 @@ You need to enable Shopping Cart,Vous devez activer le Panier, YouTube Interactions,Interactions Youtube, Your Company set in Dokos,Votre société dans Dokos, Your FEC is being prepared,Votre FEC est en cours de préparation, +Your FEC is ready - {0},Votre FEC est prêt - {0}, Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5},Votre session d'entretien a été reprogrammée de {0} {1} - {2} à {3} {4} - {5}, Your Name (required),Votre nom *, Your Organization,L'activité de la société, @@ -13594,6 +13628,7 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s {},{},} {},{}, ← Back to upload files,← Retour au téléchargement de fichiers, +🏃 Your Shortcuts,🏃 Vos Raccourcis, 🏃Your Shortcuts,🏃Vos raccourcis, 📊 Reports ,📊 Rapports, 📖 General Ledger,📖 Grand livre, @@ -13601,5 +13636,4 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s 🗃️ CRM Data & Transactions,🗃️ Données CRM & Transactions, 🗃️ Manufacturing Data & Transactions,🗃️ Données de production & Transactions, 🗃️ Projects Data,🗃️ Données des projets, -Leave empty for default format.
For available formats please read the Python documentation,Laissez vide pour le format par défaut.
Pour connaître les formats disponibles veuillez lire la documentation Python, -Gross Profit,Marge brute,Quotation Item \ No newline at end of file +🗃️ Reports & Masters,🗃️ Rapports & Données de base, -- GitLab From 766145723349268ef3e80ca529f44d7431e1a02c Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 16:55:37 +0100 Subject: [PATCH 038/155] fix: more accurate labels --- erpnext/selling/doctype/quotation_item/quotation_item.json | 6 +++--- erpnext/translations/fr.csv | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index b903695d6c4..41e473704a7 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -714,7 +714,7 @@ { "fieldname": "unit_cost_price", "fieldtype": "Currency", - "label": "Unit Cost Price", + "label": "Supplier Unit Price", "read_only": 1 }, { @@ -727,7 +727,7 @@ { "fieldname": "cost_price", "fieldtype": "Currency", - "label": "Cost Price", + "label": "Supplier Price", "read_only": 1 }, { @@ -746,7 +746,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-12-04 12:00:42.254013", + "modified": "2023-12-04 16:54:40.291026", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index d6331d294fb..e769fcf58c0 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -11316,6 +11316,7 @@ Supplier Naming By,Nommage du fournisseur selon, Supplier Part No,N° de Pièce du Fournisseur, Supplier Part Number,Numéro de Pièce du Fournisseur, Supplier Portal Users,Utilisateurs du portail fournisseur, +Supplier Unit Price,Prix unitaire du fournisseur, Supplier Price,Prix du fournisseur, Supplier Primary Address,Adresse principale du fournisseur, Supplier Primary Contact,Contact principal du fournisseur, -- GitLab From db21e63d2671fe4ba399ad8bcbd48db0f328f8c4 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 4 Dec 2023 17:01:06 +0100 Subject: [PATCH 039/155] fix: translation --- erpnext/translations/fr.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index e769fcf58c0..7fc2063dc5c 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -11291,7 +11291,7 @@ Supplier Address,Adresse du Fournisseur, Supplier Addresses And Contacts,Adresses et contacts des fournisseurs, Supplier Analytics,Analyse des fournisseurs, Supplier Contact,Contact du fournisseur, -Supplier Cost Price,Coût de revient du fournisseur, +Supplier Cost Price,Prix du fournisseur, Supplier Delivery Note,Bon de livraison du Fournisseur, Supplier Detail,Détails du Fournisseur, Supplier Details,Détails du Fournisseur, -- GitLab From 8194f0c049382ee054f9fbbc3466586f5c32f36f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 4 Dec 2023 19:19:09 +0530 Subject: [PATCH 040/155] fix: get dynamic link with parenttype contact --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d22cc5548a6..e33c88c8720 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -39,7 +39,7 @@ class Lead(SellingController, CRMNote): if self.source == "Existing Customer" and self.customer: contact = frappe.db.get_value( "Dynamic Link", - {"link_doctype": "Customer", "link_name": self.customer}, + {"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer}, "parent", ) if contact: -- GitLab From aaaa0cbe8eac99e28cce39c0e4a3561d187b1e23 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 4 Dec 2023 16:47:28 +0530 Subject: [PATCH 041/155] fix: limit end date to current date --- erpnext/accounts/report/financial_statements.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 913169358f5..b42185e18fb 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -7,7 +7,17 @@ import re import frappe from frappe import _ -from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate +from frappe.utils import ( + add_days, + add_months, + cint, + cstr, + flt, + formatdate, + get_first_day, + getdate, + today, +) from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -46,6 +56,8 @@ def get_period_list( year_start_date = getdate(period_start_date) year_end_date = getdate(period_end_date) + year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date + months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity] period_list = [] -- GitLab From 118e617ceaf8b16f98f8b9386a297e7fce8093d6 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 5 Dec 2023 07:34:21 +0100 Subject: [PATCH 042/155] fix: remove translation from OCR app --- erpnext/translations/fr.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 7fc2063dc5c..e8dbbc25535 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -89,7 +89,6 @@ (K) Valuation = Value (D) ÷ Qty (A),(K) Valorisation = Valeur (D) ÷ Qté (A), (Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.,(N° de série: {0}) ne peut pas être consommé car il est réservé pour la commande client {1}, (including),(compris), -) and this invoice (,) et cette facture (, ) for {0},) pour {0}, * Will be calculated in the transaction.,* Sera calculé lors de la transaction., **Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.,**Exercice** représente un Exercice Financier. Toutes les écritures comptables et autres transactions majeures sont suivis en **Exercice**., -- GitLab From fbaaaacf348dd56d9b1e9468ddcc266ecc0291b3 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 4 Dec 2023 15:39:25 +0530 Subject: [PATCH 043/155] feat: `Company` filter in `Stock Ledger Variance` report --- .../stock_ledger_variance.js | 16 ++++++++++++---- .../stock_ledger_variance.py | 7 ++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js index b1e4a74571e..bf3a397feef 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.js @@ -13,10 +13,18 @@ const DIFFERENCE_FIELD_NAMES = [ frappe.query_reports["Stock Ledger Variance"] = { "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, { "fieldname": "item_code", "fieldtype": "Link", - "label": "Item", + "label": __("Item"), "options": "Item", get_query: function() { return { @@ -27,7 +35,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "warehouse", "fieldtype": "Link", - "label": "Warehouse", + "label": __("Warehouse"), "options": "Warehouse", get_query: function() { return { @@ -38,7 +46,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "difference_in", "fieldtype": "Select", - "label": "Difference In", + "label": __("Difference In"), "options": [ "", "Qty", @@ -49,7 +57,7 @@ frappe.query_reports["Stock Ledger Variance"] = { { "fieldname": "include_disabled", "fieldtype": "Check", - "label": "Include Disabled", + "label": __("Include Disabled"), } ], diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index 732f108ac41..acbbe9039ac 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -230,7 +230,12 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: bin.item_code, bin.warehouse, ) - .where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0)) + .where( + (item.is_stock_item == 1) + & (item.has_serial_no == 0) + & (warehouse.is_group == 0) + & (warehouse.company == filters.company) + ) ) if filters.item_code: -- GitLab From fc1f8b7b186b1f8426ed794737b11f8cfebd7be4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 5 Dec 2023 10:14:05 +0530 Subject: [PATCH 044/155] refactor: ignore unreconcile doc on PI cancel/delete --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 1d745450f24..35ceca6dbda 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. super.onload(); // Ignore linked advances - this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Archived Document"]; + this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; if(!this.frm.doc.__islocal) { // show credit_to in print format diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f217f758be1..7af8814ec38 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1490,6 +1490,8 @@ class PurchaseInvoice(BuyingController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", + "Unreconcile Payment", + "Unreconcile Payment Entries", "Payment Ledger Entry", "Archived Document", "Tax Withheld Vouchers", -- GitLab From b2e162599e3192a6d611a0dafa80cb892dbbf820 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 5 Dec 2023 11:33:16 +0530 Subject: [PATCH 045/155] refactor: ingore on JE cancel as well --- .../doctype/journal_entry/journal_entry.js | 31 +------------------ .../doctype/journal_entry/journal_entry.py | 4 +-- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index c4facb2bbf8..fce32f1527b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -8,36 +8,7 @@ frappe.provide("erpnext.journal_entry"); frappe.ui.form.on("Journal Entry", { setup: function(frm) { frm.add_fetch("bank_account", "account", "account"); - frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"]; - - if (frm.doc.company && !frm.doc.journal_entry) { - erpnext.set_accounting_journal(frm); - } - - frm.set_query("accounting_journal", () => { - return { - filters: { - company: frm.doc.company, - }, - }; - }); - - frm.set_query("accounting_journal", "accounts", () => { - return { - filters: { - company: frm.doc.company, - }, - }; - }); - }, - - onload: function(frm) { - // For compatibility with older versions with debit_in_account_currency/credit_in_account_currency - frm.doc.accounts.forEach(account => { - if (!(account.debit || account.credit)) { - erpnext.journal_entry.set_exchange_rate(frm, account.doctype, account.name); - } - }) + frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d6da9e15a5b..b5a794b2cb4 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -175,8 +175,8 @@ class JournalEntry(AccountsController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", - "Sepa Direct Debit Details", - "Archived Document", + "Unreconcile Payment", + "Unreconcile Payment Entries", ) self.make_gl_entries(1) self.update_advance_paid() -- GitLab From e67faa03248fbf5d47bac36afaf82809f2dc5a82 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 5 Dec 2023 13:34:54 +0530 Subject: [PATCH 046/155] fix: incorrect material request quantity in Production Plan (#38566) --- .../production_plan/production_plan.py | 19 ++++--- .../production_plan/test_production_plan.py | 56 ++++++++++++++++++- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 6efb7629050..1b056c2dae8 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1522,19 +1522,23 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): ) locations = get_available_item_locations( - item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True + item.get("item_code"), + warehouses, + item.get("quantity") * item.get("conversion_factor"), + company, + ignore_validation=True, ) required_qty = item.get("quantity") + if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"): + # Convert qty to stock UOM + required_qty = required_qty * item.get("conversion_factor") + # get available material by transferring to production warehouse for d in locations: if required_qty <= 0: return - conversion_factor = 1.0 - if purchase_uom != stock_uom and purchase_uom == item["uom"]: - conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) - new_dict = copy.deepcopy(item) quantity = required_qty if d.get("qty") > required_qty else d.get("qty") @@ -1544,10 +1548,11 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): "material_request_type": "Material Transfer", "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "from_warehouse": d.get("warehouse"), + "conversion_factor": 1.0, } ) - required_qty -= quantity / conversion_factor + required_qty -= quantity new_mr_items.append(new_dict) # raise purchase request for remaining qty @@ -1559,7 +1564,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): required_qty = ceil(required_qty) - item["quantity"] = required_qty + item["quantity"] = required_qty / item.get("conversion_factor") new_mr_items.append(item) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index dd32c343588..cc9d9a0311e 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1283,12 +1283,14 @@ class TestProductionPlan(FrappeTestCase): for row in items: row = frappe._dict(row) if row.material_request_type == "Material Transfer": + self.assertTrue(row.uom == row.stock_uom) self.assertTrue(row.from_warehouse in [wh1, wh2]) self.assertEqual(row.quantity, 2) if row.material_request_type == "Purchase": + self.assertTrue(row.uom != row.stock_uom) self.assertTrue(row.warehouse == mrp_warhouse) - self.assertEqual(row.quantity, 12) + self.assertEqual(row.quantity, 12.0) def test_mr_qty_for_same_rm_with_different_sub_assemblies(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom @@ -1404,6 +1406,58 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_material_request_qty_purchase_and_material_transfer(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + bom_item = make_item( + properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"} + ).name + + store_warehouse = create_warehouse("Store Warehouse", company="_Test Company") + rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company") + + make_stock_entry( + item_code=bom_item, + qty=60, + target=store_warehouse, + rate=99, + ) + + if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}): + doc = frappe.get_doc("Item", bom_item) + doc.append("uoms", {"uom": "Nos", "conversion_factor": 10}) + doc.save() + + make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1 + ) + + pln.for_warehouse = rm_warehouse + items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": store_warehouse}] + ) + + for row in items: + self.assertEqual(row.get("quantity"), 10.0) + self.assertEqual(row.get("material_request_type"), "Material Transfer") + self.assertEqual(row.get("uom"), "_Test UOM 1") + self.assertEqual(row.get("from_warehouse"), store_warehouse) + self.assertEqual(row.get("conversion_factor"), 1.0) + + items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}] + ) + + for row in items: + self.assertEqual(row.get("quantity"), 1.0) + self.assertEqual(row.get("material_request_type"), "Purchase") + self.assertEqual(row.get("uom"), "Nos") + self.assertEqual(row.get("conversion_factor"), 10.0) + def create_production_plan(**args): """ -- GitLab From da81c816364b04d6ee87ab6b0dec3e2fed424a91 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 5 Dec 2023 14:01:04 +0530 Subject: [PATCH 047/155] fix: sql error while filtering on finance book in GL --- erpnext/accounts/report/general_ledger/general_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 4a8185f51b6..ee442723abc 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -336,7 +336,8 @@ def get_conditions(filters): if accounting_dimensions: for dimension in accounting_dimensions: - if not dimension.disabled: + # Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section + if not dimension.disabled and dimension.document_type != "Finance Book": if filters.get(dimension.fieldname): if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): filters[dimension.fieldname] = get_dimension_with_children( -- GitLab From db3f08bdcf96852a165965fe22e1eb2c64c7df50 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 5 Dec 2023 17:15:57 +0000 Subject: [PATCH 048/155] fix: merge conflicts --- .../doctype/journal_entry/journal_entry.js | 29 +++++++++++++++++++ .../doctype/journal_entry/journal_entry.py | 2 ++ .../purchase_invoice/purchase_invoice.js | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index fce32f1527b..0d5da4eb0bb 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -9,6 +9,35 @@ frappe.ui.form.on("Journal Entry", { setup: function(frm) { frm.add_fetch("bank_account", "account", "account"); frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; + + if (frm.doc.company && !frm.doc.journal_entry) { + erpnext.set_accounting_journal(frm); + } + + frm.set_query("accounting_journal", () => { + return { + filters: { + company: frm.doc.company, + }, + }; + }); + + frm.set_query("accounting_journal", "accounts", () => { + return { + filters: { + company: frm.doc.company, + }, + }; + }); + }, + + onload: function(frm) { + // For compatibility with older versions with debit_in_account_currency/credit_in_account_currency + frm.doc.accounts.forEach(account => { + if (!(account.debit || account.credit)) { + erpnext.journal_entry.set_exchange_rate(frm, account.doctype, account.name); + } + }) }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index b5a794b2cb4..fa8daed9416 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -175,6 +175,8 @@ class JournalEntry(AccountsController): "Repost Payment Ledger Items", "Repost Accounting Ledger", "Repost Accounting Ledger Items", + "Sepa Direct Debit Details", + "Archived Document", "Unreconcile Payment", "Unreconcile Payment Entries", ) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 35ceca6dbda..e44c380ee11 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. super.onload(); // Ignore linked advances - this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"]; + this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Archived Document", "Unreconcile Payment", "Unreconcile Payment Entries"]; if(!this.frm.doc.__islocal) { // show credit_to in print format -- GitLab From 415caa3d67003b8f8bb05d0a6b18512130cd7478 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 5 Dec 2023 17:51:01 +0530 Subject: [PATCH 049/155] fix: incorrect SLE for `Moving Average` valuation method --- .../stock_ledger_variance.py | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index acbbe9039ac..194f0ca637a 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -194,6 +194,7 @@ def get_columns(): def get_data(filters=None): filters = frappe._dict(filters or {}) item_warehouse_map = get_item_warehouse_combinations(filters) + valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method") data = [] if item_warehouse_map: @@ -206,7 +207,9 @@ def get_data(filters=None): continue for row in report_data: - if has_difference(row, precision, filters.difference_in): + if has_difference( + row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method + ): data.append(add_item_warehouse_details(row, item_warehouse)) break @@ -229,6 +232,7 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: .select( bin.item_code, bin.warehouse, + item.valuation_method, ) .where( (item.is_stock_item == 1) @@ -248,36 +252,38 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict: return query.run(as_dict=1) -def has_difference(row, precision, difference_in): - has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) - has_value_difference = ( - flt(row.diff_value_diff, precision) - or flt(row.fifo_value_diff, precision) - or flt(row.fifo_difference_diff, precision) - ) - has_valuation_difference = flt(row.valuation_diff, precision) or flt( - row.fifo_valuation_diff, precision - ) +def has_difference(row, precision, difference_in, valuation_method): + if valuation_method == "Moving Average": + qty_diff = flt(row.difference_in_qty, precision) + value_diff = flt(row.diff_value_diff, precision) + valuation_diff = flt(row.valuation_diff, precision) + else: + qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) + value_diff = ( + flt(row.diff_value_diff, precision) + or flt(row.fifo_value_diff, precision) + or flt(row.fifo_difference_diff, precision) + ) + valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision) - if difference_in == "Qty" and has_qty_difference: + if difference_in == "Qty" and qty_diff: return True - elif difference_in == "Value" and has_value_difference: + elif difference_in == "Value" and value_diff: return True - elif difference_in == "Valuation" and has_valuation_difference: + elif difference_in == "Valuation" and valuation_diff: return True elif difference_in not in ["Qty", "Value", "Valuation"] and ( - has_qty_difference or has_value_difference or has_valuation_difference + qty_diff or value_diff or valuation_diff ): return True - return False - def add_item_warehouse_details(row, item_warehouse): row.update( { "item_code": item_warehouse.item_code, "warehouse": item_warehouse.warehouse, + "valuation_method": item_warehouse.valuation_method, } ) -- GitLab From 89bbcddd9e5e159ade6b118887de8bb64c956525 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 5 Dec 2023 18:06:14 +0530 Subject: [PATCH 050/155] feat: add `Valuation Method` column in `Stock Ledger Variance` report --- .../stock_ledger_variance.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py index 194f0ca637a..189a90aa471 100644 --- a/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py +++ b/erpnext/stock/report/stock_ledger_variance/stock_ledger_variance.py @@ -55,6 +55,11 @@ def get_columns(): "label": _("Warehouse"), "options": "Warehouse", }, + { + "fieldname": "valuation_method", + "fieldtype": "Data", + "label": _("Valuation Method"), + }, { "fieldname": "voucher_type", "fieldtype": "Link", @@ -210,7 +215,14 @@ def get_data(filters=None): if has_difference( row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method ): - data.append(add_item_warehouse_details(row, item_warehouse)) + row.update( + { + "item_code": item_warehouse.item_code, + "warehouse": item_warehouse.warehouse, + "valuation_method": item_warehouse.valuation_method or valuation_method, + } + ) + data.append(row) break return data @@ -276,15 +288,3 @@ def has_difference(row, precision, difference_in, valuation_method): qty_diff or value_diff or valuation_diff ): return True - - -def add_item_warehouse_details(row, item_warehouse): - row.update( - { - "item_code": item_warehouse.item_code, - "warehouse": item_warehouse.warehouse, - "valuation_method": item_warehouse.valuation_method, - } - ) - - return row -- GitLab From 1fd793baac8daf9961bb61d5ae224cb13c0fee83 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 30 Nov 2023 13:28:55 +0530 Subject: [PATCH 051/155] refactor: Simpler log settings setup (#38449) --- erpnext/hooks.py | 4 ++++ erpnext/patches.txt | 1 - erpnext/patches/v14_0/setup_clear_repost_logs.py | 8 -------- erpnext/setup/install.py | 12 ++++++------ 4 files changed, 10 insertions(+), 15 deletions(-) delete mode 100644 erpnext/patches/v14_0/setup_clear_repost_logs.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 606676bb947..1deb2eb8966 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -724,6 +724,10 @@ extend_bootinfo = [ ] +default_log_clearing_doctypes = { + "Repost Item Valuation": 60, +} + jinja = { "filters": [ "frappe.contacts.doctype.address.address.get_condensed_address", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e41759409a8..f4005d83c57 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -311,7 +311,6 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v13_0.drop_unused_sle_index_parts erpnext.patches.v14_0.update_partial_tds_fields erpnext.patches.v14_0.create_incoterms_and_migrate_shipment -erpnext.patches.v14_0.setup_clear_repost_logs erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request erpnext.patches.v14_0.update_entry_type_for_journal_entry erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers diff --git a/erpnext/patches/v14_0/setup_clear_repost_logs.py b/erpnext/patches/v14_0/setup_clear_repost_logs.py deleted file mode 100644 index be9ddcab7ad..00000000000 --- a/erpnext/patches/v14_0/setup_clear_repost_logs.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors -# License: MIT. See LICENSE - -from erpnext.setup.install import setup_log_settings - - -def execute(): - setup_log_settings() diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 96b03aef1e1..a3d79ffb714 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -31,8 +31,8 @@ def after_install(): add_company_to_session_defaults() add_standard_navbar_items() add_app_name() - setup_log_settings() setup_address_and_contact_permissions() + hide_workspaces() update_roles() frappe.db.commit() @@ -215,11 +215,11 @@ def add_app_name(): frappe.db.set_single_value("Website Settings", "app_name", "Dokos") -def setup_log_settings(): - log_settings = frappe.get_single("Log Settings") - log_settings.append("logs_to_clear", {"ref_doctype": "Repost Item Valuation", "days": 60}) - - log_settings.save(ignore_permissions=True) +def hide_workspaces(): + # @dokos + # for ws in ["Integration", "Settings"]: + # frappe.db.set_value("Workspace", ws, "public", 0) + pass def set_venue_settings_defaults(): -- GitLab From f24a6044387074ffbd06455dd2dadb6e2765ca58 Mon Sep 17 00:00:00 2001 From: sandratridz <102575830+sandratridz@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:41:02 +0530 Subject: [PATCH 052/155] fix: only highest eligible coupon applied (#38416) * fix: application of pricing rule when coupon is used --- erpnext/accounts/doctype/pricing_rule/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index d69430692ba..973f4e373f8 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -587,6 +587,8 @@ def apply_pricing_rule_on_transaction(doc): if d.price_or_product_discount == "Price": if d.apply_discount_on: doc.set("apply_discount_on", d.apply_discount_on) + # Variable to track whether the condition has been met + condition_met = False for field in ["additional_discount_percentage", "discount_amount"]: pr_field = "discount_percentage" if field == "additional_discount_percentage" else field @@ -609,6 +611,11 @@ def apply_pricing_rule_on_transaction(doc): if coupon_code_pricing_rule == d.name: # if selected coupon code is linked with pricing rule doc.set(field, d.get(pr_field)) + + # Set the condition_met variable to True and break out of the loop + condition_met = True + break + else: # reset discount if not linked doc.set(field, 0) @@ -617,6 +624,10 @@ def apply_pricing_rule_on_transaction(doc): doc.set(field, 0) doc.calculate_taxes_and_totals() + + # Break out of the main loop if the condition is met + if condition_met: + break elif d.price_or_product_discount == "Product": item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []}) get_product_discount_rule(d, item_details, doc=doc) -- GitLab From 616a29ac35657c9d57e1473ab253c8099e625878 Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Wed, 6 Dec 2023 16:25:00 +0100 Subject: [PATCH 053/155] fix(transaction): Fix fallback for set_gross_profit --- erpnext/public/js/controllers/transaction.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0a470b6641f..f809f19cc2b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1958,8 +1958,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe set_gross_profit(item) { if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype)) { - const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule || "valuation_rate"] - const gross_profit_basis = item[gross_profit_basis_field] + const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] || "valuation_rate"; + const gross_profit_basis = item[gross_profit_basis_field]; if (gross_profit_basis) { const rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); @@ -1971,7 +1971,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe gross_profit_percentage(doc, cdt, cdn) { if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype)) { const item = locals[cdt][cdn]; - console.log(item.gross_profit_percentage, item.gross_profit_calculation_rule) if (item.gross_profit_percentage && item.gross_profit_calculation_rule) { const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] const rate = item[gross_profit_basis_field] * (1 + (item.gross_profit_percentage / 100)) -- GitLab From 2393691831ad708aa5e90db8daf500c95ef46944 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 6 Dec 2023 20:53:55 +0530 Subject: [PATCH 054/155] chore: remove unused cache=True --- erpnext/startup/boot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 7d2d08e5c71..485f3625e91 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -74,3 +74,11 @@ def update_page_info(bootinfo): "Sales Person Tree": {"title": _("Sales Person Tree"), "route": "Tree/Sales Person"}, } ) + + +def bootinfo(bootinfo): + if bootinfo.get("user") and bootinfo["user"].get("name"): + bootinfo["user"]["employee"] = "" + employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name") + if employee: + bootinfo["user"]["employee"] = employee -- GitLab From 1439351d2260c48b8ba0e24d472e3c56c5cc9186 Mon Sep 17 00:00:00 2001 From: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:18:13 +0000 Subject: [PATCH 055/155] feat: add employee number to client user bootinfo (#38477) --- erpnext/hooks.py | 1 + erpnext/startup/boot.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 606676bb947..5395c980296 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -721,6 +721,7 @@ additional_timeline_content = { extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", + "erpnext.startup.boot.bootinfo", ] diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 7d2d08e5c71..5be65afe990 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -74,3 +74,13 @@ def update_page_info(bootinfo): "Sales Person Tree": {"title": _("Sales Person Tree"), "route": "Tree/Sales Person"}, } ) + + +def bootinfo(bootinfo): + if bootinfo.get("user") and bootinfo["user"].get("name"): + bootinfo["user"]["employee"] = "" + employee = frappe.db.get_value( + "Employee", {"user_id": bootinfo["user"]["name"]}, "name", cache=True + ) + if employee: + bootinfo["user"]["employee"] = employee -- GitLab From 3e336462bf784436d0a7cbbee4807b21f6f21a74 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 11:40:15 +0100 Subject: [PATCH 056/155] feat: Auto select supplier down payment account for supplier down payments --- .../doctype/payment_entry/payment_entry.json | 13 ++++++------- .../accounts/doctype/payment_entry/payment_entry.py | 3 +++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 1df58cd4cdb..31dcbc620cc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -641,10 +641,9 @@ }, { "default": "0", - "depends_on": "eval:[\"Pay\", \"Receive\"].includes(doc.payment_type)", + "depends_on": "eval:[\"Pay\"].includes(doc.payment_type)", "fieldname": "down_payment", "fieldtype": "Check", - "hidden": 1, "label": "Down Payment" }, { @@ -810,10 +809,10 @@ "is_sealed": 1, "is_submittable": 1, "links": [ - { - "group": "Integration Request", - "link_doctype": "Integration Request", - "link_fieldname": "reference_docname" + { + "group": "Integration Request", + "link_doctype": "Integration Request", + "link_fieldname": "reference_docname" }, { "is_child_table": 1, @@ -823,7 +822,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2023-11-23 12:07:20.887885", + "modified": "2023-12-01 11:23:54.646228", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f3adee1a254..37bd14f0510 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -281,6 +281,9 @@ class PaymentEntry(AccountsController): doc.delink_advance_entries(self.name) def check_if_down_payment(self): + if self.down_payment and self.payment_type == "Pay": + self.paid_to = get_party_account(self.party_type, self.party, self.company, down_payment=True) + is_down_payment = False for d in self.get("references"): if d.reference_doctype == "Sales Invoice": -- GitLab From c6f38f33a913465d6341ecb02d36099f3052f138 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 11:52:01 +0100 Subject: [PATCH 057/155] feat: Fetch supplier down payments in purchase invoices --- erpnext/controllers/accounts_controller.py | 46 ++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 899ad4aafe0..020329d6c5c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1020,29 +1020,35 @@ class AccountsController(TransactionBase): res = journal_entries + payment_entries - if self.doctype == "Sales Invoice" and order_list: - party_type = "Customer" - party = self.customer - party_account = (get_party_account(party_type, party, self.company),) - amount_field = "credit_in_account_currency" - order_doctype = "Sales Invoice" - - invoice_list = list( - set( - frappe.db.sql_list( - """ - SELECT si.name - FROM `tabSales Invoice` si - LEFT JOIN `tabSales Invoice Item` sii - ON sii.parent = si.name - WHERE si.is_down_payment_invoice = 1 - AND sii.sales_order in ({0}) - """.format( - ",".join([f'"{ol}"' for ol in order_list]) + if self.doctype in ("Sales Invoice", "Purchase Invoice") and order_list: + party_type = "Customer" if self.doctype == "Sales Invoice" else "Supplier" + party = self.customer if self.doctype == "Sales Invoice" else self.supplier + party_account = (get_party_account(party_type, party, self.company, down_payment=True),) + amount_field = ( + "credit_in_account_currency" + if self.doctype == "Sales Invoice" + else "debit_in_account_currency" + ) + order_doctype = "Sales Invoice" if self.doctype == "Sales Invoice" else "Purchase Order" + + invoice_list = order_list + if self.doctype == "Sales Invoice": + invoice_list = list( + set( + frappe.db.sql_list( + """ + SELECT si.name + FROM `tabSales Invoice` si + LEFT JOIN `tabSales Invoice Item` sii + ON sii.parent = si.name + WHERE si.is_down_payment_invoice = 1 + AND sii.sales_order in ({0}) + """.format( + ",".join([f'"{ol}"' for ol in order_list]) + ) ) ) ) - ) down_payments_je = get_advance_journal_entries( party_type, -- GitLab From 72a641c8e3b42f4a9bc524b795812c356eba8a8d Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 12:28:23 +0100 Subject: [PATCH 058/155] wip: link between purchase invoice and down payment --- .../doctype/payment_entry/payment_entry.py | 1 + .../purchase_invoice/purchase_invoice.py | 1 + .../purchase_invoice_advance.json | 11 ++++++++- .../purchase_invoice_advance.py | 24 +++++++++++++++++-- .../doctype/sales_invoice/sales_invoice.py | 9 ------- erpnext/controllers/accounts_controller.py | 12 +++++++++- erpnext/public/js/utils/ledger_preview.js | 2 +- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 37bd14f0510..2b572e682e6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -283,6 +283,7 @@ class PaymentEntry(AccountsController): def check_if_down_payment(self): if self.down_payment and self.payment_type == "Pay": self.paid_to = get_party_account(self.party_type, self.party, self.company, down_payment=True) + return is_down_payment = False for d in self.get("references"): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7af8814ec38..2e96a9d1570 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -288,6 +288,7 @@ class PurchaseInvoice(BuyingController): self.validate_write_off_account() self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount") self.create_remarks() + self.validate_down_payment_advances() self.set_status() self.validate_purchase_receipt_if_update_stock() validate_inter_company_party( diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 6426ee99435..999793a5cb3 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -10,6 +10,7 @@ "reference_name", "remarks", "reference_row", + "is_down_payment", "col_break1", "advance_amount", "allocated_amount", @@ -111,13 +112,21 @@ "label": "Reference Exchange Rate", "non_negative": 1, "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.is_down_payment", + "fieldname": "is_down_payment", + "fieldtype": "Check", + "label": "Is Down Payment", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-23 21:13:18.013816", + "modified": "2023-12-01 12:05:18.070813", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Advance", diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py index ef7c68435ea..402c69fdbc1 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py @@ -1,10 +1,30 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -import frappe from frappe.model.document import Document class PurchaseInvoiceAdvance(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + advance_amount: DF.Currency + allocated_amount: DF.Currency + exchange_gain_loss: DF.Currency + is_down_payment: DF.Check + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + ref_exchange_rate: DF.Float + reference_name: DF.DynamicLink | None + reference_row: DF.Data | None + reference_type: DF.Link | None + remarks: DF.Text | None + # end: auto-generated types + pass diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 5dfe0410ce6..9de686c58f4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -203,15 +203,6 @@ class SalesInvoice(SellingController): ) ) - def validate_down_payment_advances(self): - for advance in self.get("advances"): - if ( - flt(advance.allocated_amount) <= flt(advance.advance_amount) - and advance.reference_type == "Payment Entry" - and cint(advance.is_down_payment) - ): - advance.allocated_amount = advance.advance_amount - def validate_item_cost_centers(self): for item in self.items: cost_center_company = frappe.get_cached_value("Cost Center", item.cost_center, "company") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 020329d6c5c..1f6ea4dd0b9 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1352,7 +1352,7 @@ class AccountsController(TransactionBase): if flt(d.allocated_amount) > 0: down_payment = ( cint(frappe.db.get_value(d.reference_type, d.reference_name, "down_payment")) - if d.reference_type == "Payment Entry" + if d.reference_type == "Payment Entry" and d.doctype == "Sales Invoice" else 0 ) args = frappe._dict( @@ -1385,6 +1385,7 @@ class AccountsController(TransactionBase): "exchange_gain_loss": flt(d.get("exchange_gain_loss")), } ) + if not down_payment: lst.append(args) @@ -2350,6 +2351,15 @@ class AccountsController(TransactionBase): else: frappe.throw(_("No updates pending for reposting")) + def validate_down_payment_advances(self): + for advance in self.get("advances"): + if ( + flt(advance.allocated_amount) <= flt(advance.advance_amount) + and advance.reference_type == "Payment Entry" + and cint(advance.is_down_payment) + ): + advance.allocated_amount = advance.advance_amount + @frappe.whitelist() def get_tax_rate(account_head): diff --git a/erpnext/public/js/utils/ledger_preview.js b/erpnext/public/js/utils/ledger_preview.js index 85d4a7d51e9..8c559bbc90f 100644 --- a/erpnext/public/js/utils/ledger_preview.js +++ b/erpnext/public/js/utils/ledger_preview.js @@ -14,7 +14,7 @@ erpnext.accounts.ledger_preview = { "docname": frm.doc.name }, "callback": function(response) { - me.make_dialog("Accounting Ledger Preview", "accounting_ledger_preview_html", response.message.gl_columns, response.message.gl_data); + me.make_dialog(__("Accounting Ledger Preview"), "accounting_ledger_preview_html", response.message.gl_columns, response.message.gl_data); } }) }, __("Preview")); -- GitLab From 7e9fac91514836d0df858941ca11861be639c13b Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 12:37:33 +0100 Subject: [PATCH 059/155] fix: Add logic to down payment invoice --- .../doctype/payment_entry/payment_entry.json | 4 +- .../purchase_invoice/purchase_invoice.js | 45 ++++++++++++++++++- .../purchase_invoice/purchase_invoice.json | 18 +++++++- .../purchase_invoice/purchase_invoice.py | 2 + 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 31dcbc620cc..bd0923c5c33 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -641,7 +641,7 @@ }, { "default": "0", - "depends_on": "eval:[\"Pay\"].includes(doc.payment_type)", + "depends_on": "eval:[\"Pay\", \"Receive\"].includes(doc.payment_type)", "fieldname": "down_payment", "fieldtype": "Check", "label": "Down Payment" @@ -822,7 +822,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2023-12-01 11:23:54.646228", + "modified": "2023-12-01 12:30:50.488738", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e44c380ee11..62b11988224 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -672,5 +672,48 @@ frappe.ui.form.on("Purchase Invoice", { }, }); } - } + }, + + is_down_payment_invoice: function(frm) { + if (frm.doc.is_down_payment_invoice) { + const so = [...new Set(frm.doc.items.map(line => { + return line.purchase_order + }))] + frm.set_value("items", []) + so.map(value => { + const d = frm.add_child("items"); + d.purchase_order = value; + }) + frm.refresh_field("items"); + + frappe.db.get_list("Item", {filters: {is_down_payment_item: 1}}) + .then(r => { + if (r.length == 1) { + frm.doc.items.forEach(line => { + frappe.model.set_value(line.doctype, line.name, "item_code", r[0].name) + }) + } + }) + } + }, }) + + +frappe.ui.form.on('Purchase Invoice Item', { + is_down_payment_item: function(frm, cdt, cdn) { + calculate_down_payment(locals[cdt][cdn]) + }, + down_payment_rate: function(frm, cdt, cdn) { + calculate_down_payment(locals[cdt][cdn]) + }, +}) + +const calculate_down_payment = line => { + if (line.purchase_order && line.is_down_payment_item) { + frappe.db.get_value("Sales Order", line.purchase_order, ["base_total", "total"], r => { + frappe.model.set_value(line.doctype, line.name, "price_list_rate", flt(line.down_payment_rate) / 100.0 * flt(r.total)) + frappe.model.set_value(line.doctype, line.name, "base_rate", flt(line.down_payment_rate) / 100.0 * flt(r.base_total)) + frappe.model.set_value(line.doctype, line.name, "rate", flt(line.down_payment_rate) / 100.0 * flt(r.total)) + }) + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 27753067a55..c288af26fba 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -14,6 +14,7 @@ "supplier_name", "tax_id", "company", + "is_down_payment_invoice", "column_break_6", "posting_date", "posting_time", @@ -1640,14 +1641,27 @@ "fieldtype": "Link", "label": "Supplier Group", "options": "Supplier Group" + }, + { + "default": "0", + "depends_on": "eval:!doc.is_return", + "fieldname": "is_down_payment_invoice", + "fieldtype": "Check", + "label": "Is Down Payment Invoice" } ], "icon": "uil uil-file-alt", "idx": 204, "is_sealed": 1, "is_submittable": 1, - "links": [], - "modified": "2023-11-29 15:35:44.697496", + "links": [ + { + "group": "Payment Request", + "link_doctype": "Payment Request", + "link_fieldname": "reference_name" + } + ], + "modified": "2023-12-01 12:30:32.635607", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2e96a9d1570..048480bcac2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -133,6 +133,7 @@ class PurchaseInvoice(BuyingController): in_words: DF.Data | None incoterm: DF.Link | None inter_company_invoice_reference: DF.Link | None + is_down_payment_invoice: DF.Check is_internal_supplier: DF.Check is_old_subcontracting_flow: DF.Check is_opening: DF.Literal["No", "Yes"] @@ -222,6 +223,7 @@ class PurchaseInvoice(BuyingController): write_off_amount: DF.Currency write_off_cost_center: DF.Link | None # end: auto-generated types + def __init__(self, *args, **kwargs): super(PurchaseInvoice, self).__init__(*args, **kwargs) self.status_updater = [ -- GitLab From 44ae3f350419b62540e5a68ca4ef56317f515aeb Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 12:39:15 +0100 Subject: [PATCH 060/155] fix: Commonify down payment determination logic --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 2b572e682e6..6eb8220b105 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -281,13 +281,9 @@ class PaymentEntry(AccountsController): doc.delink_advance_entries(self.name) def check_if_down_payment(self): - if self.down_payment and self.payment_type == "Pay": - self.paid_to = get_party_account(self.party_type, self.party, self.company, down_payment=True) - return - is_down_payment = False for d in self.get("references"): - if d.reference_doctype == "Sales Invoice": + if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"): is_dp_invoice = frappe.db.get_value( d.reference_doctype, d.reference_name, "is_down_payment_invoice" ) -- GitLab From 38c1f9f7e6dd4f5d91f91bc6d8286d67ce5983f7 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 12:40:16 +0100 Subject: [PATCH 061/155] fix: Hide field again --- 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 bd0923c5c33..a76c522504f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -644,6 +644,7 @@ "depends_on": "eval:[\"Pay\", \"Receive\"].includes(doc.payment_type)", "fieldname": "down_payment", "fieldtype": "Check", + "hidden": 1, "label": "Down Payment" }, { @@ -822,7 +823,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2023-12-01 12:30:50.488738", + "modified": "2023-12-01 12:39:56.533991", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", -- GitLab From ec364bf22f53714e5f8f56f200913c71e80934d0 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 13:24:21 +0100 Subject: [PATCH 062/155] fix: logic for purchase invoice --- .../purchase_invoice/purchase_invoice.js | 2 +- .../purchase_invoice/purchase_invoice.py | 44 ++++++--- .../purchase_invoice_item.json | 17 +++- .../purchase_invoice_item.py | 93 ++++++++++++++++++- erpnext/controllers/accounts_controller.py | 23 ++++- 5 files changed, 157 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 62b11988224..1caed8d952c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -710,7 +710,7 @@ frappe.ui.form.on('Purchase Invoice Item', { const calculate_down_payment = line => { if (line.purchase_order && line.is_down_payment_item) { - frappe.db.get_value("Sales Order", line.purchase_order, ["base_total", "total"], r => { + frappe.db.get_value("Purchase Order", line.purchase_order, ["base_total", "total"], r => { frappe.model.set_value(line.doctype, line.name, "price_list_rate", flt(line.down_payment_rate) / 100.0 * flt(r.total)) frappe.model.set_value(line.doctype, line.name, "base_rate", flt(line.down_payment_rate) / 100.0 * flt(r.base_total)) frappe.model.set_value(line.doctype, line.name, "rate", flt(line.down_payment_rate) / 100.0 * flt(r.total)) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 048480bcac2..91ac4e0182a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -543,6 +543,17 @@ class PurchaseInvoice(BuyingController): title=_("Missing Account"), ) item.expense_account = asset_category_account + elif self.is_down_payment_invoice and item.is_down_payment_item: + credit_to = get_party_account( + "Supplier", self.supplier, self.company, self.is_down_payment_invoice + ) + + if isinstance(credit_to, list): + credit_to = credit_to[-1] + + for d in self.get("items"): + d.expense_account = credit_to + elif not item.expense_account and for_validate: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -1053,23 +1064,26 @@ class PurchaseInvoice(BuyingController): ) if not self.is_internal_transfer(): - gl_entries.append( - self.get_gl_dict( - { - "account": expense_account, - "against": self.supplier, - "debit": amount, - "cost_center": item.cost_center, - "project": item.project or self.project, - "remarks": item.get("remarks") - or f'{_("Item")}: {item.qty} {item.item_code} - {_(item.uom)} / {_("Supplier")}: {self.supplier}', - "accounting_journal": self.accounting_journal, - }, - account_currency, - item=item, - ) + gl_dict = self.get_gl_dict( + { + "account": expense_account, + "against": self.supplier, + "debit": amount, + "cost_center": item.cost_center, + "project": item.project or self.project, + "remarks": item.get("remarks") + or f'{_("Item")}: {item.qty} {item.item_code} - {_(item.uom)} / {_("Supplier")}: {self.supplier}', + "accounting_journal": self.accounting_journal, + }, + account_currency, + item=item, ) + if item.get("is_down_payment_item"): + gl_dict.update({"party_type": "Supplier", "party": self.supplier}) + + gl_entries.append(gl_dict) + # check if the exchange rate has changed if item.get("purchase_receipt"): if ( 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 9b20624d34d..1ca02c492f9 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -9,6 +9,8 @@ "field_order": [ "item_code", "product_bundle", + "is_down_payment_item", + "down_payment_rate", "col_break1", "item_name", "description_section", @@ -918,12 +920,25 @@ "fieldtype": "Link", "label": "WIP Composite Asset", "options": "Asset" + }, + { + "default": "0", + "fieldname": "is_down_payment_item", + "fieldtype": "Check", + "label": "Is Down Payment Item", + "read_only": 1 + }, + { + "depends_on": "eval:doc.is_down_payment_item", + "fieldname": "down_payment_rate", + "fieldtype": "Percent", + "label": "Down Payment Rate" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-30 16:26:05.629780", + "modified": "2023-12-01 12:47:14.774060", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py index 6b6c1201076..b5c2a2c69ed 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py @@ -1,10 +1,99 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -import frappe from frappe.model.document import Document class PurchaseInvoiceItem(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + allow_zero_valuation_rate: DF.Check + amount: DF.Currency + apply_tds: DF.Check + asset_category: DF.Link | None + asset_location: DF.Link | None + base_amount: DF.Currency + base_net_amount: DF.Currency + base_net_rate: DF.Currency + base_price_list_rate: DF.Currency + base_rate: DF.Currency + base_rate_with_margin: DF.Currency + batch_no: DF.Link | None + bom: DF.Link | None + brand: DF.Link | None + conversion_factor: DF.Float + cost_center: DF.Link | None + deferred_expense_account: DF.Link | None + description: DF.TextEditor | None + discount_amount: DF.Currency + discount_percentage: DF.Percent + down_payment_rate: DF.Percent + enable_deferred_expense: DF.Check + expense_account: DF.Link | None + from_warehouse: DF.Link | None + image: DF.Attach | None + include_exploded_items: DF.Check + is_down_payment_item: DF.Check + is_fixed_asset: DF.Check + is_free_item: DF.Check + item_code: DF.Link | None + item_group: DF.Link | None + item_name: DF.Data + item_tax_amount: DF.Currency + item_tax_rate: DF.Code | None + item_tax_template: DF.Link | None + landed_cost_voucher_amount: DF.Currency + manufacturer: DF.Link | None + manufacturer_part_no: DF.Data | None + margin_rate_or_amount: DF.Float + margin_type: DF.Literal["", "Percentage", "Amount"] + net_amount: DF.Currency + net_rate: DF.Currency + page_break: DF.Check + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + po_detail: DF.Data | None + pr_detail: DF.Data | None + price_list_rate: DF.Currency + pricing_rules: DF.SmallText | None + product_bundle: DF.Link | None + project: DF.Link | None + purchase_invoice_item: DF.Data | None + purchase_order: DF.Link | None + purchase_receipt: DF.Link | None + qty: DF.Float + quality_inspection: DF.Link | None + rate: DF.Currency + rate_with_margin: DF.Currency + received_qty: DF.Float + rejected_qty: DF.Float + rejected_serial_and_batch_bundle: DF.Link | None + rejected_serial_no: DF.Text | None + rejected_warehouse: DF.Link | None + rm_supp_cost: DF.Currency + sales_invoice_item: DF.Data | None + serial_and_batch_bundle: DF.Link | None + serial_no: DF.Text | None + service_end_date: DF.Date | None + service_start_date: DF.Date | None + service_stop_date: DF.Date | None + stock_qty: DF.Float + stock_uom: DF.Link | None + stock_uom_rate: DF.Currency + total_weight: DF.Float + uom: DF.Link + valuation_rate: DF.Currency + warehouse: DF.Link | None + weight_per_unit: DF.Float + weight_uom: DF.Link | None + wip_composite_asset: DF.Link | None + # end: auto-generated types + pass diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1f6ea4dd0b9..d0c9a1e887b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1029,9 +1029,9 @@ class AccountsController(TransactionBase): if self.doctype == "Sales Invoice" else "debit_in_account_currency" ) - order_doctype = "Sales Invoice" if self.doctype == "Sales Invoice" else "Purchase Order" + order_doctype = self.doctype - invoice_list = order_list + invoice_list = [] if self.doctype == "Sales Invoice": invoice_list = list( set( @@ -1049,6 +1049,23 @@ class AccountsController(TransactionBase): ) ) ) + elif self.doctype == "Purchase Invoice": + invoice_list = list( + set( + frappe.db.sql_list( + """ + SELECT pi.name + FROM `tabPurchase Invoice` pi + LEFT JOIN `tabPurchase Invoice Item` pii + ON pii.parent = pi.name + WHERE pi.is_down_payment_invoice = 1 + AND pii.purchase_order in ({0}) + """.format( + ",".join([f'"{ol}"' for ol in order_list]) + ) + ) + ) + ) down_payments_je = get_advance_journal_entries( party_type, @@ -1352,7 +1369,7 @@ class AccountsController(TransactionBase): if flt(d.allocated_amount) > 0: down_payment = ( cint(frappe.db.get_value(d.reference_type, d.reference_name, "down_payment")) - if d.reference_type == "Payment Entry" and d.doctype == "Sales Invoice" + if d.reference_type == "Payment Entry" else 0 ) args = frappe._dict( -- GitLab From 61cd4c402b65676e2662303944b5ed7a8ce74ec3 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 13:31:11 +0100 Subject: [PATCH 063/155] fix: Query for advances --- erpnext/controllers/accounts_controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d0c9a1e887b..e45da06c68a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1019,11 +1019,10 @@ class AccountsController(TransactionBase): ) res = journal_entries + payment_entries - if self.doctype in ("Sales Invoice", "Purchase Invoice") and order_list: party_type = "Customer" if self.doctype == "Sales Invoice" else "Supplier" party = self.customer if self.doctype == "Sales Invoice" else self.supplier - party_account = (get_party_account(party_type, party, self.company, down_payment=True),) + party_account = (get_party_account(party_type, party, self.company),) amount_field = ( "credit_in_account_currency" if self.doctype == "Sales Invoice" -- GitLab From dc112ce67088a10cea2d3a82117bc173406b0772 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 13:50:06 +0100 Subject: [PATCH 064/155] fix: Commonify balance invoice logic --- .../purchase_invoice/purchase_invoice.py | 2 + .../doctype/sales_invoice/sales_invoice.py | 71 ------------------- erpnext/controllers/accounts_controller.py | 71 +++++++++++++++++++ 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 91ac4e0182a..c8bb5c7a2cf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -796,6 +796,8 @@ class PurchaseInvoice(BuyingController): self.make_tax_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) + self.make_down_payment_final_invoice_entries(gl_entries) + gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = merge_similar_entries(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9de686c58f4..eb9cec24f65 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1085,77 +1085,6 @@ class SalesInvoice(SellingController): ) ) - def make_down_payment_final_invoice_entries(self, gl_entries): - # In the case of a down payment with multiple payments, associated entries of - # the gl_entries list would be credited/debited multiple times if we didn't make - # sure that the pair of GL Entry was not already processed. - handled_down_payment_entries: set[str] = set() - - for d in self.get("advances"): - if ( - flt(d.allocated_amount) > 0 and d.reference_type == "Payment Entry" and cint(d.is_down_payment) - ): - payment_entry = frappe.get_doc(d.reference_type, d.reference_name) - down_payment_entries = [] - gl_entry = frappe.qb.DocType("GL Entry") - for ref in payment_entry.references: - down_payment_entries.extend( - ( - frappe.qb.from_(gl_entry) - .select( - "name", - "account", - "against", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ) - .where(gl_entry.voucher_type == ref.reference_doctype) - .where(gl_entry.voucher_no == ref.reference_name) - .where(gl_entry.is_cancelled == 0) - .for_update() - ).run(as_dict=1) - ) - - down_payment_accounts = [ - entry["against"] for entry in down_payment_entries if entry["account"] == self.debit_to - ] - for down_payment_entry in down_payment_entries: - if down_payment_entry["account"] in down_payment_accounts and not [ - x for x in gl_entries if x["account"] == down_payment_entry["account"] - ]: - gl_entries.append( - self.get_gl_dict( - { - "account": down_payment_entry["account"], - "against": down_payment_entry["account"], - "party_type": "Customer", - "party": self.customer, - "accounting_journal": self.accounting_journal, - }, - self.currency, - ) - ) - - for down_payment_entry in down_payment_entries: - if down_payment_entry["name"] in handled_down_payment_entries: - # Skip this down payment entry if it has already been handled, - # possibly for a previous payment entry. - continue - handled_down_payment_entries.add(down_payment_entry["name"]) - - for gl_entry in gl_entries: - if gl_entry["account"] == down_payment_entry["account"]: - if gl_entry["account"] not in down_payment_accounts: - gl_entry["debit"] -= down_payment_entry["debit"] - gl_entry["debit_in_account_currency"] -= down_payment_entry["debit_in_account_currency"] - gl_entry["credit"] -= down_payment_entry["credit"] - gl_entry["credit_in_account_currency"] -= down_payment_entry["credit_in_account_currency"] - else: - gl_entry["debit"] += down_payment_entry["credit"] - gl_entry["debit_in_account_currency"] += down_payment_entry["credit_in_account_currency"] - def make_tax_gl_entries(self, gl_entries): enable_discount_accounting = cint( frappe.db.get_single_value("Selling Settings", "enable_discount_accounting") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e45da06c68a..33f9e20dacf 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2376,6 +2376,77 @@ class AccountsController(TransactionBase): ): advance.allocated_amount = advance.advance_amount + def make_down_payment_final_invoice_entries(self, gl_entries): + # In the case of a down payment with multiple payments, associated entries of + # the gl_entries list would be credited/debited multiple times if we didn't make + # sure that the pair of GL Entry was not already processed. + handled_down_payment_entries: set[str] = set() + + for d in self.get("advances"): + if ( + flt(d.allocated_amount) > 0 and d.reference_type == "Payment Entry" and cint(d.is_down_payment) + ): + payment_entry = frappe.get_doc(d.reference_type, d.reference_name) + down_payment_entries = [] + gl_entry = frappe.qb.DocType("GL Entry") + for ref in payment_entry.references: + down_payment_entries.extend( + ( + frappe.qb.from_(gl_entry) + .select( + "name", + "account", + "against", + "debit", + "debit_in_account_currency", + "credit", + "credit_in_account_currency", + ) + .where(gl_entry.voucher_type == ref.reference_doctype) + .where(gl_entry.voucher_no == ref.reference_name) + .where(gl_entry.is_cancelled == 0) + .for_update() + ).run(as_dict=1) + ) + + down_payment_accounts = [ + entry["against"] for entry in down_payment_entries if entry["account"] == self.debit_to + ] + for down_payment_entry in down_payment_entries: + if down_payment_entry["account"] in down_payment_accounts and not [ + x for x in gl_entries if x["account"] == down_payment_entry["account"] + ]: + gl_entries.append( + self.get_gl_dict( + { + "account": down_payment_entry["account"], + "against": down_payment_entry["account"], + "party_type": "Customer" if self.doctype == "Sales Invoice" else "Supplier", + "party": self.customer if self.doctype == "Sales Invoice" else self.supplier, + "accounting_journal": self.accounting_journal, + }, + self.currency, + ) + ) + + for down_payment_entry in down_payment_entries: + if down_payment_entry["name"] in handled_down_payment_entries: + # Skip this down payment entry if it has already been handled, + # possibly for a previous payment entry. + continue + handled_down_payment_entries.add(down_payment_entry["name"]) + + for gl_entry in gl_entries: + if gl_entry["account"] == down_payment_entry["account"]: + if gl_entry["account"] not in down_payment_accounts: + gl_entry["debit"] -= down_payment_entry["debit"] + gl_entry["debit_in_account_currency"] -= down_payment_entry["debit_in_account_currency"] + gl_entry["credit"] -= down_payment_entry["credit"] + gl_entry["credit_in_account_currency"] -= down_payment_entry["credit_in_account_currency"] + else: + gl_entry["debit"] += down_payment_entry["credit"] + gl_entry["debit_in_account_currency"] += down_payment_entry["credit_in_account_currency"] + @frappe.whitelist() def get_tax_rate(account_head): -- GitLab From 46ef7727d6f97badf23bb77a6d3d90980d15b558 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 1 Dec 2023 14:19:41 +0100 Subject: [PATCH 065/155] fix: Final invoice for purchase down payments --- erpnext/controllers/accounts_controller.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 33f9e20dacf..c10efc7321a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2410,7 +2410,9 @@ class AccountsController(TransactionBase): ) down_payment_accounts = [ - entry["against"] for entry in down_payment_entries if entry["account"] == self.debit_to + entry["against"] + for entry in down_payment_entries + if (entry["account"] == self.debit_to if self.doctype == "Sales Invoice" else self.credit_to) ] for down_payment_entry in down_payment_entries: if down_payment_entry["account"] in down_payment_accounts and not [ @@ -2446,6 +2448,8 @@ class AccountsController(TransactionBase): else: gl_entry["debit"] += down_payment_entry["credit"] gl_entry["debit_in_account_currency"] += down_payment_entry["credit_in_account_currency"] + gl_entry["credit"] += down_payment_entry["debit"] + gl_entry["credit_in_account_currency"] += down_payment_entry["debit_in_account_currency"] @frappe.whitelist() -- GitLab From 52d7e3488bc33a248e75eb06716264355f253da5 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 7 Dec 2023 09:50:28 +0100 Subject: [PATCH 066/155] tests: Add test cases for down payments --- .../purchase_invoice/test_down_payment.py | 205 ++++ .../purchase_invoice/test_records.json | 40 +- .../doctype/sales_invoice/test_records.json | 1 + erpnext/controllers/accounts_controller.py | 3 + erpnext/stock/doctype/item/test_records.json | 952 +++++++++--------- 5 files changed, 737 insertions(+), 464 deletions(-) create mode 100644 erpnext/accounts/doctype/purchase_invoice/test_down_payment.py diff --git a/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py b/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py new file mode 100644 index 00000000000..981e87333db --- /dev/null +++ b/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py @@ -0,0 +1,205 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import flt, nowdate + +from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + +test_dependencies = ["Journal Entry", "Contact", "Address", "Account"] +test_records = frappe.get_test_records("Purchase Invoice") + + +class TestDownPayment(FrappeTestCase): + def setUp(self): + frappe.db.set_value( + "Company", + "_Test Company", + "default_advance_paid_account", + "_Test Down Payment - _TC", + ) + + def make(self, is_return=False, return_against=None): + test_record = [r for r in test_records if r.get("title") == "Down Payment Invoice"] + dp_invoice = frappe.copy_doc(test_record[0]) + dp_invoice.is_down_payment_invoice = 1 + if is_return: + dp_invoice.is_return = True + dp_invoice.return_against = return_against + + for item in dp_invoice.items: + item.qty = item.qty * -1 + + dp_invoice.insert() + dp_invoice.submit() + return dp_invoice + + def test_down_payment_gl_entries(self): + dp_invoice = self.make() + self.assertTrue(dp_invoice.items[0].expense_account == "_Test Down Payment - _TC") + self.assertTrue(flt(dp_invoice.outstanding_amount) == 500) + + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Purchase Invoice", "voucher_no": dp_invoice.name}, + pluck="account", + ) + self.assertListEqual(gl_entries, ["_Test Down Payment - _TC", "Creditors - _TC"]) + + def test_outstanding_amount_for_advances(self): + dp_invoice_1 = self.make() + dp_invoice_2 = self.make() + + self.assertTrue(flt(dp_invoice_1.outstanding_amount) == 500.0) + self.assertTrue(flt(dp_invoice_2.outstanding_amount) == 500.0) + + pe = get_payment_entry("Purchase Invoice", dp_invoice_1.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = dp_invoice_1.currency + pe.paid_to_account_currency = dp_invoice_1.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = dp_invoice_1.grand_total + dp_invoice_2.grand_total + pe.received_amount = dp_invoice_1.grand_total + dp_invoice_2.grand_total + + pe.append( + "references", + { + "reference_doctype": "Purchase Invoice", + "reference_name": dp_invoice_2.name, + "allocated_amount": dp_invoice_2.outstanding_amount, + }, + ) + + pe.insert() + pe.submit() + + dp_invoice_1.reload() + dp_invoice_2.reload() + + self.assertTrue(flt(dp_invoice_1.outstanding_amount) == 0) + self.assertTrue(flt(dp_invoice_2.outstanding_amount) == 0) + + return_invoice = self.make(is_return=True, return_against=dp_invoice_2.name) + return_invoice.reload() + self.assertTrue(flt(return_invoice.outstanding_amount) == -500.0) + dp_invoice_2.reload() + self.assertTrue( + flt(dp_invoice_2.outstanding_amount) == 0 + ) # 2023-08-29: Credit Note don't change the outstanding amount of the source invoice anymore + + def test_outstanding_amount_for_payment_entries(self): + dp_invoice = self.make() + pe = get_payment_entry("Purchase Invoice", dp_invoice.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = dp_invoice.currency + pe.paid_to_account_currency = dp_invoice.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = dp_invoice.grand_total + pe.insert() + pe.submit() + + self.assertTrue(pe.paid_to == "Creditors - _TC") + + dp_invoice.reload() + self.assertTrue(flt(dp_invoice.outstanding_amount) == 0) + + def test_outstanding_amount_for_credit_notes(self): + dp_invoice = self.make() + pe = get_payment_entry("Purchase Invoice", dp_invoice.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = dp_invoice.currency + pe.paid_to_account_currency = dp_invoice.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = dp_invoice.outstanding_amount + pe.insert() + pe.submit() + + dp_invoice.reload() + self.assertTrue(flt(dp_invoice.outstanding_amount) == 0) + + return_invoice = self.make(is_return=True, return_against=dp_invoice.name) + return_invoice.reload() + self.assertTrue(flt(return_invoice.outstanding_amount) == -500.0) + + dp_invoice.reload() + self.assertTrue( + flt(dp_invoice.outstanding_amount) == 0 + ) # 2023-08-29: Credit Note don't change the outstanding amount of the source invoice anymore + + pe = get_payment_entry("Purchase Invoice", return_invoice.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = return_invoice.currency + pe.paid_to_account_currency = return_invoice.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = abs(return_invoice.outstanding_amount) + pe.received_amount = abs(return_invoice.outstanding_amount) + pe.insert() + pe.submit() + + return_invoice.reload() + self.assertTrue(flt(return_invoice.outstanding_amount) == 0) + + def test_gl_entries_for_final_invoice(self): + dp_invoice = self.make() + pe = get_payment_entry("Purchase Invoice", dp_invoice.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = dp_invoice.currency + pe.paid_to_account_currency = dp_invoice.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = abs(dp_invoice.outstanding_amount) + pe.received_amount = abs(dp_invoice.outstanding_amount) + pe.insert() + pe.submit() + + gl = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": pe.name}, + fields=["is_cancelled", "debit", "credit", "account"], + ) + self.assertTrue(not any(x for x in gl if x.is_cancelled == 1)) + + final_invoice = frappe.copy_doc(dp_invoice) + final_invoice.docstatus = 0 + final_invoice.is_down_payment_invoice = False + final_invoice.items[0].item_code = "_Test Item" + final_invoice.items[0].expense_account = "_Test Account Cost for Goods Sold - _TC" + final_invoice.items[0].rate = 1000 + final_invoice.debit_to = "Creditors - _TC" + final_invoice.allocate_advances_automatically = 0 + final_invoice.insert() + + pe.reload() + final_invoice.reload() + final_invoice.append( + "advances", + { + "reference_type": "Payment Entry", + "reference_name": pe.name, + "reference_row": pe.references[0].name, + "advance_amount": flt(500.0), + "allocated_amount": flt(500.0), + "is_down_payment": 1, + }, + ) + final_invoice.save() + final_invoice.submit() + + self.assertTrue(flt(final_invoice.outstanding_amount), 500) + + gl = frappe.get_all( + "GL Entry", + filters={"voucher_type": "Payment Entry", "voucher_no": pe.name}, + fields=["is_cancelled", "debit", "credit", "account"], + ) + self.assertTrue( + not any(x.get("account") == "_Test Down Payment - _TC" for x in gl if x.is_cancelled == 0) + ) + self.assertTrue(any(x.get("account") == "Creditors - _TC" for x in gl if x.is_cancelled == 0)) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index 757e1f5997e..023437d06f9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -23,7 +23,7 @@ "qty": 10, "rate": 50, "uom": "_Test UOM", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "_Test Warehouse - _TC" }, { "amount": 750, @@ -39,7 +39,7 @@ "qty": 5, "rate": 150, "uom": "_Test UOM", - "warehouse": "_Test Warehouse - _TC" + "warehouse": "_Test Warehouse - _TC" } ], "grand_total": 0, @@ -141,9 +141,6 @@ "supplier": "_Test Supplier", "supplier_name": "_Test Supplier" }, - - - { "bill_no": "NA", "buying_price_list": "_Test Price List", @@ -205,5 +202,38 @@ ], "supplier": "_Test Supplier", "supplier_name": "_Test Supplier" + }, + { + "company": "_Test Company", + "conversion_rate": 1.0, + "currency": "INR", + "cost_center": "_Test Cost Center - _TC", + "supplier": "_Test Supplier", + "supplier_name": "_Test Supplier", + "doctype": "Purchase Invoice", + "debit_to": "_Test Payable - _TC", + "items": [ + { + "conversion_factor": 1.0, + "cost_center": "_Test Cost Center - _TC", + "doctype": "Purchase Invoice Item", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "item_code": "999-Down Payment", + "item_name": "999-Down Payment", + "is_down_payment_item": 1, + "parentfield": "items", + "qty": 1, + "rate": 500.0, + "uom": "_Test UOM" + } + ], + "base_grand_total": 561.8, + "grand_total": 561.8, + "is_pos": 0, + "base_net_total": 500.0, + "plc_conversion_rate": 1.0, + "price_list_currency": "INR", + "buying_price_list": "_Test Price List", + "title": "Down Payment Invoice" } ] diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index cfbf8080630..f80e3e6d531 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -417,6 +417,7 @@ "doctype": "Sales Invoice Item", "income_account": "Sales - _TC", "expense_account": "_Test Account Cost for Goods Sold - _TC", + "is_down_payment_item": 1, "item_code": "999-Down Payment", "item_name": "999-Down Payment", "parentfield": "items", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c10efc7321a..2ed5366c8fd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1100,6 +1100,9 @@ class AccountsController(TransactionBase): return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print")) def validate_advance_entries(self): + if self.get("is_down_payment_invoice"): + return + order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index 23b4cf90b31..699f60e99fe 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -1,460 +1,494 @@ [ - { - "description": "_Test Item 1", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item", - "item_group": "_Test Item Group", - "item_name": "_Test Item", - "apply_warehouse_wise_reorder_level": 1, - "opening_stock": 10, - "valuation_rate": 100, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "reorder_levels": [ - { - "material_request_type": "Purchase", - "warehouse": "_Test Warehouse - _TC", - "warehouse_reorder_level": 20, - "warehouse_reorder_qty": 20 - } - ], - "uoms": [ - { - "uom": "_Test UOM", - "conversion_factor": 1.0 - }, - { - "uom": "_Test UOM 1", - "conversion_factor": 10.0 - } - ], - "stock_uom": "_Test UOM" - }, - { - "description": "_Test Item 2", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item 2", - "item_group": "_Test Item Group", - "item_name": "_Test Item 2", - "stock_uom": "_Test UOM", - "opening_stock": 10, - "valuation_rate": 100, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Home Desktop 100 3", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Home Desktop 100", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Item Home Desktop 100", - "valuation_rate": 100, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "taxes": [ - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" - } - ], - "stock_uom": "_Test UOM 1" - }, - { - "description": "_Test Item Home Desktop 200 4", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Home Desktop 200", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Item Home Desktop 200", - "stock_uom": "_Test UOM 1", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Product Bundle Item 5", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 0, - "is_sub_contracted_item": 0, - "item_code": "_Test Product Bundle Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Product Bundle Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test FG Item 6", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "item_code": "_Test FG Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test FG Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Non Stock Item 7", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 0, - "is_sub_contracted_item": 0, - "item_code": "_Test Non Stock Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Non Stock Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Serialized Item 8", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 1, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Serialized Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Serialized Item", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Serialized Item 9", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 1, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Serialized Item With Series", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Serialized Item With Series", - "serial_no_series": "ABCD.#####", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Home Desktop Manufactured 10", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Home Desktop Manufactured", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Item Home Desktop Manufactured", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test FG Item 2 11", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "item_code": "_Test FG Item 2", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test FG Item 2", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Variant Item 12", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 1, - "item_code": "_Test Variant Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Variant Item", - "stock_uom": "_Test UOM", - "has_variants": 1, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "attributes": [ - { - "attribute": "Test Size" - } - ], - "apply_warehouse_wise_reorder_level": 1, - "reorder_levels": [ - { - "material_request_type": "Purchase", - "warehouse": "_Test Warehouse - _TC", - "warehouse_reorder_level": 20, - "warehouse_reorder_qty": 20 - } - ] - }, - { - "description": "_Test Item 1", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Warehouse Group Wise Reorder", - "item_group": "_Test Item Group", - "item_name": "_Test Item Warehouse Group Wise Reorder", - "apply_warehouse_wise_reorder_level": 1, - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse Group-C1 - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "reorder_levels": [ - { - "warehouse_group": "_Test Warehouse Group - _TC", - "material_request_type": "Purchase", - "warehouse": "_Test Warehouse Group-C1 - _TC", - "warehouse_reorder_level": 20, - "warehouse_reorder_qty": 20 - } - ], - "stock_uom": "_Test UOM" - }, - { - "description": "_Test Item With Item Tax Template", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item With Item Tax Template", - "item_group": "_Test Item Group", - "item_name": "_Test Item With Item Tax Template", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "taxes": [ - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" - }, - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", - "tax_category": "_Test Tax Category 1" - } - ] - }, - { - "description": "_Test Item Inherit Group Item Tax Template 1", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Inherit Group Item Tax Template 1", - "item_group": "_Test Item Group Tax Parent", - "item_name": "_Test Item Inherit Group Item Tax Template 1", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Inherit Group Item Tax Template 2", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Inherit Group Item Tax Template 2", - "item_group": "_Test Item Group Tax Child Override", - "item_name": "_Test Item Inherit Group Item Tax Template 2", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }] - }, - { - "description": "_Test Item Override Group Item Tax Template", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 1, - "is_sub_contracted_item": 0, - "item_code": "_Test Item Override Group Item Tax Template", - "item_group": "_Test Item Group Tax Child Override", - "item_name": "_Test Item Override Group Item Tax Template", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "taxes": [ - { - "doctype": "Item Tax", - "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 20 - _TC" - }, - { - "doctype": "Item Tax", - "parentfield": "taxes", - "tax_category": "_Test Tax Category 1", - "item_tax_template": "_Test Item Tax Template 1 - _TC" - } - ] - }, - { - "description": "_Test", - "doctype": "Item", - "is_stock_item": 1, - "item_code": "138-CMS Shoe", - "item_group": "_Test Item Group", - "item_name": "138-CMS Shoe", - "stock_uom": "_Test UOM" - }, - { - "description": "_Test", - "doctype": "Item", - "is_stock_item": 1, - "item_code": "999-Down Payment", - "item_group": "_Test Item Group", - "item_name": "999-Down Payment", - "stock_uom": "_Test UOM" - } - ] + { + "description": "_Test Item 1", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item", + "item_group": "_Test Item Group", + "item_name": "_Test Item", + "apply_warehouse_wise_reorder_level": 1, + "opening_stock": 10, + "valuation_rate": 100, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "reorder_levels": [ + { + "material_request_type": "Purchase", + "warehouse": "_Test Warehouse - _TC", + "warehouse_reorder_level": 20, + "warehouse_reorder_qty": 20 + } + ], + "uoms": [ + { + "uom": "_Test UOM", + "conversion_factor": 1.0 + }, + { + "uom": "_Test UOM 1", + "conversion_factor": 10.0 + } + ], + "stock_uom": "_Test UOM" + }, + { + "description": "_Test Item 2", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item 2", + "item_group": "_Test Item Group", + "item_name": "_Test Item 2", + "stock_uom": "_Test UOM", + "opening_stock": 10, + "valuation_rate": 100, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Home Desktop 100 3", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Home Desktop 100", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Item Home Desktop 100", + "valuation_rate": 100, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "taxes": [ + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" + } + ], + "stock_uom": "_Test UOM 1" + }, + { + "description": "_Test Item Home Desktop 200 4", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Home Desktop 200", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Item Home Desktop 200", + "stock_uom": "_Test UOM 1", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Product Bundle Item 5", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 0, + "is_sub_contracted_item": 0, + "item_code": "_Test Product Bundle Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Product Bundle Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test FG Item 6", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "item_code": "_Test FG Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test FG Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Non Stock Item 7", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 0, + "is_sub_contracted_item": 0, + "item_code": "_Test Non Stock Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Non Stock Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Serialized Item 8", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 1, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Serialized Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Serialized Item", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Serialized Item 9", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 1, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Serialized Item With Series", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Serialized Item With Series", + "serial_no_series": "ABCD.#####", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Home Desktop Manufactured 10", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Home Desktop Manufactured", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Item Home Desktop Manufactured", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test FG Item 2 11", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "item_code": "_Test FG Item 2", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test FG Item 2", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Variant Item 12", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 1, + "item_code": "_Test Variant Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Variant Item", + "stock_uom": "_Test UOM", + "has_variants": 1, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "attributes": [ + { + "attribute": "Test Size" + } + ], + "apply_warehouse_wise_reorder_level": 1, + "reorder_levels": [ + { + "material_request_type": "Purchase", + "warehouse": "_Test Warehouse - _TC", + "warehouse_reorder_level": 20, + "warehouse_reorder_qty": 20 + } + ] + }, + { + "description": "_Test Item 1", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Warehouse Group Wise Reorder", + "item_group": "_Test Item Group", + "item_name": "_Test Item Warehouse Group Wise Reorder", + "apply_warehouse_wise_reorder_level": 1, + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse Group-C1 - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "reorder_levels": [ + { + "warehouse_group": "_Test Warehouse Group - _TC", + "material_request_type": "Purchase", + "warehouse": "_Test Warehouse Group-C1 - _TC", + "warehouse_reorder_level": 20, + "warehouse_reorder_qty": 20 + } + ], + "stock_uom": "_Test UOM" + }, + { + "description": "_Test Item With Item Tax Template", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item With Item Tax Template", + "item_group": "_Test Item Group", + "item_name": "_Test Item With Item Tax Template", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "taxes": [ + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC" + }, + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "tax_category": "_Test Tax Category 1" + } + ] + }, + { + "description": "_Test Item Inherit Group Item Tax Template 1", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Inherit Group Item Tax Template 1", + "item_group": "_Test Item Group Tax Parent", + "item_name": "_Test Item Inherit Group Item Tax Template 1", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Inherit Group Item Tax Template 2", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Inherit Group Item Tax Template 2", + "item_group": "_Test Item Group Tax Child Override", + "item_name": "_Test Item Inherit Group Item Tax Template 2", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ] + }, + { + "description": "_Test Item Override Group Item Tax Template", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 1, + "is_sub_contracted_item": 0, + "item_code": "_Test Item Override Group Item Tax Template", + "item_group": "_Test Item Group Tax Child Override", + "item_name": "_Test Item Override Group Item Tax Template", + "stock_uom": "_Test UOM", + "item_defaults": [ + { + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + } + ], + "taxes": [ + { + "doctype": "Item Tax", + "parentfield": "taxes", + "item_tax_template": "_Test Account Excise Duty @ 20 - _TC" + }, + { + "doctype": "Item Tax", + "parentfield": "taxes", + "tax_category": "_Test Tax Category 1", + "item_tax_template": "_Test Item Tax Template 1 - _TC" + } + ] + }, + { + "description": "_Test", + "doctype": "Item", + "is_stock_item": 1, + "item_code": "138-CMS Shoe", + "item_group": "_Test Item Group", + "item_name": "138-CMS Shoe", + "stock_uom": "_Test UOM" + }, + { + "description": "_Test", + "doctype": "Item", + "is_down_payment_item": 1, + "item_code": "999-Down Payment", + "item_group": "_Test Item Group", + "item_name": "999-Down Payment", + "stock_uom": "_Test UOM" + } +] -- GitLab From bb1150d8b1d22a6286756ce5269337c8faf8b2de Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 7 Dec 2023 15:29:50 +0530 Subject: [PATCH 067/155] chore: minor code cleanup (#38615) --- .../stock/doctype/stock_reconciliation/stock_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index ec5443081e7..651d558cf6e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -209,7 +209,7 @@ frappe.ui.form.on("Stock Reconciliation", { set_amount_quantity: function(doc, cdt, cdn) { var d = frappe.model.get_doc(cdt, cdn); - if (d.qty & d.valuation_rate) { + if (d.qty && d.valuation_rate) { frappe.model.set_value(cdt, cdn, "amount", flt(d.qty) * flt(d.valuation_rate)); frappe.model.set_value(cdt, cdn, "quantity_difference", flt(d.qty) - flt(d.current_qty)); frappe.model.set_value(cdt, cdn, "amount_difference", flt(d.amount) - flt(d.current_amount)); -- GitLab From 0323b11604b994d2de0b39239b44cc8752041da3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 7 Dec 2023 18:00:07 +0530 Subject: [PATCH 068/155] fix: serial and batch bundle permission (#38618) --- .../serial_and_batch_bundle.json | 116 +++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json index c1500e261ff..482757eb61d 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -1,7 +1,7 @@ { "actions": [], "autoname": "naming_series:", - "creation": "2022-09-29 14:56:38.338267", + "creation": "2023-08-11 17:22:12.907518", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -250,7 +250,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-28 12:56:03.072224", + "modified": "2023-12-07 17:56:55.528563", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Bundle", @@ -270,6 +270,118 @@ "share": 1, "submit": 1, "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, + "write": 1 } ], "sort_field": "modified", -- GitLab From b05a65c887fa8d6a166de6f281e9418a46f9b3ea Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 7 Dec 2023 19:42:40 +0530 Subject: [PATCH 069/155] fix: ignore non-existing regional customizations (#38621) --- .../setup_wizard/operations/taxes_setup.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 15f84d7478f..afa1b8a371a 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -2,12 +2,11 @@ # License: GNU General Public License v3. See license.txt -import frappe -import copy -import os import json -from frappe.utils import flt -from erpnext.accounts.doctype.account.account import RootNotEditable +import os + +import frappe +from frappe import _ def create_sales_tax(args): @@ -88,7 +87,13 @@ def make_sales_and_purchase_tax_templates(company, tax_data, template_name=None) "description": tax_details.get("description") or tax_details.get("account_name"), } ) - sales_tax_template["taxes"].append(tax_details) + frappe.get_attr(module_name)(country, company) + except (ImportError, AttributeError): + pass + except Exception: + # Log error and ignore if failed to setup regional tax settings + frappe.log_error("Unable to setup regional tax settings") + if not sales_tax_template.get("taxes"): return -- GitLab From 78500312a7d97ee8b4d7ca410663221d8d9868aa Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 7 Dec 2023 19:39:24 +0530 Subject: [PATCH 070/155] fix: format only if searched text contain link value text --- erpnext/public/js/financial_statements.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 6431d8f184f..8b023a80b55 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements"); erpnext.financial_statements = { "filters": get_filters(), - "formatter": function(value, row, column, data, default_formatter) { + "formatter": function(value, row, column, data, default_formatter, filter) { if (data && column.fieldname=="account") { value = data.account_name || value; + if (filter && filter?.text && filter?.type == "contains") { + if (!value.toLowerCase().includes(filter.text)) { + return value; + } + } + if (data.account) { column.link_onclick = "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; -- GitLab From 6dccb1943884cfe67462c5900c15054ac8f92317 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 7 Dec 2023 20:28:19 +0000 Subject: [PATCH 071/155] fix: merge conflict --- .../setup_wizard/operations/taxes_setup.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index afa1b8a371a..ef65dddcc0f 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -1,12 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -import json import os import frappe -from frappe import _ +import copy +import json +from frappe.utils import flt +from erpnext.accounts.doctype.account.account import RootNotEditable def create_sales_tax(args): @@ -87,12 +88,7 @@ def make_sales_and_purchase_tax_templates(company, tax_data, template_name=None) "description": tax_details.get("description") or tax_details.get("account_name"), } ) - frappe.get_attr(module_name)(country, company) - except (ImportError, AttributeError): - pass - except Exception: - # Log error and ignore if failed to setup regional tax settings - frappe.log_error("Unable to setup regional tax settings") + sales_tax_template["taxes"].append(tax_details) if not sales_tax_template.get("taxes"): @@ -186,7 +182,8 @@ def update_regional_tax_settings(country, company): frappe.scrub(country) ) frappe.get_attr(module_name)(country, company) + except (ImportError, AttributeError): + pass except Exception: # Log error and ignore if failed to setup regional tax settings frappe.log_error("Unable to setup regional tax settings") - pass -- GitLab From 21a5111114e9d0f676bca4ff092c9b3456d5bf96 Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Thu, 7 Dec 2023 23:08:23 +0100 Subject: [PATCH 072/155] refactor: simplify conditional logic Command: `sourcery review --fix --enable de-morgan .` --- erpnext/__init__.py | 8 ++++---- erpnext/accounts/deferred_revenue.py | 2 +- .../exchange_rate_revaluation.py | 2 +- .../loyalty_program/test_loyalty_program.py | 2 +- .../doctype/payment_entry/payment_entry.py | 15 ++++++++------- .../accounts/doctype/pricing_rule/pricing_rule.py | 2 +- .../process_payment_reconciliation.py | 2 +- .../doctype/purchase_invoice/purchase_invoice.py | 2 +- .../doctype/sales_invoice/sales_invoice.py | 8 ++++---- .../doctype/shipping_rule/shipping_rule.py | 4 ++-- .../unreconcile_payment/unreconcile_payment.py | 6 ++---- .../accounts_receivable/accounts_receivable.py | 4 ++-- .../deferred_revenue_and_expense.py | 2 +- .../gross_and_net_profit_report.py | 2 +- .../accounts/report/gross_profit/gross_profit.py | 2 +- .../tax_withholding_details.py | 2 +- erpnext/accounts/utils.py | 4 ++-- erpnext/assets/doctype/asset/asset.py | 2 +- .../doctype/asset_movement/asset_movement.py | 2 +- .../purchase_order_analysis.py | 2 +- .../requested_items_to_order_and_receive.py | 4 ++-- .../supplier_quotation_comparison.py | 4 ++-- erpnext/controllers/queries.py | 11 ++++++++--- erpnext/controllers/stock_controller.py | 2 +- erpnext/crm/doctype/appointment/appointment.py | 4 ++-- erpnext/erpnext_integrations/utils.py | 2 +- .../doctype/blanket_order/blanket_order.py | 2 +- erpnext/manufacturing/doctype/bom/bom.py | 13 +++++++++---- .../doctype/production_plan/production_plan.py | 2 +- .../doctype/work_order/work_order.py | 8 ++++---- erpnext/patches/v12_0/set_task_status.py | 2 +- erpnext/patches/v13_0/update_sla_enhancements.py | 2 +- erpnext/portal/utils.py | 2 +- .../sales_order_analysis/sales_order_analysis.py | 2 +- .../authorization_control.py | 10 ++++++++-- erpnext/setup/doctype/department/department.py | 2 +- .../setup/doctype/email_digest/email_digest.py | 2 +- erpnext/setup/doctype/employee/employee.py | 2 +- erpnext/setup/install.py | 2 +- .../stock/doctype/putaway_rule/putaway_rule.py | 2 +- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- .../stock_ledger_entry/stock_ledger_entry.py | 4 +++- .../stock_reservation_entry.py | 2 +- erpnext/stock/stock_ledger.py | 2 +- .../service_level_agreement.py | 8 ++++---- 45 files changed, 95 insertions(+), 78 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 10da6b02331..786162c0cac 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -47,7 +47,7 @@ def get_company_currency(company): """Returns the default company currency""" if not frappe.flags.company_currency: frappe.flags.company_currency = {} - if not company in frappe.flags.company_currency: + if company not in frappe.flags.company_currency: frappe.flags.company_currency[company] = frappe.db.get_value( "Company", company, "default_currency", cache=True ) @@ -81,7 +81,7 @@ def is_perpetual_inventory_enabled(company): if not hasattr(frappe.local, "enable_perpetual_inventory"): frappe.local.enable_perpetual_inventory = {} - if not company in frappe.local.enable_perpetual_inventory: + if company not in frappe.local.enable_perpetual_inventory: frappe.local.enable_perpetual_inventory[company] = ( frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 ) @@ -96,7 +96,7 @@ def get_default_finance_book(company=None): if not hasattr(frappe.local, "default_finance_book"): frappe.local.default_finance_book = {} - if not company in frappe.local.default_finance_book: + if company not in frappe.local.default_finance_book: frappe.local.default_finance_book[company] = frappe.get_cached_value( "Company", company, "default_finance_book" ) @@ -108,7 +108,7 @@ def get_party_account_type(party_type): if not hasattr(frappe.local, "party_account_types"): frappe.local.party_account_types = {} - if not party_type in frappe.local.party_account_types: + if party_type not in frappe.local.party_account_types: frappe.local.party_account_types[party_type] = ( frappe.db.get_value("Party Type", party_type, "account_type") or "" ) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index bef18ee1db4..487ab9662ba 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -232,7 +232,7 @@ def calculate_monthly_amount( if amount + already_booked_amount_in_account_currency > item.net_amount: amount = item.net_amount - already_booked_amount_in_account_currency - if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date: partial_month = flt(date_diff(end_date, start_date)) / flt( date_diff(get_last_day(end_date), get_first_day(start_date)) ) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 977cfe94f8a..bc06286bd37 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -22,7 +22,7 @@ class ExchangeRateRevaluation(Document): self.set_total_gain_loss() def validate_rounding_loss_allowance(self): - if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1): + if self.rounding_loss_allowance < 0 or self.rounding_loss_allowance >= 1: frappe.throw(_("Rounding Loss Allowance should be between 0 and 1")) def set_total_gain_loss(self): diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 351cfce0ca3..15a1b575d55 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -143,7 +143,7 @@ class TestLoyaltyProgram(unittest.TestCase): "Loyalty Point Entry", {"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer}, ) - self.assertEqual(True, not (lpe is None)) + self.assertEqual(True, lpe is not None) # cancelling sales invoice si.reload() diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f3adee1a254..c379558b014 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -335,12 +335,12 @@ class PaymentEntry(AccountsController): self.set("mode_of_payment", party_mode_of_payment) # @dokos - if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance): + if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance: acc = get_account_details(self.paid_from, self.posting_date, self.cost_center) self.paid_from_account_currency = acc.account_currency self.paid_from_account_balance = acc.account_balance - if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance): + if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance: acc = get_account_details(self.paid_to, self.posting_date, self.cost_center) self.paid_to_account_currency = acc.account_currency self.paid_to_account_balance = acc.account_balance @@ -356,8 +356,9 @@ class PaymentEntry(AccountsController): ) -> None: for d in self.get("references"): if d.allocated_amount: - if update_ref_details_only_for and ( - not (d.reference_doctype, d.reference_name) in update_ref_details_only_for + if ( + update_ref_details_only_for + and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for ): continue @@ -673,7 +674,7 @@ class PaymentEntry(AccountsController): self.status = status def set_tax_withholding(self): - if not self.party_type == "Supplier": + if self.party_type != "Supplier": return if not self.apply_tax_withholding_amount: @@ -764,7 +765,7 @@ class PaymentEntry(AccountsController): self.base_received_amount = self.base_paid_amount if ( self.paid_from_account_currency == self.paid_to_account_currency - and not self.payment_type == "Internal Transfer" + and self.payment_type != "Internal Transfer" ): self.received_amount = self.paid_amount @@ -1717,7 +1718,7 @@ def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: "Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date" ) for payment_term in payment_schedule: - if not payment_term.outstanding > 0.1: + if payment_term.outstanding <= 0.1: continue doc_details = exc_rates.get(payment_term.parent, None) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 45a8dd68a1e..101cb4fb3a6 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -176,7 +176,7 @@ class PricingRule(Document): def validate_price_list_with_currency(self): if self.currency and self.for_price_list: price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True) - if not self.currency == price_list_currency: + if self.currency != price_list_currency: throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency)) def validate_dates(self): diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index eb54342ca27..269ac3b31f1 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -450,7 +450,7 @@ def reconcile(doc: None | str = None) -> None: frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") else: - if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"): + if frappe.db.get_value("Process Payment Reconciliation", doc, "status") != "Paused": # trigger next batch in job # generate reconcile job name allocation = get_next_allocation(log) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7af8814ec38..00d7195dcd7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -374,7 +374,7 @@ class PurchaseInvoice(BuyingController): check_list = [] for d in self.get("items"): - if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: + if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt: check_list.append(d.purchase_order) check_on_hold_or_closed_status("Purchase Order", d.purchase_order) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 5dfe0410ce6..1aaa46ac9f5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -297,7 +297,7 @@ class SalesInvoice(SellingController): self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.check_credit_limit() - if not cint(self.is_pos) == 1 and not self.is_return: + if cint(self.is_pos) != 1 and not self.is_return: self.update_against_document_in_jv() self.update_time_sheet(self.name) @@ -1900,9 +1900,9 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc if inter_company_reference: doc = frappe.get_doc(ref_doc, inter_company_reference) ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer - if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party: + if frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") != party: frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype))) - if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company: + if frappe.get_cached_value(ref_partytype, ref_party, "represents_company") != company: frappe.throw(_("Invalid Company for Inter Company Transaction.")) elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party: @@ -1912,7 +1912,7 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc filters={"parenttype": partytype, "parent": party}, ) companies = [d.company for d in companies] - if not company in companies: + if company not in companies: frappe.throw( _("{0} not allowed to transact with {1}. Please change the Company.").format( _(partytype), company diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 688a648cfc3..839d01a95f6 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -192,14 +192,14 @@ class ShippingRule(Document): } if self.shipping_rule_type == "Selling": # check if not applied on purchase - if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges": + if doc.meta.get_field("taxes").options != "Sales Taxes and Charges": frappe.throw(_("Shipping rule only applicable for Selling")) shipping_charge["doctype"] = "Sales Taxes and Charges" else: # check if not applied on sales - if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges": + if doc.meta.get_field("taxes").options != "Purchase Taxes and Charges": frappe.throw(_("Shipping rule only applicable for Buying")) shipping_charge["doctype"] = "Purchase Taxes and Charges" diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py index 69639f32122..6f45ea24dea 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py @@ -18,10 +18,8 @@ from erpnext.accounts.utils import ( class UnreconcilePayment(Document): def validate(self): self.supported_types = ["Payment Entry", "Journal Entry"] - if not self.voucher_type in self.supported_types: - frappe.throw( - _("Only {0} are supported").format(comma_and([_(t) for t in self.supported_types])) - ) + if self.voucher_type not in self.supported_types: + frappe.throw(_("Only {0} are supported").format(comma_and(list(map(_, self.supported_types))))) @frappe.whitelist() def get_allocations_from_payment(self): diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 77789721e6c..16d1973f342 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -123,7 +123,7 @@ class ReceivablePayableReport(object): else: key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) - if not key in self.voucher_balance: + if key not in self.voucher_balance: self.voucher_balance[key] = frappe._dict( voucher_type=ple.voucher_type, voucher_no=ple.voucher_no, @@ -938,7 +938,7 @@ class ReceivablePayableReport(object): return True def get_party_details(self, party): - if not party in self.party_details: + if party not in self.party_details: if self.account_type == "Receivable": fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"] diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 65846d0b833..f50a5563a29 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -97,7 +97,7 @@ class Deferred_Item(object): if base_amount + already_booked_amount > self.base_net_amount: base_amount = self.base_net_amount - already_booked_amount - if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): + if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date: partial_month = flt(date_diff(end_date, start_date)) / flt( date_diff(get_last_day(end_date), get_first_day(start_date)) ) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 07483d744b9..19b4f8847f9 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -169,7 +169,7 @@ def set_total(node, value, complete_list, totals): totals[node["account"]] += value parent = node["parent_account"] - if not parent == "": + if parent != "": return set_total( next(item for item in complete_list if item["account"] == parent), value, complete_list, totals ) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 38060bb5b2e..e4efefe7f5a 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -695,7 +695,7 @@ class GrossProfitGenerator(object): def get_average_buying_rate(self, row, item_code): args = row - if not item_code in self.average_buying_rate: + if item_code not in self.average_buying_rate: args.update( { "voucher_type": row.parenttype, diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 3b94c165e84..f52bd8175f2 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -154,7 +154,7 @@ def get_gle_map(documents): ) for d in gle: - if not d.voucher_no in gle_map: + if d.voucher_no not in gle_map: gle_map[d.voucher_no] = [d] else: gle_map[d.voucher_no].append(d) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ded02c11c5a..b388254b839 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1050,11 +1050,11 @@ def get_outstanding_invoices( if ( min_outstanding and max_outstanding - and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding) + and (outstanding_amount < min_outstanding or outstanding_amount > max_outstanding) ): continue - if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: + if d.voucher_type != "Purchase Invoice" or d.voucher_no not in held_invoices: outstanding_invoices.append( frappe._dict( { diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f30be302e2e..48a2696032a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -238,7 +238,7 @@ class Asset(AccountsController): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): - if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): + if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice: frappe.throw( _("Please create purchase receipt or purchase invoice for the item {0}").format( self.item_code diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 620aad80ed9..267fe6f2592 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -65,7 +65,7 @@ class AssetMovement(Document): frappe.throw(_("Source and Target Location cannot be same")) if self.purpose == "Receipt": - if not (d.source_location) and not (d.target_location or d.to_employee): + if not (d.source_location) and not d.target_location and not d.to_employee: frappe.throw( _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) ) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index b6e46302ffe..b88efe13c00 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -114,7 +114,7 @@ def prepare_data(data, filters): if filters.get("group_by_po"): po_name = row["purchase_order"] - if not po_name in purchase_order_map: + if po_name not in purchase_order_map: # create an entry row_copy = copy.deepcopy(row) purchase_order_map[po_name] = row_copy diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 07187352eb7..d43101072bc 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -110,7 +110,7 @@ def prepare_data(data, filters): for row in data: # item wise map for charts - if not row["item_code"] in item_qty_map: + if row["item_code"] not in item_qty_map: item_qty_map[row["item_code"]] = { "qty": flt(row["stock_qty"], precision), "stock_qty": flt(row["stock_qty"], precision), @@ -127,7 +127,7 @@ def prepare_data(data, filters): if filters.get("group_by_mr"): # consolidated material request map for group by filter - if not row["material_request"] in material_request_map: + if row["material_request"] not in material_request_map: # create an entry with mr as key row_copy = copy.deepcopy(row) material_request_map[row["material_request"]] = row_copy diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 01ff28d8103..73b7d458303 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -126,7 +126,7 @@ def prepare_data(supplier_quotation_data, filters): # map for chart preparation of the form {'supplier1': {'qty': 'price'}} supplier = data.get("supplier_name") if filters.get("item_code"): - if not supplier in supplier_qty_price_map: + if supplier not in supplier_qty_price_map: supplier_qty_price_map[supplier] = {} supplier_qty_price_map[supplier][row["qty"]] = row["price"] @@ -169,7 +169,7 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): for supplier in suppliers: entry = supplier_qty_price_map[supplier] for qty in qty_list: - if not qty in data_points_map: + if qty not in data_points_map: data_points_map[qty] = [] if qty in entry: data_points_map[qty].append(entry[qty]) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 842e9e60164..bb16d8beceb 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -211,7 +211,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = meta.get_search_fields() columns = "" - extra_searchfields = [field for field in searchfields if not field in ["name", "description"]] + extra_searchfields = [field for field in searchfields if field not in ["name", "description"]] if extra_searchfields: columns += ", " + ", ".join(extra_searchfields) @@ -222,7 +222,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = searchfields + [ field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] + for field in [ + searchfield or "name", + "item_code", + "item_group", + "item_name", + ] if field not in searchfields ] searchfields = " or ".join(field + " like %(txt)s" for field in searchfields) @@ -862,7 +867,7 @@ def get_fields(doctype, fields=None): meta = frappe.get_meta(doctype) fields.extend(meta.get_search_fields()) - if meta.title_field and not meta.title_field.strip() in fields: + if meta.title_field and meta.title_field.strip() not in fields: fields.insert(1, meta.title_field.strip()) return unique(fields) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 07cad380fe2..1b2f0210cdd 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -642,7 +642,7 @@ class StockController(AccountsController): ) qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus") - if not qa_docstatus == 1: + if qa_docstatus != 1: link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection) msg = ( f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}" diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2f5da34674a..b155f4d65ff 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -34,7 +34,7 @@ class Appointment(Document): "Appointment", filters={"scheduled_time": self.scheduled_time} ) number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents") - if not number_of_agents == 0: + if number_of_agents != 0: if number_of_appointments_in_same_slot >= number_of_agents: frappe.throw(_("Time slot is not available")) # Link lead @@ -89,7 +89,7 @@ class Appointment(Document): cal_event.save(ignore_permissions=True) def set_verified(self, email): - if not email == self.customer_email: + if email != self.customer_email: frappe.throw(_("Email verification failed.")) # Create new lead self.create_lead_and_link() diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index f72b47250b6..5a66708837e 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -16,7 +16,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key="secret"): hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest() ) - if frappe.request.data and not sig == bytes(frappe.get_request_header(hmac_key).encode()): + if frappe.request.data and sig != bytes(frappe.get_request_header(hmac_key).encode()): frappe.throw(_("Unverified Webhook Data")) frappe.set_user(settings.modified_by) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 0135a4f9712..9d29fdb6037 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -64,7 +64,7 @@ def make_order(source_name): def update_item(source, target, source_parent): target_qty = source.get("qty") - source.get("ordered_qty") - target.qty = target_qty if not flt(target_qty) < 0 else 0 + target.qty = target_qty if flt(target_qty) >= 0 else 0 item = get_item_defaults(target.item_code, source_parent.company) if item: target.item_name = item.get("item_name") diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 6c0c92b6481..dca1f58ebd3 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1316,7 +1316,7 @@ def get_bom_diff(bom1, bom2): # check for deletions for d in old_value: - if not d.get(identifier) in new_row_by_identifier: + if d.get(identifier) not in new_row_by_identifier: out.removed.append([df.fieldname, d.as_dict()]) return out @@ -1332,13 +1332,18 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "item_name", "item_group", "description"] fields.extend( - [field for field in searchfields if not field in ["name", "item_group", "description"]] + [field for field in searchfields if field not in ["name", "item_group", "description"]] ) searchfields = searchfields + [ field - for field in [searchfield or "name", "item_code", "item_group", "item_name"] - if not field in searchfields + for field in [ + searchfield or "name", + "item_code", + "item_group", + "item_name", + ] + if field not in searchfields ] query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1b056c2dae8..6ceb1123879 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -740,7 +740,7 @@ class ProductionPlan(Document): key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) - if not key in material_request_map: + if key not in material_request_map: # make a new MR for the combination material_request_map[key] = frappe.new_doc("Material Request") material_request = material_request_map[key] diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 36a0cae5cca..dc3dd5c4ef7 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -856,7 +856,7 @@ class WorkOrder(Document): validate_end_of_life(self.production_item) def validate_qty(self): - if not self.qty > 0: + if self.qty <= 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) if ( @@ -883,7 +883,7 @@ class WorkOrder(Document): max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0) - if not max_qty > 0: + if max_qty <= 0: frappe.throw( _("Cannot produce more item for {0}").format(self.production_item), OverProductionError ) @@ -894,7 +894,7 @@ class WorkOrder(Document): ) def validate_transfer_against(self): - if not self.docstatus == 1: + if self.docstatus != 1: # let user configure operations until they're ready to submit return if not self.operations: @@ -907,7 +907,7 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: - if not d.time_in_mins > 0: + if d.time_in_mins <= 0: frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py index 1c6654e57ac..27810d7abe9 100644 --- a/erpnext/patches/v12_0/set_task_status.py +++ b/erpnext/patches/v12_0/set_task_status.py @@ -10,7 +10,7 @@ def execute(): ) if property_setter_name: property_setter = frappe.get_doc("Property Setter", property_setter_name) - if not "Completed" in property_setter.value: + if "Completed" not in property_setter.value: property_setter.value = property_setter.value + "\nCompleted" property_setter.save() diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py index 84c683acd2c..cf9e185a659 100644 --- a/erpnext/patches/v13_0/update_sla_enhancements.py +++ b/erpnext/patches/v13_0/update_sla_enhancements.py @@ -46,7 +46,7 @@ def execute(): {"response_time": response_time, "resolution_time": resolution_time}, ) if priority.parenttype == "Service Level": - if not priority.parent in priority_dict: + if priority.parent not in priority_dict: priority_dict[priority.parent] = [] priority_dict[priority.parent].append(priority) diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 18b2df9aa4c..8b3e91adc68 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -50,7 +50,7 @@ def create_customer_or_supplier(): party = frappe.new_doc(doctype) fullname = frappe.utils.get_fullname(user) - if not doctype == "Customer": + if doctype != "Customer": party.update( { "supplier_name": fullname, diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 8b3b6449a81..c922ab5b0ea 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -164,7 +164,7 @@ def prepare_data(data, so_elapsed_time, filters): if filters.get("group_by_so"): so_name = row["sales_order"] - if not so_name in sales_order_map: + if so_name not in sales_order_map: # create an entry row_copy = copy.deepcopy(row) sales_order_map[so_name] = row_copy diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py index dc503ff087b..c0237b72be4 100644 --- a/erpnext/setup/doctype/authorization_control/authorization_control.py +++ b/erpnext/setup/doctype/authorization_control/authorization_control.py @@ -176,7 +176,10 @@ class AuthorizationControl(TransactionBase): # Remove user specific rules from global authorization rules for r in based_on: - if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: + if r in final_based_on and r not in [ + "Itemwise Discount", + "Item Group wise Discount", + ]: final_based_on.remove(r) # Check for authorization set on particular roles @@ -204,7 +207,10 @@ class AuthorizationControl(TransactionBase): # Remove role specific rules from global authorization rules for r in based_on: - if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: + if r in final_based_on and r not in [ + "Itemwise Discount", + "Item Group wise Discount", + ]: final_based_on.remove(r) # Check for global authorization diff --git a/erpnext/setup/doctype/department/department.py b/erpnext/setup/doctype/department/department.py index 1745178574b..ba6042b378f 100644 --- a/erpnext/setup/doctype/department/department.py +++ b/erpnext/setup/doctype/department/department.py @@ -26,7 +26,7 @@ class Department(NestedSet): def before_rename(self, old, new, merge=False): # renaming consistency with abbreviation - if not frappe.get_cached_value("Company", self.company, "abbr") in new: + if frappe.get_cached_value("Company", self.company, "abbr") not in new: new = get_abbreviated_name(new, self.company) return new diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index b25dfc09356..cd24db7bfd3 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -645,7 +645,7 @@ class EmailDigest(Document): ] def get_root_type_accounts(self, root_type): - if not root_type in self._accounts: + if root_type not in self._accounts: self._accounts[root_type] = [ d.name for d in frappe.db.get_all( diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 1b094c9986d..5901d05a3b5 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -187,7 +187,7 @@ class Employee(NestedSet): throw(_("Please enter relieving date.")) def validate_for_enabled_user_id(self, enabled): - if not self.status == "Active": + if self.status != "Active": return if enabled is None: diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 96b03aef1e1..061fd62ef25 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -192,7 +192,7 @@ def add_standard_navbar_items(): for item in erpnext_navbar_items: current_labels = [item.get("item_label") for item in current_navbar_items] - if not item.get("item_label") in current_labels: + if item.get("item_label") not in current_labels: navbar_settings.append("help_dropdown", item) for item in current_navbar_items: diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py index 0a04210e0b0..56190f513b7 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py @@ -149,7 +149,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None): pending_qty -= qty_to_allocate rule["free_space"] -= stock_qty_to_allocate - if not pending_stock_qty > 0: + if pending_stock_qty <= 0: break # if pending qty after applying all rules, add row without warehouse diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8934f8e4ccd..9f1a5db995f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -151,7 +151,7 @@ class StockEntry(StockController): self.calculate_rate_and_amount() self.validate_putaway_capacity() - if not self.get("purpose") == "Manufacture": + if self.get("purpose") != "Manufacture": # ignore scrap item wh difference and empty source/target wh # in Manufacture Entry self.reset_default_field_value("from_warehouse", "items", "s_warehouse") diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index c1b205132cf..bc145040a27 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -174,7 +174,9 @@ class StockLedgerEntry(Document): if not self.serial_and_batch_bundle: self.throw_error_message(f"Serial No / Batch No are mandatory for Item {self.item_code}") - if self.serial_and_batch_bundle and not (item_detail.has_serial_no or item_detail.has_batch_no): + if ( + self.serial_and_batch_bundle and not item_detail.has_serial_no and not item_detail.has_batch_no + ): self.throw_error_message(f"Serial No and Batch No are not allowed for Item {self.item_code}") def throw_error_message(self, message, exception=frappe.ValidationError): diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 729ca9564e7..b0fb1b3796e 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -894,7 +894,7 @@ def create_stock_reservation_entries_for_so_items( continue # Stock should be reserved from the Pick List if has Picked Qty. - if not from_voucher_type == "Pick List" and flt(item.picked_qty) > 0: + if from_voucher_type != "Pick List" and flt(item.picked_qty) > 0: frappe.throw( _("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format( item.idx, frappe.bold(item.item_code) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9142a27f4c0..9203f4570ad 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1711,7 +1711,7 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return - if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): + if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation": return neg_sle = get_future_sle_with_negative_qty(args) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index e88530aab7d..a4a95bd1964 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -65,7 +65,7 @@ class ServiceLevelAgreement(Document): priorities.append(priority.priority) # Check if repeated priority - if not len(set(priorities)) == len(priorities): + if len(set(priorities)) != len(priorities): repeated_priority = get_repeated(priorities) frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority)) @@ -93,7 +93,7 @@ class ServiceLevelAgreement(Document): ) # Check for repeated workday - if not len(set(support_days)) == len(support_days): + if len(set(support_days)) != len(support_days): repeated_days = get_repeated(support_days) frappe.throw(_("Workday {0} has been repeated.").format(repeated_days)) @@ -716,13 +716,13 @@ def change_service_level_agreement_and_priority(self): and frappe.db.get_single_value("Support Settings", "track_service_level_agreement") ): - if not self.priority == frappe.db.get_value("Issue", self.name, "priority"): + if self.priority != frappe.db.get_value("Issue", self.name, "priority"): self.set_response_and_resolution_time( priority=self.priority, service_level_agreement=self.service_level_agreement ) frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority)) - if not self.service_level_agreement == frappe.db.get_value( + if self.service_level_agreement != frappe.db.get_value( "Issue", self.name, "service_level_agreement" ): self.set_response_and_resolution_time( -- GitLab From 876de662591a202eb71b7ac21d2365839e882e05 Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Thu, 7 Dec 2023 23:08:23 +0100 Subject: [PATCH 073/155] chore: git blame ignore rev --- .git-blame-ignore-revs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 1cc91fb5c9a..ec683df69ba 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -8,5 +8,27 @@ # # $ git config blame.ignoreRevsFile .git-blame-ignore-revs -# Reformat all files with Black and flake8 -fbfe3de6a2a90654aa1fdc9f4832b297363dc4bb +# Replace use of Class.extend with native JS class +1fe891b287a1b3f225d29ee3d07e7b1824aba9e7 + +# This commit just changes spaces to tabs for indentation in some files +5f473611bd6ed57703716244a054d3fb5ba9cd23 + +# Whitespace fix throughout codebase +4551d7d6029b6f587f6c99d4f8df5519241c6a86 +b147b85e6ac19a9220cd1e2958a6ebd99373283a + +# sort and cleanup imports +915b34391c2066dfc83e60a5813c5a877cebe7ac + +# removing six compatibility layer +8fe5feb6a4372bf5f2dfaf65fca41bbcc25c8ce7 + +# bulk format python code with black +494bd9ef78313436f0424b918f200dab8fc7c20b + +# bulk format python code with black +baec607ff5905b1c67531096a9cf50ec7ff00a5d + +# bulk refactor with sourcery +21a5111114e9d0f676bca4ff092c9b3456d5bf96 -- GitLab From f9f9ba31789e3b01575d92b74bec8bbd4159e971 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 17 Nov 2023 12:21:51 +0100 Subject: [PATCH 074/155] feat(web): Add "Event Card" web template to display list of events - Registration amount - Location Co-authored-by: Corentin Forler --- erpnext/venue/web_template/__init__.py | 0 .../venue/web_template/event_card/__init__.py | 0 .../web_template/event_card/event_card.html | 155 ++++++++++++++++++ .../web_template/event_card/event_card.json | 28 ++++ 4 files changed, 183 insertions(+) create mode 100644 erpnext/venue/web_template/__init__.py create mode 100644 erpnext/venue/web_template/event_card/__init__.py create mode 100644 erpnext/venue/web_template/event_card/event_card.html create mode 100644 erpnext/venue/web_template/event_card/event_card.json diff --git a/erpnext/venue/web_template/__init__.py b/erpnext/venue/web_template/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/venue/web_template/event_card/__init__.py b/erpnext/venue/web_template/event_card/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/venue/web_template/event_card/event_card.html b/erpnext/venue/web_template/event_card/event_card.html new file mode 100644 index 00000000000..4c719bdc041 --- /dev/null +++ b/erpnext/venue/web_template/event_card/event_card.html @@ -0,0 +1,155 @@ +{%- macro event_card(event, is_full_width=False, align="Left") -%} + {# {%- set align_events_class = resolve_class({ + 'align-events-end': align == 'Right', + 'align-events-center': align == 'Center', + 'align-events-start': align == 'Left', + }) -%} #} + {# {%- set col_size = 3 if is_full_width else 4 -%} #} + {%- set title = event.subject -%} + {%- set title = title[:50] + "..." if title|len > 50 else title -%} + {%- set image = event.image -%} + {%- set description = event.short_description-%} + + + {% if image %} +
+ {{ title }} +
+ {% else %} +
+ {{ frappe.utils.get_abbr(title) }} +
+ {% endif %} + + {{ event_card_body(title, description, event, align) }} +
+{%- endmacro -%} + + +{%- macro event_card_date(date) -%} + {% if date %} + {% set date = frappe.utils.get_datetime(date) %} + + {% endif %} +{%- endmacro -%} + +{%- macro event_card_body(title, description, event, align) -%} + {# {%- set align_class = resolve_class({ + 'text-right': align == 'Right', + 'text-center': align == 'Center', + 'text-left': align == 'Left', + }) -%} #} + {% if event.published_address %} + {% set address = frappe.get_doc("Address", event.published_address) %} + {% endif %} + +
+
+
+ {{ title or '' }} +
+
+ +
+ {% if event.repeat_this_event %} + + {% endif %} + + + + {{ event_card_date(event.starts_on) }} + +
+ + {% if address %} +
+ + {{ address | get_condensed_address }} +
+ {% endif %} + +
+ {% if event.registration_amount %} + + {{ frappe.format(event.registration_amount, df=event.meta.get("fields", {"fieldname": "registration_amount"})[0]) }} + {% else %} + {{ _("Free") }} + {% endif %} +
+ + {# #} +
+{%- endmacro -%} + + +{% set primary_action = "/events" %} +{% set primary_action_label = _("View List") %} + +
+
+
+ {%- if title -%} +

+ {{ title }} +

+ {%- endif -%} + {%- if subtitle -%} +

+ {{ subtitle }} +

+ {%- endif -%} +
+
+ {%- if primary_action -%} + + {{ primary_action_label }} + + {%- endif -%} +
+
+ +
+ {%- set event_list = frappe.get_all( + 'Event', + {'published': 1, 'event_type': 'Public'}, + order_by='starts_on asc', + limit=limit if limit else 24, + ) -%} + {%- for event in event_list: -%} + {%- set event = frappe.get_doc('Event', event) -%} + {{ event_card(event, is_full_width=True, align='Center') }} + {%- endfor -%} +
+ + {%- if primary_action and (event_list | length) >= 24 -%} + + {%- endif -%} +
diff --git a/erpnext/venue/web_template/event_card/event_card.json b/erpnext/venue/web_template/event_card/event_card.json new file mode 100644 index 00000000000..e7fbf27adda --- /dev/null +++ b/erpnext/venue/web_template/event_card/event_card.json @@ -0,0 +1,28 @@ +{ + "creation": "2023-12-07 17:29:26.161880", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "default": "16", + "fieldname": "limit", + "fieldtype": "Int", + "label": "Limit", + "reqd": 0 + } + ], + "modified": "2023-12-07 17:29:26.161880", + "modified_by": "Administrator", + "module": "Venue", + "name": "Event Card", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Section" +} -- GitLab From 35792689eb6a81367bd115d069181660deed6b3e Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Fri, 8 Dec 2023 00:32:31 +0100 Subject: [PATCH 075/155] feat(event): Add published_address custom field --- erpnext/venue/custom/event.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/venue/custom/event.json b/erpnext/venue/custom/event.json index 1e093f1f4e3..870197b9505 100644 --- a/erpnext/venue/custom/event.json +++ b/erpnext/venue/custom/event.json @@ -10,12 +10,24 @@ "label": "Maximum number of registrations", "non_negative": 1 }, + { + "depends_on": "eval:doc.published", + "dt": "Event", + "fieldname": "published_address", + "fieldtype": "Link", + "insert_after": "role", + "is_system_generated": 1, + "label": "Address", + "options": "Address", + "module": "Venue", + "name": "Event-published_address" + }, { "depends_on": "eval:doc.published", "dt": "Event", "fieldname": "registrations_section", "fieldtype": "Section Break", - "insert_after": "role", + "insert_after": "published_address", "is_system_generated": 1, "label": "Registrations", "module": "Venue", -- GitLab From e52a8f0fa6f05e85a9206e8f83437873b8559abf Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Fri, 8 Dec 2023 00:32:41 +0100 Subject: [PATCH 076/155] chore: fmt --- erpnext/venue/custom/event.json | 286 ++++++++++++++++---------------- 1 file changed, 143 insertions(+), 143 deletions(-) diff --git a/erpnext/venue/custom/event.json b/erpnext/venue/custom/event.json index 870197b9505..d136d2ec506 100644 --- a/erpnext/venue/custom/event.json +++ b/erpnext/venue/custom/event.json @@ -1,144 +1,144 @@ { - "custom_fields": [ - { - "description": "Leave this field empty or write 0 to not set a limit.", - "dt": "Event", - "fieldname": "max_number_of_registrations", - "fieldtype": "Int", - "insert_after": "status", - "is_system_generated": 1, - "label": "Maximum number of registrations", - "non_negative": 1 - }, - { - "depends_on": "eval:doc.published", - "dt": "Event", - "fieldname": "published_address", - "fieldtype": "Link", - "insert_after": "role", - "is_system_generated": 1, - "label": "Address", - "options": "Address", - "module": "Venue", - "name": "Event-published_address" - }, - { - "depends_on": "eval:doc.published", - "dt": "Event", - "fieldname": "registrations_section", - "fieldtype": "Section Break", - "insert_after": "published_address", - "is_system_generated": 1, - "label": "Registrations", - "module": "Venue", - "name": "Event-registrations_section" - }, - { - "default": "0", - "dt": "Event", - "fieldname": "allow_registrations", - "fieldtype": "Check", - "insert_after": "registrations_section", - "is_system_generated": 1, - "label": "Allow registrations", - "module": "Venue", - "name": "Event-allow_registrations" - }, - { - "default": "0", - "depends_on": "eval:doc.allow_registrations", - "description": "Allows for the registration of multiple people (e.g. family) using a single user account/email address.", - "dt": "Event", - "fieldname": "allow_multiple_registrations", - "fieldtype": "Check", - "hidden": 1, - "insert_after": "allow_registrations", - "is_system_generated": 1, - "label": "Allow users to register multiple times", - "module": "Venue", - "name": "Event-allow_multiple_registrations" - }, - { - "default": "0", - "depends_on": "eval:doc.allow_registrations", - "dt": "Event", - "fieldname": "allow_cancellations", - "fieldtype": "Check", - "insert_after": "allow_multiple_registrations", - "is_system_generated": 1, - "label": "Allow cancellations", - "module": "Venue", - "name": "Event-allow_cancellations" - }, - { - "depends_on": "eval:doc.allow_registrations", - "dt": "Event", - "fieldname": "registration_form", - "fieldtype": "Link", - "insert_after": "allow_cancellations", - "is_system_generated": 1, - "label": "Registration Form", - "module": "Venue", - "name": "Event-registration_form", - "options": "Web Form" - }, - { - "default": "0", - "depends_on": "eval:doc.registration_form&&doc.allow_registrations", - "dt": "Event", - "fieldname": "registration_amount", - "fieldtype": "Currency", - "insert_after": "registration_form", - "is_system_generated": 1, - "label": "Registration amount", - "non_negative": 1 - }, - { - "dt": "Event", - "fieldname": "column_break_38", - "fieldtype": "Column Break", - "insert_after": "amount", - "is_system_generated": 1, - "name": "Event-column_break_38" - }, - { - "default": null, - "depends_on": "eval:!doc.registration_form", - "dt": "Event", - "fieldname": "success_message", - "fieldtype": "HTML", - "insert_after": "column_break_38", - "is_system_generated": 1, - "label": "Registration success message" - } - ], - "custom_perms": [], - "doctype": "Event", - "links": [ - { - "group": "Registrations", - "is_child_table": 0, - "link_doctype": "Event Registration", - "link_fieldname": "event", - "name": "6b3d53940f", - "parent": "Event", - "parent_doctype": null, - "parentfield": "links", - "parenttype": "DocType", - "table_fieldname": null - } - ], - "property_setters": [ - { - "doc_type": "Event", - "doctype_or_field": "DocType", - "module": "Venue", - "name": "Event-main-links_order", - "property": "links_order", - "property_type": "Small Text", - "value": "[\"6b3d53940f\"]", - "is_system_generated": 1 - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "description": "Leave this field empty or write 0 to not set a limit.", + "dt": "Event", + "fieldname": "max_number_of_registrations", + "fieldtype": "Int", + "insert_after": "status", + "is_system_generated": 1, + "label": "Maximum number of registrations", + "non_negative": 1 + }, + { + "depends_on": "eval:doc.published", + "dt": "Event", + "fieldname": "published_address", + "fieldtype": "Link", + "insert_after": "role", + "is_system_generated": 1, + "label": "Address", + "options": "Address", + "module": "Venue", + "name": "Event-published_address" + }, + { + "depends_on": "eval:doc.published", + "dt": "Event", + "fieldname": "registrations_section", + "fieldtype": "Section Break", + "insert_after": "published_address", + "is_system_generated": 1, + "label": "Registrations", + "module": "Venue", + "name": "Event-registrations_section" + }, + { + "default": "0", + "dt": "Event", + "fieldname": "allow_registrations", + "fieldtype": "Check", + "insert_after": "registrations_section", + "is_system_generated": 1, + "label": "Allow registrations", + "module": "Venue", + "name": "Event-allow_registrations" + }, + { + "default": "0", + "depends_on": "eval:doc.allow_registrations", + "description": "Allows for the registration of multiple people (e.g. family) using a single user account/email address.", + "dt": "Event", + "fieldname": "allow_multiple_registrations", + "fieldtype": "Check", + "hidden": 1, + "insert_after": "allow_registrations", + "is_system_generated": 1, + "label": "Allow users to register multiple times", + "module": "Venue", + "name": "Event-allow_multiple_registrations" + }, + { + "default": "0", + "depends_on": "eval:doc.allow_registrations", + "dt": "Event", + "fieldname": "allow_cancellations", + "fieldtype": "Check", + "insert_after": "allow_multiple_registrations", + "is_system_generated": 1, + "label": "Allow cancellations", + "module": "Venue", + "name": "Event-allow_cancellations" + }, + { + "depends_on": "eval:doc.allow_registrations", + "dt": "Event", + "fieldname": "registration_form", + "fieldtype": "Link", + "insert_after": "allow_cancellations", + "is_system_generated": 1, + "label": "Registration Form", + "module": "Venue", + "name": "Event-registration_form", + "options": "Web Form" + }, + { + "default": "0", + "depends_on": "eval:doc.registration_form&&doc.allow_registrations", + "dt": "Event", + "fieldname": "registration_amount", + "fieldtype": "Currency", + "insert_after": "registration_form", + "is_system_generated": 1, + "label": "Registration amount", + "non_negative": 1 + }, + { + "dt": "Event", + "fieldname": "column_break_38", + "fieldtype": "Column Break", + "insert_after": "amount", + "is_system_generated": 1, + "name": "Event-column_break_38" + }, + { + "default": null, + "depends_on": "eval:!doc.registration_form", + "dt": "Event", + "fieldname": "success_message", + "fieldtype": "HTML", + "insert_after": "column_break_38", + "is_system_generated": 1, + "label": "Registration success message" + } + ], + "custom_perms": [], + "doctype": "Event", + "links": [ + { + "group": "Registrations", + "is_child_table": 0, + "link_doctype": "Event Registration", + "link_fieldname": "event", + "name": "6b3d53940f", + "parent": "Event", + "parent_doctype": null, + "parentfield": "links", + "parenttype": "DocType", + "table_fieldname": null + } + ], + "property_setters": [ + { + "doc_type": "Event", + "doctype_or_field": "DocType", + "module": "Venue", + "name": "Event-main-links_order", + "property": "links_order", + "property_type": "Small Text", + "value": "[\"6b3d53940f\"]", + "is_system_generated": 1 + } + ], + "sync_on_migrate": 1 +} -- GitLab From 4a202697e29147495dca5f7fcc55e12f48e8454f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 7 Dec 2023 10:16:46 +0530 Subject: [PATCH 077/155] fix(ux): don't update qty blindly --- erpnext/public/js/controllers/transaction.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f809f19cc2b..446997c55ef 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -494,7 +494,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", - child: item, args: { doc: me.frm.doc, args: { @@ -544,6 +543,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ + () => { + var child = locals[cdt][cdn]; + var std_field_list = ["doctype"] + .concat(frappe.model.std_fields_list) + .concat(frappe.model.child_table_field_list); + + for (var key in r.message) { + if (std_field_list.indexOf(key) === -1) { + if (key === "qty" && child[key]) continue; + child[key] = r.message[key]; + } + } + }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); -- GitLab From 2de6cd9438dde43235230fd4357dd2d1f8820298 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 8 Dec 2023 13:50:08 +0100 Subject: [PATCH 078/155] tests: Additionnal tests --- .../purchase_invoice/test_down_payment.py | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py b/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py index 981e87333db..397b10c9b95 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_down_payment.py @@ -1,8 +1,11 @@ +import unittest + import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, nowdate from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry +from erpnext.setup.utils import get_exchange_rate test_dependencies = ["Journal Entry", "Contact", "Address", "Account"] test_records = frappe.get_test_records("Purchase Invoice") @@ -203,3 +206,286 @@ class TestDownPayment(FrappeTestCase): not any(x.get("account") == "_Test Down Payment - _TC" for x in gl if x.is_cancelled == 0) ) self.assertTrue(any(x.get("account") == "Creditors - _TC" for x in gl if x.is_cancelled == 0)) + + +class TestDownPaymentMultiplePayments(FrappeTestCase): + def setUp(self): + frappe.db.set_value( + "Company", + "_Test Company", + "default_advance_paid_account", + "_Test Down Payment USD - _TC", + ) + + def get_accounting_params(self): + return { + "company": "_Test Company", + "cost_center": "_Test Cost Center - _TC", + "warehouse": "_Test Warehouse - _TC", + "currency": "USD", + "party_account_currency": "USD", + "price_list_currency": "USD", + "buying_price_list": "Standard Buying", + "conversion_rate": get_exchange_rate("USD", "INR"), + } + + def create_purchase_order(self, amount: float): + return frappe.get_doc( + { + "doctype": "Purchase Order", + "supplier": "_Test Supplier USD", + **self.get_accounting_params(), + "items": [ + { + **self.get_accounting_params(), + "item_code": "_Test Item", + "qty": 1, + "rate": amount, + "schedule_date": "2020-01-01", + } + ], + "transaction_date": "2020-01-01", + "schedule_date": "2020-01-01", + "taxes_and_charges": "", + } + ) + + def create_purchase_invoice(self, purchase_order: "frappe.Document"): + return frappe.get_doc( + { + "doctype": "Purchase Invoice", + "supplier": purchase_order.supplier, + **self.get_accounting_params(), + "items": [ + { + **self.get_accounting_params(), + **item.as_dict(), + "name": None, + "parent": None, + "parentfield": None, + "parenttype": None, + "idx": None, + "doctype": "Purchase Invoice Item", + "purchase_order": purchase_order.name, + } + for item in purchase_order.items + ], + "taxes_and_charges": "", + "is_down_payment_invoice": 0, + "disable_rounded_total": 0, + } + ) + + def create_down_payment_purchase_invoice( + self, purchase_order: "frappe.Document", percentage: float = 0.3 + ): + return frappe.get_doc( + { + "doctype": "Purchase Invoice", + "supplier": purchase_order.supplier, + **self.get_accounting_params(), + "items": [ + { + **self.get_accounting_params(), + "doctype": "Purchase Invoice Item", + "item_code": "999-Down Payment", + "is_down_payment_item": 1, + "qty": 1, + "rate": purchase_order.grand_total * percentage, + "delivery_date": "2020-01-01", + "purchase_order": purchase_order.name, + } + ], + "taxes_and_charges": "", + "is_down_payment_invoice": 1, + "disable_rounded_total": 0, + } + ) + + def create_payment_entry(self, voucher: "frappe.Document", amount: float): + return frappe.get_doc( + { + "doctype": "Payment Entry", + "company": voucher.company, + "payment_type": "Pay", + "party_type": "Supplier", + "party": voucher.supplier, + "paid_to": "_Test Payable USD - _TC", + "paid_from": "_Test Bank USD - _TC", + "paid_from_account_currency": "USD", + "paid_to_account_currency": "USD", + "paid_amount": amount, + "received_amount": amount, + "reference_no": "123", + "reference_date": "2020-01-01", + "references": [ + { + "reference_doctype": voucher.doctype, + "reference_name": voucher.name, + "allocated_amount": amount, + "due_date": "2020-01-01", + } + ], + } + ) + + def get_advance_for_payment_entry(self, pe: "frappe.Document"): + return { + "reference_name": pe.name, + "reference_type": "Payment Entry", + "advance_amount": pe.paid_amount, + "allocated_amount": pe.paid_amount, + "is_down_payment": 1, + "ref_exchange_rate": self.get_accounting_params()["conversion_rate"], + } + + def test_down_payment_full(self): + # Create a Sales Order + so = self.create_purchase_order(amount=10_000) + so.submit().reload() + + self.assertAlmostEqual(so.grand_total, 10_000) + self.assertAlmostEqual(so.total_taxes_and_charges, 0) + self.assertEqual(len(so.taxes), 0) + + # Create a down payment Sales Invoice against the Sales Order + dp_si = self.create_down_payment_purchase_invoice(purchase_order=so, percentage=0.3) + dp_si.submit().reload() + + self.assertAlmostEqual(dp_si.grand_total, 3_000) + self.assertAlmostEqual(dp_si.total_advance, 0) + self.assertAlmostEqual(dp_si.outstanding_amount, 3_000) + self.assertAlmostEqual(dp_si.total_taxes_and_charges, 0) + self.assertEqual(len(dp_si.taxes), 0) + + # Create a Payment Entry against the Sales Invoice + advances = [] + for v in [100, 900, 2000]: + pe = self.create_payment_entry(voucher=dp_si, amount=v) + pe.submit().reload() + advances.append(pe) + + # Create a draft Sales Invoice against the Sales Order, and add the payments as advances + si = self.create_purchase_invoice(purchase_order=so) + for dp in advances: + si.append("advances", self.get_advance_for_payment_entry(pe=dp)) + + si.save().reload() + + self.assertAlmostEqual(si.grand_total, 10_000) + self.assertAlmostEqual(si.total_advance, 3_000) + self.assertAlmostEqual(si.outstanding_amount, 7000) + + # Submit the Sales Invoice + si.submit().reload() + + self.assertAlmostEqual(si.grand_total, 10_000) + self.assertAlmostEqual(si.total_advance, 3_000) + self.assertAlmostEqual(si.outstanding_amount, 7_000) + self.assertEqual(si.status, "Partly Paid") + + dp_si.reload() + self.assertEqual(dp_si.status, "Paid") + + def test_down_payment_partial(self): + so = self.create_purchase_order(amount=10_000).submit() + + dp_si = self.create_down_payment_purchase_invoice(purchase_order=so, percentage=0.3) + dp_si.submit() + + advances = [] + for v in [10, 90]: + advances.append(self.create_payment_entry(voucher=dp_si, amount=v).submit()) + + si = self.create_purchase_invoice(purchase_order=so) + for dp in advances: + si.append("advances", self.get_advance_for_payment_entry(pe=dp)) + + si.save() + + self.assertAlmostEqual(si.grand_total, 10_000) + self.assertAlmostEqual(si.total_advance, 100) + self.assertAlmostEqual(si.outstanding_amount, 9_900) + + si.submit().reload() + + self.assertAlmostEqual(si.grand_total, 10_000) + self.assertAlmostEqual(si.total_advance, 100) + # The outstanding amount of the invoice is the grand total (10_000) minus the down payment (3000, 30% of 10_000), which is 7_000 + self.assertAlmostEqual(si.outstanding_amount, 7_000) + self.assertEqual(si.status, "Partly Paid") + + dp_si.reload() + self.assertEqual(dp_si.outstanding_amount, 3000 - 100) + self.assertEqual(dp_si.status, "Partly Paid") + + def test_down_payment_partial_two_decimal_places(self): + so = self.create_purchase_order(amount=10_000.50).submit() + dp_si = self.create_down_payment_purchase_invoice(purchase_order=so, percentage=0.3) + dp_si.submit() + + advances = [] + for v in [10, 90]: + advances.append(self.create_payment_entry(voucher=dp_si, amount=v).submit()) + + si = self.create_purchase_invoice(purchase_order=so) + for dp in advances: + si.append("advances", self.get_advance_for_payment_entry(pe=dp)) + + si.save() + + self.assertAlmostEqual(si.grand_total, 10_000.50) + self.assertAlmostEqual(si.total_advance, 100) + self.assertAlmostEqual(si.outstanding_amount, 9_900.50) + + si.submit().reload() + + self.assertAlmostEqual(si.grand_total, 10_000.50) + self.assertAlmostEqual(si.total_advance, 100) + # The outstanding amount of the invoice is the grand total (10_000.50) minus the down payment (3000.15, 30% of 10_000.50), which is 7_000.35 + self.assertAlmostEqual(si.outstanding_amount, 7_000.35) + self.assertEqual(si.status, "Partly Paid") + + dp_si.reload() + self.assertEqual(dp_si.outstanding_amount, 3000.15 - 100) + self.assertEqual(dp_si.status, "Partly Paid") + + @unittest.skip( + "Not working yet because rounding is not supported for outstanding amount: si.precision('outstanding_amount') is 2." + ) + def test_down_payment_not_rounded(self): + so = self.create_purchase_order(amount=10_000.410) + so.disable_rounded_total = 1 + so.submit() + + dp_si = self.create_down_payment_purchase_invoice(purchase_order=so, percentage=0.3) + dp_si.disable_rounded_total = 1 + dp_si.submit() + + advances = [] + for v in [10, 90]: + advances.append(self.create_payment_entry(voucher=dp_si, amount=v).submit()) + + si = self.create_purchase_invoice(purchase_order=so) + si.disable_rounded_total = 1 + for dp in advances: + si.append("advances", self.get_advance_for_payment_entry(pe=dp)) + + si.save() + + self.assertAlmostEqual(si.grand_total, 10_000.410) + self.assertAlmostEqual(si.total_advance, 100) + self.assertAlmostEqual(si.outstanding_amount, 9_900.410) + + si.submit().reload() + + self.assertAlmostEqual(si.grand_total, 10_000.410) + self.assertAlmostEqual(si.total_advance, 100) + # The outstanding amount of the invoice is the grand total (10_000.410) minus the down payment (3000.123, 30% of 10_000.410), which is 7_000.287 + self.assertAlmostEqual( + si.outstanding_amount, 7_000.287 + ) # TODO: This is failing because of rounding: got 7_000.29 + self.assertEqual(si.status, "Partly Paid") + + dp_si.reload() + self.assertEqual(dp_si.status, "Partly Paid") -- GitLab From c98b81b2955abb88aec51add84bffccc684d9779 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 8 Dec 2023 14:17:59 +0100 Subject: [PATCH 079/155] fix: Automatically set mode of payment from customer/supplier --- .../accounts/doctype/payment_request/payment_request.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 0b137d63b84..609dcf6d3e1 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -238,10 +238,18 @@ class PaymentRequest(Document): def set_customer(self): if customer := self.get_customer(): self.customer = customer + self.set_mode_of_payment("Customer", self.customer) def set_supplier(self): if supplier := self.get_supplier(): self.supplier = supplier + self.set_mode_of_payment("Supplier", self.supplier) + + def set_mode_of_payment(self, party_type, party): + if not self.mode_of_payment and ( + mop := frappe.db.get_value(party_type, party, "mode_of_payment") + ): + self.mode_of_payment = mop def get_customer(self): if frappe.db.has_column(self.reference_doctype, "customer"): -- GitLab From 02f2bb059c6137e32708e3094f12785fafb781c9 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 8 Dec 2023 14:24:08 +0100 Subject: [PATCH 080/155] fix: Set print format filter based on subscription type --- erpnext/accounts/doctype/subscription/subscription.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.js b/erpnext/accounts/doctype/subscription/subscription.js index 80c47baf3e1..ce7e407f171 100644 --- a/erpnext/accounts/doctype/subscription/subscription.js +++ b/erpnext/accounts/doctype/subscription/subscription.js @@ -24,7 +24,7 @@ frappe.ui.form.on('Subscription', { frm.set_query('print_format', function() { return { filters: { - "doc_type": "Sales Invoice", + "doc_type": frm.doc.generate_invoice_before_payment ? "Sales Invoice" : "Sales Order", "disabled": 0 } } -- GitLab From f8310803d646f695aba83d68571f7bc7dfcd3d49 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 8 Dec 2023 14:37:47 +0100 Subject: [PATCH 081/155] fix: fetch from_date and to_date from sales order --- erpnext/selling/doctype/sales_order/sales_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c64eacbb2db..fc0878dc2bc 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -995,6 +995,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): "field_map": { "party_account_currency": "party_account_currency", "payment_terms_template": "payment_terms_template", + "from_date": "from_date", + "to_date": "to_date", }, "field_no_map": ["payment_terms_template"], "validation": {"docstatus": ["=", 1]}, -- GitLab From afb5da786581e13c259430415ac5d35d104a8f28 Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Fri, 8 Dec 2023 16:06:46 +0100 Subject: [PATCH 082/155] fix: Set sort_order to DESC if sort_field is modified --- erpnext/accounts/doctype/account/account.json | 4 +- .../accounts_settings/accounts_settings.json | 4 +- erpnext/accounts/doctype/dunning/dunning.json | 4 +- .../mode_of_payment/mode_of_payment.json | 4 +- .../sales_taxes_and_charges.json | 56 +++++++++---------- .../sales_taxes_and_charges_template.json | 4 +- .../sepa_direct_debit/sepa_direct_debit.json | 4 +- .../doctype/shipping_rule/shipping_rule.json | 6 +- erpnext/buying/doctype/supplier/supplier.json | 4 +- .../communication_medium.json | 4 +- .../communication_medium_timeslot.json | 6 +- .../job_card_time_log/job_card_time_log.json | 6 +- .../production_plan/production_plan.json | 4 +- .../doctype/work_order/work_order.json | 4 +- .../doctype/workstation/workstation.json | 6 +- .../doctype/activity_type/activity_type.json | 6 +- .../projects/doctype/task_type/task_type.json | 46 +++++++-------- .../quality_meeting_agenda.json | 6 +- .../installation_note_item.json | 4 +- .../product_bundle/product_bundle.json | 4 +- erpnext/setup/doctype/company/company.json | 4 +- .../setup/doctype/department/department.json | 4 +- .../doctype/designation/designation.json | 6 +- .../doctype/sales_partner/sales_partner.json | 6 +- .../doctype/sales_person/sales_person.json | 6 +- .../supplier_group/supplier_group.json | 6 +- erpnext/setup/doctype/uom/uom.json | 4 +- erpnext/stock/doctype/bin/bin.json | 4 +- .../item_manufacturer/item_manufacturer.json | 16 +++--- .../stock/doctype/item_price/item_price.json | 4 +- .../stock/doctype/price_list/price_list.json | 6 +- .../quality_inspection.json | 4 +- .../stock_entry_detail.json | 4 +- .../stock_entry_type/stock_entry_type.json | 4 +- .../stock_settings/stock_settings.json | 4 +- .../warehouse_type/warehouse_type.json | 6 +- .../issue_priority/issue_priority.json | 6 +- 37 files changed, 140 insertions(+), 140 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 47b67400f34..417c248e532 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -201,7 +201,7 @@ "index_web_pages_for_search": 1, "is_tree": 1, "links": [], - "modified": "2023-07-20 18:18:44.405723", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Account", @@ -264,7 +264,7 @@ "search_fields": "account_number", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "title_field": "title", "states": [], "track_changes": 1 diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index c911a05cc32..138f4aea5ad 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -466,7 +466,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-20 09:37:47.650347", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -492,7 +492,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index 7fabe3a3a39..8222c353045 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -381,7 +381,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2023-06-15 15:46:53.865712", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning", @@ -431,7 +431,7 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "customer_name", "track_changes": 1 diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json index 2c13181f7e4..be424daa27b 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json @@ -125,7 +125,7 @@ "icon": "fa fa-credit-card", "idx": 1, "links": [], - "modified": "2023-11-15 18:59:37.796372", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Mode of Payment", @@ -188,6 +188,6 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 01364959fda..b4316e8639a 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -186,44 +186,44 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" -}, -{ - "default": "0", - "depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", - "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", - "fieldname": "included_in_paid_amount", - "fieldtype": "Check", - "label": "Considered In Paid Amount" -}, -{ - "default": "0", - "fieldname": "dont_recompute_tax", - "fieldtype": "Check", - "hidden": 1, - "label": "Dont Recompute tax", - "print_hide": 1, - "read_only": 1 -}, -{ - "fetch_from": "account_head.account_currency", - "fieldname": "account_currency", - "fieldtype": "Link", - "label": "Account Currency", - "options": "Currency", - "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", + "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry", + "fieldname": "included_in_paid_amount", + "fieldtype": "Check", + "label": "Considered In Paid Amount" + }, + { + "default": "0", + "fieldname": "dont_recompute_tax", + "fieldtype": "Check", + "hidden": 1, + "label": "Dont Recompute tax", + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "account_currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-10-17 13:08:17.776528", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json index 03d6766e205..f7cd827f6d4 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json @@ -80,7 +80,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-02-20 16:33:50.446725", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges Template", @@ -126,7 +126,7 @@ ], "show_title_field_in_link": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "title", "track_changes": 1 diff --git a/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.json b/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.json index 25e457a59ab..b011c5c87aa 100644 --- a/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.json +++ b/erpnext/accounts/doctype/sepa_direct_debit/sepa_direct_debit.json @@ -178,7 +178,7 @@ "icon": "fa fa-check", "is_submittable": 1, "links": [], - "modified": "2023-11-27 17:24:10.724417", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Sepa Direct Debit", @@ -222,7 +222,7 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.json b/erpnext/accounts/doctype/shipping_rule/shipping_rule.json index 034235355cb..2fb227e4a0b 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.json +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.json @@ -186,7 +186,7 @@ "icon": "fa fa-truck", "idx": 1, "links": [], - "modified": "2023-05-26 14:10:36.669437", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Shipping Rule", @@ -254,6 +254,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index f6e77c9974a..4094aa1961e 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -486,7 +486,7 @@ "link_fieldname": "party" } ], - "modified": "2023-11-17 09:41:48.149567", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", @@ -549,7 +549,7 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "supplier_name", "track_changes": 1 diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.json b/erpnext/communication/doctype/communication_medium/communication_medium.json index 92ffc7dac12..4bb8f1bfcab 100644 --- a/erpnext/communication/doctype/communication_medium/communication_medium.json +++ b/erpnext/communication/doctype/communication_medium/communication_medium.json @@ -63,7 +63,7 @@ "options": "" } ], - "modified": "2020-10-27 16:22:08.068542", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Communication", "name": "Communication Medium", @@ -83,6 +83,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 } diff --git a/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json index b278ca08f5c..e0f5f1b43d2 100644 --- a/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json +++ b/erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json @@ -44,13 +44,13 @@ } ], "istable": 1, - "modified": "2019-06-05 12:19:59.994979", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Communication", "name": "Communication Medium Timeslot", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json index a7102d7d237..1dae06febd8 100644 --- a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json @@ -64,7 +64,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-23 14:30:00.970916", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Time Log", @@ -72,6 +72,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index e2ec1fa2bef..0bb1043ba10 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -429,7 +429,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-11-03 14:08:11.928027", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", @@ -451,6 +451,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 437c99f14f5..583b0a323f1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -592,7 +592,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-08-11 18:35:49.852069", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", @@ -623,7 +623,7 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "production_item", "track_changes": 1, diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index 04595621500..da8673894b9 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -150,7 +150,7 @@ "icon": "uil uil-wrench", "idx": 1, "links": [], - "modified": "2023-02-24 16:58:10.000588", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", @@ -173,7 +173,7 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/activity_type/activity_type.json b/erpnext/projects/doctype/activity_type/activity_type.json index 41ee57e0f28..1eea1547a22 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.json +++ b/erpnext/projects/doctype/activity_type/activity_type.json @@ -47,7 +47,7 @@ "icon": "uil uil-stopwatch", "idx": 1, "links": [], - "modified": "2023-02-20 16:33:50.446725", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Projects", "name": "Activity Type", @@ -83,6 +83,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/task_type/task_type.json b/erpnext/projects/doctype/task_type/task_type.json index b29f9a48299..9261e285c79 100644 --- a/erpnext/projects/doctype/task_type/task_type.json +++ b/erpnext/projects/doctype/task_type/task_type.json @@ -21,7 +21,7 @@ } ], "links": [], - "modified": "2022-08-29 17:46:41.342979", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Projects", "name": "Task Type", @@ -39,32 +39,32 @@ "role": "System Manager", "share": 1, "write": 1 -}, -{ - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects Manager", - "share": 1, - "write": 1 -}, -{ - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1 } ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json b/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json index e53d18650c7..9f0f80bc479 100644 --- a/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json +++ b/erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json @@ -15,7 +15,7 @@ } ], "istable": 1, - "modified": "2019-05-26 20:49:01.328146", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Meeting Agenda", @@ -23,6 +23,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/selling/doctype/installation_note_item/installation_note_item.json b/erpnext/selling/doctype/installation_note_item/installation_note_item.json index 75cf7c09cdf..e76075a9324 100644 --- a/erpnext/selling/doctype/installation_note_item/installation_note_item.json +++ b/erpnext/selling/doctype/installation_note_item/installation_note_item.json @@ -112,7 +112,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-03-12 13:47:08.257955", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Selling", "name": "Installation Note Item", @@ -120,7 +120,7 @@ "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json index b6cc703e0e9..84fb87529aa 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.json +++ b/erpnext/selling/doctype/product_bundle/product_bundle.json @@ -77,7 +77,7 @@ "icon": "fa fa-sitemap", "idx": 1, "links": [], - "modified": "2023-11-22 15:20:46.805114", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Selling", "name": "Product Bundle", @@ -114,6 +114,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 1573a9110ef..e94642db2b9 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -778,7 +778,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-10-23 10:19:24.322898", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -838,7 +838,7 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/setup/doctype/department/department.json b/erpnext/setup/doctype/department/department.json index 542395d1baf..ab61614deab 100644 --- a/erpnext/setup/doctype/department/department.json +++ b/erpnext/setup/doctype/department/department.json @@ -90,7 +90,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2023-08-28 17:26:46.826501", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Department", @@ -125,6 +125,6 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/setup/doctype/designation/designation.json b/erpnext/setup/doctype/designation/designation.json index a5b2ac9128a..1f44c5c6376 100644 --- a/erpnext/setup/doctype/designation/designation.json +++ b/erpnext/setup/doctype/designation/designation.json @@ -31,7 +31,7 @@ "icon": "fa fa-bookmark", "idx": 1, "links": [], - "modified": "2023-02-10 01:53:41.319386", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Designation", @@ -57,7 +57,7 @@ "quick_entry": 1, "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "translated_doctype": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.json b/erpnext/setup/doctype/sales_partner/sales_partner.json index cc01705b795..adab4f65806 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.json +++ b/erpnext/setup/doctype/sales_partner/sales_partner.json @@ -192,7 +192,7 @@ "icon": "fa fa-user", "idx": 1, "links": [], - "modified": "2022-08-03 15:48:32.571826", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Sales Partner", @@ -225,6 +225,6 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/sales_person/sales_person.json b/erpnext/setup/doctype/sales_person/sales_person.json index 80a5b425759..aa4912550b3 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.json +++ b/erpnext/setup/doctype/sales_person/sales_person.json @@ -145,7 +145,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2023-02-24 16:58:05.718649", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Sales Person", @@ -183,6 +183,6 @@ "show_name_in_global_search": 1, "show_preview_popup": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.json b/erpnext/setup/doctype/supplier_group/supplier_group.json index e681fdbc4d9..48207af90ed 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.json +++ b/erpnext/setup/doctype/supplier_group/supplier_group.json @@ -122,7 +122,7 @@ "index_web_pages_for_search": 1, "is_tree": 1, "links": [], - "modified": "2023-02-21 18:25:16.243227", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "Supplier Group", @@ -191,6 +191,6 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/uom/uom.json b/erpnext/setup/doctype/uom/uom.json index 330b9944e99..281edd65534 100644 --- a/erpnext/setup/doctype/uom/uom.json +++ b/erpnext/setup/doctype/uom/uom.json @@ -40,7 +40,7 @@ "icon": "fa fa-compass", "idx": 1, "links": [], - "modified": "2022-07-25 14:47:10.251003", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Setup", "name": "UOM", @@ -85,7 +85,7 @@ "quick_entry": 1, "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "translated_doctype": 1 } diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index ff7256aa55d..e1d847a5ed8 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -187,7 +187,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-11-01 16:51:17.079107", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Bin", @@ -219,6 +219,6 @@ "quick_entry": 1, "search_fields": "item_code,warehouse", "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json index 2de6d4d4f47..28c54b45d32 100644 --- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json +++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json @@ -55,14 +55,14 @@ "read_only": 1 }, { - "default": "0", - "fieldname": "is_default", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Default" + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Default" } ], - "modified": "2020-04-16 19:07:31.175919", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Item Manufacturer", @@ -107,7 +107,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "title_field": "item_code", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index c775958d65d..d50d7d74645 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -223,7 +223,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-01-19 08:26:04.041861", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", @@ -259,7 +259,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "title_field": "item_name", "track_changes": 1 diff --git a/erpnext/stock/doctype/price_list/price_list.json b/erpnext/stock/doctype/price_list/price_list.json index 20e399bb390..2a74c77cc2c 100644 --- a/erpnext/stock/doctype/price_list/price_list.json +++ b/erpnext/stock/doctype/price_list/price_list.json @@ -83,7 +83,7 @@ "icon": "fa fa-tags", "idx": 1, "links": [], - "modified": "2022-08-03 15:48:31.064198", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Price List", @@ -127,6 +127,6 @@ "search_fields": "currency", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index bedd44e4d97..7eb8c3fe2f3 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -245,7 +245,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-23 11:56:50.282878", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", @@ -270,6 +270,6 @@ "search_fields": "item_code, report_date, reference_name", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 591aa0d4dbf..4db07f3acf0 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -589,7 +589,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-09 12:41:18.210864", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", @@ -597,6 +597,6 @@ "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [] } diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json index 57d3dba4b1d..cee12a7e683 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json @@ -29,7 +29,7 @@ } ], "links": [], - "modified": "2022-07-10 16:52:17.517739", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Type", @@ -87,7 +87,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1, "translated_doctype": 1 diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 1c5cccc9031..3141b366769 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -427,7 +427,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-19 12:35:30.068799", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -449,7 +449,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "states": [], "track_changes": 1 } diff --git a/erpnext/stock/doctype/warehouse_type/warehouse_type.json b/erpnext/stock/doctype/warehouse_type/warehouse_type.json index 9993bfd93f2..b5661049bc3 100644 --- a/erpnext/stock/doctype/warehouse_type/warehouse_type.json +++ b/erpnext/stock/doctype/warehouse_type/warehouse_type.json @@ -13,7 +13,7 @@ "label": "Description" } ], - "modified": "2019-05-27 18:35:33.354571", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse Type", @@ -103,6 +103,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/support/doctype/issue_priority/issue_priority.json b/erpnext/support/doctype/issue_priority/issue_priority.json index 38a187b56ff..76236208292 100644 --- a/erpnext/support/doctype/issue_priority/issue_priority.json +++ b/erpnext/support/doctype/issue_priority/issue_priority.json @@ -13,7 +13,7 @@ "label": "Description" } ], - "modified": "2019-05-20 17:06:38.095647", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Support", "name": "Issue Priority", @@ -34,6 +34,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} -- GitLab From 17d04c449b57d8022fe2b10b75f3bb341b1041a0 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 8 Dec 2023 18:00:13 +0100 Subject: [PATCH 083/155] feat: default calendar and resource for projects --- .../projects/doctype/project/project_calendar.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 erpnext/projects/doctype/project/project_calendar.js diff --git a/erpnext/projects/doctype/project/project_calendar.js b/erpnext/projects/doctype/project/project_calendar.js new file mode 100644 index 00000000000..ce669acc238 --- /dev/null +++ b/erpnext/projects/doctype/project/project_calendar.js @@ -0,0 +1,16 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.views.calendar["Project"] = { + field_map: { + "start": "expected_start_date", + "end": "expected_end_date", + "id": "name", + "title": "project_name", + "allDay": "allDay", + "progress": "progress" + }, + gantt: true, + get_events_method: "frappe.desk.calendar.get_events", + default_resource: "customer" +} -- GitLab From e9bfcca827d9d5a40639c80aa0776cfd88b8f066 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 8 Dec 2023 18:54:00 +0100 Subject: [PATCH 084/155] fix: Hide is_down_payment_item if not set --- .../sales_invoice_item.json | 3 +- .../sales_invoice_item/sales_invoice_item.py | 89 ++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) 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 e3165c919eb..7e8c79b7b3e 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -819,6 +819,7 @@ }, { "default": "0", + "depends_on": "eval:doc.is_down_payment_item", "fieldname": "is_down_payment_item", "fieldtype": "Check", "label": "Is Down Payment Item", @@ -927,7 +928,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-14 18:34:10.479329", + "modified": "2023-12-08 18:52:40.530527", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py index a75324c0449..061d24ddd7c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py @@ -2,9 +2,96 @@ # License: GNU General Public License v3. See license.txt -import frappe from frappe.model.document import Document class SalesInvoiceItem(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + actual_batch_qty: DF.Float + actual_qty: DF.Float + allow_zero_valuation_rate: DF.Check + amount: DF.Currency + asset: DF.Link | None + barcode: DF.Data | None + base_amount: DF.Currency + base_net_amount: DF.Currency + base_net_rate: DF.Currency + base_price_list_rate: DF.Currency + base_rate: DF.Currency + base_rate_with_margin: DF.Currency + batch_no: DF.Link | None + brand: DF.Data | None + conversion_factor: DF.Float + cost_center: DF.Link + customer_item_code: DF.Data | None + deferred_revenue_account: DF.Link | None + delivered_by_supplier: DF.Check + delivered_qty: DF.Float + delivery_note: DF.Link | None + description: DF.TextEditor | None + discount_account: DF.Link | None + discount_amount: DF.Currency + discount_percentage: DF.Percent + dn_detail: DF.Data | None + down_payment_rate: DF.Percent + enable_deferred_revenue: DF.Check + event_registration: DF.Link | None + expense_account: DF.Link | None + finance_book: DF.Link | None + grant_commission: DF.Check + has_item_scanned: DF.Check + image: DF.Attach | None + income_account: DF.Link + incoming_rate: DF.Currency + is_down_payment_item: DF.Check + is_fixed_asset: DF.Check + is_free_item: DF.Check + item_code: DF.Link | None + item_group: DF.Link | None + item_name: DF.Data + item_tax_rate: DF.SmallText | None + item_tax_template: DF.Link | None + margin_rate_or_amount: DF.Float + margin_type: DF.Literal["", "Percentage", "Amount"] + net_amount: DF.Currency + net_rate: DF.Currency + page_break: DF.Check + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + price_list_rate: DF.Currency + pricing_rules: DF.SmallText | None + project: DF.Link | None + purchase_order: DF.Link | None + purchase_order_item: DF.Data | None + qty: DF.Float + quality_inspection: DF.Link | None + rate: DF.Currency + rate_with_margin: DF.Currency + sales_invoice_item: DF.Data | None + sales_order: DF.Link | None + serial_and_batch_bundle: DF.Link | None + serial_no: DF.SmallText | None + service_end_date: DF.Date | None + service_start_date: DF.Date | None + service_stop_date: DF.Date | None + so_detail: DF.Data | None + stock_qty: DF.Float + stock_uom: DF.Link | None + stock_uom_rate: DF.Currency + target_warehouse: DF.Link | None + total_weight: DF.Float + uom: DF.Link + warehouse: DF.Link | None + weight_per_unit: DF.Float + weight_uom: DF.Link | None + # end: auto-generated types + pass -- GitLab From 84260a6439cccec596f9503b237ee41110c97ef3 Mon Sep 17 00:00:00 2001 From: creative-paramu Date: Fri, 8 Dec 2023 11:05:09 +0530 Subject: [PATCH 085/155] fix(ux): `Shipping Address Link` --- erpnext/public/js/controllers/buying.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 348e7e22c82..71f2d622a4f 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -36,14 +36,14 @@ erpnext.buying = { // no idea where me is coming from if(this.frm.get_field('shipping_address')) { - this.frm.set_query("shipping_address", function() { - if(me.frm.doc.customer) { + this.frm.set_query("shipping_address", () => { + if(this.frm.doc.customer) { return { query: 'frappe.contacts.doctype.address.address.address_query', - filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } + filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer } }; } else - return erpnext.queries.company_address_query(me.frm.doc) + return erpnext.queries.company_address_query(this.frm.doc) }); } } -- GitLab From 96381cc6a79111ddd6af5dc9b2974a6255b99dd7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 10 Dec 2023 11:05:58 +0530 Subject: [PATCH 086/155] fix: auto delete draft serial and batch bundle (#38637) --- erpnext/controllers/accounts_controller.py | 24 +++++++++ .../test_serial_and_batch_bundle.py | 52 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2ed5366c8fd..64bb4dbe6c7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -287,6 +287,30 @@ class AccountsController(TransactionBase): def on_trash(self): self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() + self.remove_serial_and_batch_bundle() + + # delete sl and gl entries on deletion of transaction + if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): + ple = frappe.qb.DocType("Payment Ledger Entry") + frappe.qb.from_(ple).delete().where( + (ple.voucher_type == self.doctype) & (ple.voucher_no == self.name) + ).run() + frappe.db.sql( + "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name) + ) + frappe.db.sql( + "delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", + (self.doctype, self.name), + ) + + def remove_serial_and_batch_bundle(self): + bundles = frappe.get_all( + "Serial and Batch Bundle", + filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)}, + ) + + for bundle in bundles: + frappe.delete_doc("Serial and Batch Bundle", bundle.name) def validate_deferred_income_expense_account(self): field_map = { diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index b8b9d06a86d..c49b5cd52a2 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -367,6 +367,58 @@ class TestSerialandBatchBundle(FrappeTestCase): # Batch does not belong to serial no self.assertRaises(frappe.exceptions.ValidationError, doc.save) + def test_auto_delete_draft_serial_and_batch_bundle(self): + serial_and_batch_code = "New Serial No Auto Delete 1" + make_item( + serial_and_batch_code, + { + "has_serial_no": 1, + "serial_no_series": "TEST-SER-VALL-.#####", + "is_stock_item": 1, + }, + ) + + ste = make_stock_entry( + item_code=serial_and_batch_code, + target="_Test Warehouse - _TC", + qty=1, + rate=500, + do_not_submit=True, + ) + + serial_no = "SN-TEST-AUTO-DEL" + if not frappe.db.exists("Serial No", serial_no): + frappe.get_doc( + { + "doctype": "Serial No", + "serial_no": serial_no, + "item_code": serial_and_batch_code, + "company": "_Test Company", + } + ).insert(ignore_permissions=True) + + bundle_doc = make_serial_batch_bundle( + { + "item_code": serial_and_batch_code, + "warehouse": "_Test Warehouse - _TC", + "voucher_type": "Stock Entry", + "posting_date": ste.posting_date, + "posting_time": ste.posting_time, + "qty": 1, + "serial_nos": [serial_no], + "type_of_transaction": "Inward", + "do_not_submit": True, + } + ) + + bundle_doc.reload() + ste.items[0].serial_and_batch_bundle = bundle_doc.name + ste.save() + ste.reload() + + ste.delete() + self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name)) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos -- GitLab From c0a21c75d4cf47d859aacf5c9a29a60f79a0822b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 11 Dec 2023 10:35:31 +0530 Subject: [PATCH 087/155] fix: not able to make serial and batch using csv import (#38659) --- .../serial_and_batch_bundle.js | 2 +- .../serial_and_batch_bundle.py | 36 +++++++++++++++++-- .../serial_and_batch_entry.json | 4 +-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index cda444510a8..9f01ee9ae62 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -121,7 +121,7 @@ frappe.ui.form.on('Serial and Batch Bundle', { frappe.throw(__("Please attach CSV file")); } - if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) { + if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) { frappe.throw(__("Please enter serial nos")); } }, diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 2d32eb346b1..e76e79ff8e9 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 @@ -475,6 +475,22 @@ class SerialandBatchBundle(Document): serial_batches = {} for row in self.entries: + if self.has_serial_no and not row.serial_no: + frappe.throw( + _("At row {0}: Serial No is mandatory for Item {1}").format( + bold(row.idx), bold(self.item_code) + ), + title=_("Serial No is mandatory"), + ) + + if self.has_batch_no and not row.batch_no: + frappe.throw( + _("At row {0}: Batch No is mandatory for Item {1}").format( + bold(row.idx), bold(self.item_code) + ), + title=_("Batch No is mandatory"), + ) + if row.serial_no: serial_nos.append(row.serial_no) @@ -758,6 +774,9 @@ def parse_csv_file_to_get_serial_batch(reader): if index == 0: has_serial_no = row[0] == "Serial No" has_batch_no = row[0] == "Batch No" + if not has_batch_no: + has_batch_no = row[1] == "Batch No" + continue if not row[0]: @@ -774,6 +793,13 @@ def parse_csv_file_to_get_serial_batch(reader): } ) + batch_nos.append( + { + "batch_no": row[1], + "qty": row[2], + } + ) + serial_nos.append(_dict) elif has_batch_no: batch_nos.append( @@ -809,6 +835,9 @@ def make_serial_nos(item_code, serial_nos): serial_nos_details = [] user = frappe.session.user for serial_no in serial_nos: + if frappe.db.exists("Serial No", serial_no): + continue + serial_nos_details.append( ( serial_no, @@ -839,7 +868,7 @@ def make_serial_nos(item_code, serial_nos): frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details)) - frappe.msgprint(_("Serial Nos are created successfully")) + frappe.msgprint(_("Serial Nos are created successfully"), alert=True) def make_batch_nos(item_code, batch_nos): @@ -850,6 +879,9 @@ def make_batch_nos(item_code, batch_nos): batch_nos_details = [] user = frappe.session.user for batch_no in batch_nos: + if frappe.db.exists("Batch", batch_no): + continue + batch_nos_details.append( (batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description) ) @@ -868,7 +900,7 @@ def make_batch_nos(item_code, batch_nos): frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details)) - frappe.msgprint(_("Batch Nos are created successfully")) + frappe.msgprint(_("Batch Nos are created successfully"), alert=True) def parse_serial_nos(data): diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json index a6d192b77ea..c2bc636a174 100644 --- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json +++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json @@ -27,7 +27,6 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Serial No", - "mandatory_depends_on": "eval:parent.has_serial_no == 1", "options": "Serial No", "search_index": 1 }, @@ -38,7 +37,6 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Batch No", - "mandatory_depends_on": "eval:parent.has_batch_no == 1", "options": "Batch", "search_index": 1 }, @@ -122,7 +120,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-07-03 15:29:50.199075", + "modified": "2023-12-10 19:47:48.227772", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Entry", -- GitLab From e22982c13c75e638229d2bcd897e52c1a890ecf4 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 09:14:03 +0100 Subject: [PATCH 088/155] fix: merge conflict --- erpnext/controllers/accounts_controller.py | 14 -------------- .../test_serial_and_batch_bundle.py | 1 + 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 64bb4dbe6c7..b6976e660d3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -289,20 +289,6 @@ class AccountsController(TransactionBase): self._remove_references_in_unreconcile() self.remove_serial_and_batch_bundle() - # delete sl and gl entries on deletion of transaction - if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): - ple = frappe.qb.DocType("Payment Ledger Entry") - frappe.qb.from_(ple).delete().where( - (ple.voucher_type == self.doctype) & (ple.voucher_no == self.name) - ).run() - frappe.db.sql( - "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name) - ) - frappe.db.sql( - "delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", - (self.doctype, self.name), - ) - def remove_serial_and_batch_bundle(self): bundles = frappe.get_all( "Serial and Batch Bundle", diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index c49b5cd52a2..9e06c50e34f 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -8,6 +8,7 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, nowtime, today from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry class TestSerialandBatchBundle(FrappeTestCase): -- GitLab From 7a79d14f7ac32100cdf7c946303470b25eb487ca Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 10:37:27 +0100 Subject: [PATCH 089/155] feat: Party auto match in bank transactions --- .../accounts_settings/accounts_settings.json | 30 ++- .../accounts_settings/accounts_settings.py | 48 +++++ .../bank_transaction/auto_match_party.py | 183 ++++++++++++++++++ .../bank_transaction/bank_transaction.js | 21 +- .../bank_transaction/bank_transaction.json | 76 +++++++- .../bank_transaction/bank_transaction.py | 94 +++++++-- .../bank_transaction/test_auto_match_party.py | 151 +++++++++++++++ .../doctype/plaid_settings/plaid_settings.py | 6 +- erpnext/setup/doctype/employee/employee.json | 14 +- pyproject.toml | 1 + 10 files changed, 590 insertions(+), 34 deletions(-) create mode 100644 erpnext/accounts/doctype/bank_transaction/auto_match_party.py create mode 100644 erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 138f4aea5ad..3fa149ce7ca 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -67,6 +67,9 @@ "frozen_accounts_modifier", "tab_break_dpet", "show_balance_in_coa", + "banking_tab", + "enable_party_matching", + "enable_fuzzy_matching", "reports_tab", "remarks_section", "general_ledger_remarks_length", @@ -411,17 +414,30 @@ "fieldtype": "Check", "label": "Show Taxes as Table in Print" }, + { + "fieldname": "banking_tab", + "fieldtype": "Tab Break", + "label": "Banking" + }, { "default": "0", - "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", - "fieldname": "ignore_account_closing_balance", + "description": "Auto match and set the Party in Bank Transactions", + "fieldname": "enable_party_matching", "fieldtype": "Check", - "label": "Ignore Account Closing Balance" + "label": "Enable Automatic Party Matching" }, { "default": "0", - "description": "Tax Amount will be rounded on a row(items) level", - "fieldname": "round_row_wise_tax", + "depends_on": "enable_party_matching", + "description": "Approximately match the description/party name against parties", + "fieldname": "enable_fuzzy_matching", + "fieldtype": "Check", + "label": "Enable Fuzzy Matching" + }, + { + "default": "0", + "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", + "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", "label": "Ignore Account Closing Balance" }, @@ -430,7 +446,7 @@ "description": "Tax Amount will be rounded on a row(items) level", "fieldname": "round_row_wise_tax", "fieldtype": "Check", - "label": "Round Tax Amount Row-wise" + "label": "Ignore Account Closing Balance" }, { "fieldname": "reports_tab", @@ -466,7 +482,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-12-08 16:03:01.935070", + "modified": "2023-12-11 10:22:18.348446", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ac3d44bb5e7..4048be10f61 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,6 +14,54 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + acc_frozen_upto: DF.Date | None + add_taxes_from_item_tax_template: DF.Check + allow_multi_currency_invoices_against_single_party_account: DF.Check + allow_stale: DF.Check + auto_reconcile_payments: DF.Check + automatically_fetch_payment_terms: DF.Check + automatically_process_deferred_accounting_entry: DF.Check + book_asset_depreciation_entry_automatically: DF.Check + book_deferred_entries_based_on: DF.Literal["Days", "Months"] + book_deferred_entries_via_journal_entry: DF.Check + book_tax_discount_loss: DF.Check + check_supplier_invoice_uniqueness: DF.Check + credit_controller: DF.Link | None + default_payment_days: DF.Int + determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] + enable_common_party_accounting: DF.Check + enable_fuzzy_matching: DF.Check + enable_party_matching: DF.Check + frozen_accounts_modifier: DF.Link | None + general_ledger_remarks_length: DF.Int + ignore_account_closing_balance: DF.Check + make_payment_via_journal_entry: DF.Check + mandatory_accounting_journal: DF.Check + merge_similar_account_heads: DF.Check + over_billing_allowance: DF.Currency + post_change_gl_entries: DF.Check + receivable_payable_remarks_length: DF.Int + role_allowed_to_over_bill: DF.Link | None + round_row_wise_tax: DF.Check + show_balance_in_coa: DF.Check + show_inclusive_tax_in_print: DF.Check + show_payment_schedule_in_print: DF.Check + show_taxes_as_table_in_print: DF.Check + stale_days: DF.Int + submit_journal_entries: DF.Check + unlink_advance_payment_on_cancelation_of_order: DF.Check + unlink_payment_on_cancellation_of_invoice: DF.Check + validate_posting_date_chronology_in_sales_invoices: DF.Check + # end: auto-generated types + def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py new file mode 100644 index 00000000000..04dab4c28a0 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -0,0 +1,183 @@ +from typing import Tuple, Union + +import frappe +from frappe.utils import flt +from rapidfuzz import fuzz, process + + +class AutoMatchParty: + """ + Matches by Account/IBAN and then by Party Name/Description sequentially. + Returns when a result is obtained. + + Result (if present) is of the form: (Party Type, Party,) + """ + + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self) -> Union[Tuple, None]: + result = None + result = AutoMatchbyAccountIBAN( + bank_party_account_number=self.bank_party_account_number, + bank_party_iban=self.bank_party_iban, + deposit=self.deposit, + ).match() + + fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching") + if not result and fuzzy_matching_enabled: + result = AutoMatchbyPartyNameDescription( + bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit + ).match() + + return result + + +class AutoMatchbyAccountIBAN: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self): + if not (self.bank_party_account_number or self.bank_party_iban): + return None + + result = self.match_account_in_party() + return result + + def match_account_in_party(self) -> Union[Tuple, None]: + """Check if there is a IBAN/Account No. match in Customer/Supplier/Employee""" + result = None + parties = get_parties_in_order(self.deposit) + or_filters = self.get_or_filters() + + for party in parties: + party_result = frappe.db.get_all( + "Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1 + ) + + if party == "Employee" and not party_result: + # Search in Bank Accounts first for Employee, and then Employee record + if "bank_account_no" in or_filters: + or_filters["bank_ac_no"] = or_filters.pop("bank_account_no") + + party_result = frappe.db.get_all( + party, or_filters=or_filters, pluck="name", limit_page_length=1 + ) + + if party_result: + result = ( + party, + party_result[0], + ) + break + + return result + + def get_or_filters(self) -> dict: + or_filters = {} + if self.bank_party_account_number: + or_filters["bank_account_no"] = self.bank_party_account_number + + if self.bank_party_iban: + or_filters["iban"] = self.bank_party_iban + + return or_filters + + +class AutoMatchbyPartyNameDescription: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self) -> Union[Tuple, None]: + # fuzzy search by customer/supplier & employee + if not (self.bank_party_name or self.description): + return None + + result = self.match_party_name_desc_in_party() + return result + + def match_party_name_desc_in_party(self) -> Union[Tuple, None]: + """Fuzzy search party name and/or description against parties in the system""" + result = None + parties = get_parties_in_order(self.deposit) + + for party in parties: + filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} + field = party.lower() + "_name" + names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"]) + + for field in ["bank_party_name", "description"]: + if not self.get(field): + continue + + result, skip = self.fuzzy_search_and_return_result(party, names, field) + if result or skip: + break + + if result or skip: + # Skip If: It was hard to distinguish between close matches and so match is None + # OR if the right match was found + break + + return result + + def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]: + skip = False + result = process.extract( + query=self.get(field), + choices={row.get("name"): row.get("party_name") for row in names}, + scorer=fuzz.token_set_ratio, + ) + party_name, skip = self.process_fuzzy_result(result) + + if not party_name: + return None, skip + + return ( + party, + party_name, + ), skip + + def process_fuzzy_result(self, result: Union[list, None]): + """ + If there are multiple valid close matches return None as result may be faulty. + Return the result only if one accurate match stands out. + + Returns: Result, Skip (whether or not to discontinue matching) + """ + SCORE, PARTY_ID, CUTOFF = 1, 2, 80 + + if not result or not len(result): + return None, False + + first_result = result[0] + if len(result) == 1: + return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True + + second_result = result[1] + if first_result[SCORE] > CUTOFF: + # If multiple matches with the same score, return None but discontinue matching + # Matches were found but were too close to distinguish between + if first_result[SCORE] == second_result[SCORE]: + return None, True + + return first_result[PARTY_ID], True + else: + return None, False + + +def get_parties_in_order(deposit: float) -> list: + parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive + if flt(deposit) > 0: + parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay + + return parties diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 6e504668d8a..4c1052a69ab 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -42,6 +42,12 @@ frappe.ui.form.on('Bank Transaction', { } } }); + + frm.set_query('party_type', function() { + return { + query: "erpnext.setup.doctype.party_type.party_type.get_party_type" + }; + }); }, refresh(frm) { frm.page.clear_actions_menu(); @@ -54,12 +60,19 @@ frappe.ui.form.on('Bank Transaction', { if (frm.doc.status === "Unreconciled" && frm.doc.docstatus === 1) { frm.add_custom_button(__('Set status as Closed'), function () { frm.trigger("close_bank_transaction"); - }); + }, __("Actions")); } - frm.add_custom_button(__('Bank reconciliation dashboard'), function () { - frappe.set_route("bank-reconciliation"); - }); + if (frm.doc.docstatus == 1) { + frm.add_custom_button(__('Bank reconciliation'), function () { + frappe.set_route("bank-reconciliation"); + }); + + frm.add_custom_button(__('Unreconcile Transaction'), () => { + frm.call('remove_payment_entries') + .then( () => frm.refresh() ); + }, __("Actions")); + } }, close_bank_transaction(frm) { return frappe.call({ diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 1561e3f7b0b..119dab4ceac 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -21,8 +21,10 @@ "currency", "section_break_4", "debit", + "deposit", "column_break_7", "credit", + "withdrawal", "section_break_10", "description", "section_break_14", @@ -35,7 +37,14 @@ "allocated_amount", "amended_from", "column_break_17", - "unallocated_amount" + "unallocated_amount", + "party_section", + "party_type", + "party", + "column_break_3czf", + "bank_party_name", + "bank_party_account_number", + "bank_party_iban" ], "fields": [ { @@ -62,6 +71,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "default": "Unreconciled", "fieldname": "status", "fieldtype": "Select", @@ -97,13 +107,13 @@ "fieldname": "debit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Debit" + "label": "Deposit" }, { "fieldname": "credit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Credit" + "label": "Withdrawal" }, { "fieldname": "column_break_7", @@ -187,7 +197,7 @@ "allow_on_submit": 1, "fieldname": "total_debit", "fieldtype": "Currency", - "label": "Total Debit", + "label": "Total Allocated Debit", "read_only": 1 }, { @@ -198,7 +208,7 @@ "allow_on_submit": 1, "fieldname": "total_credit", "fieldtype": "Currency", - "label": "Total Credit", + "label": "Total Allocated Credit", "read_only": 1 }, { @@ -231,11 +241,65 @@ "hidden": 1, "label": "Bank", "options": "Bank" + }, + { + "fieldname": "party_section", + "fieldtype": "Section Break", + "label": "Payment From / To" + }, + { + "allow_on_submit": 1, + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType" + }, + { + "allow_on_submit": 1, + "fieldname": "party", + "fieldtype": "Dynamic Link", + "label": "Party", + "options": "party_type" + }, + { + "fieldname": "column_break_3czf", + "fieldtype": "Column Break" + }, + { + "fieldname": "bank_party_name", + "fieldtype": "Data", + "label": "Party Name/Account Holder (Bank Statement)" + }, + { + "fieldname": "bank_party_iban", + "fieldtype": "Data", + "label": "Party IBAN (Bank Statement)" + }, + { + "fieldname": "bank_party_account_number", + "fieldtype": "Data", + "label": "Party Account No. (Bank Statement)" + }, + { + "fieldname": "deposit", + "fieldtype": "Currency", + "hidden": 1, + "label": "Deposit", + "oldfieldname": "debit", + "options": "currency" + }, + { + "fieldname": "withdrawal", + "fieldtype": "Currency", + "hidden": 1, + "label": "Withdrawal", + "oldfieldname": "credit", + "options": "currency" } ], "is_submittable": 1, "links": [], - "modified": "2023-10-16 11:44:31.419057", + "modified": "2023-12-11 10:20:42.555879", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 5ed85904557..a20f2c84b4f 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -28,14 +28,20 @@ class BankTransaction(StatusUpdater): bank: DF.Link | None bank_account: DF.Link bank_account_head: DF.Link | None + bank_party_account_number: DF.Data | None + bank_party_iban: DF.Data | None + bank_party_name: DF.Data | None category: DF.Link | None company: DF.Link credit: DF.Currency currency: DF.Link date: DF.Date debit: DF.Currency + deposit: DF.Currency description: DF.SmallText | None naming_series: DF.Literal["ACC-BTN-.YYYY.-"] + party: DF.DynamicLink | None + party_type: DF.Link | None payment_entries: DF.Table[BankTransactionPayments] reference_number: DF.Data | None status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled", "Closed"] @@ -43,37 +49,71 @@ class BankTransaction(StatusUpdater): total_debit: DF.Currency transaction_type: DF.Data | None unallocated_amount: DF.Currency + withdrawal: DF.Currency # end: auto-generated types - def after_insert(self): - self.unallocated_amount = flt(self.credit) - flt(self.debit) + def before_validate(self): + self.set_allocation_in_bank_transaction() def before_insert(self): self.check_similar_entries() self.check_transaction_references() + def validate(self): + # Keep for backward compatibility + if not (self.debit or self.credit) and (self.deposit or self.withdrawals): + self.credit = self.deposit + self.debit = self.withdrawals + else: + self.deposit = self.credit + self.withdrawals = self.debit + + self.validate_duplicate_references() + + def validate_duplicate_references(self): + """Make sure the same voucher is not allocated twice within the same Bank Transaction""" + if not self.payment_entries: + return + + pe = [] + for row in self.payment_entries: + reference = (row.payment_document, row.payment_entry) + if reference in pe: + frappe.throw( + _("{0} {1} is allocated twice in this Bank Transaction").format( + row.payment_document, row.payment_entry + ) + ) + pe.append(reference) + def before_save(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() self.check_reconciliation_amounts() - def on_submit(self): - self.check_reconciliation_amounts() + def before_submit(self): self.set_allocation_in_linked_docs() + self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + + def on_submit(self): self.set_payment_entries_clearance_date() self.set_status() + if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): + self.auto_set_party() + def before_update_after_submit(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() - def on_update_after_submit(self): - self.check_reconciliation_amounts() self.set_allocation_in_linked_docs() + self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + + def on_update_after_submit(self): self.set_payment_entries_clearance_date() self.set_status(update=True) @@ -83,6 +123,7 @@ class BankTransaction(StatusUpdater): self.payment_entries = [] def on_cancel(self): + self.remove_payment_entries() self.set_status(update=True) def check_similar_entries(self): @@ -220,16 +261,13 @@ class BankTransaction(StatusUpdater): transaction_amount = flt(self.credit) - flt(self.debit) - self.db_set("allocated_amount", flt(allocated_amount) if allocated_amount else 0) - self.db_set( - "unallocated_amount", - (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount, + self.allocated_amount = flt(allocated_amount) if allocated_amount else 0 + self.unallocated_amount = ( + (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount ) if transaction_amount == self.allocated_amount: - self.db_set("status", "Reconciled") - - self.reload() + self.status = "Reconciled" def check_reconciliation_amounts(self): for payment_entry in self.payment_entries: @@ -330,6 +368,36 @@ class BankTransaction(StatusUpdater): self.date = auto_repeat_doc.next_schedule_date self.reference_number = f"""{formatdate(self.date, "YYYYMMDD")}-{frappe.generate_hash("", 10)}""" + @frappe.whitelist() + def remove_payment_entries(self, cancel=False): + for payment_entry in self.payment_entries: + self.remove(payment_entry) + + if cancel: + self.run_method("before_update_after_submit") + self.run_method("on_update_after_submit") + else: + self.save() + + def auto_set_party(self): + from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty + + if self.party_type and self.party: + return + + result = AutoMatchParty( + bank_party_account_number=self.bank_party_account_number, + bank_party_iban=self.bank_party_iban, + bank_party_name=self.bank_party_name, + description=self.description, + deposit=self.deposit, + ).match() + + if not result: + return + + self.party_type, self.party = result + def get_unreconciled_amount(payment_entry): return frappe.db.get_value( diff --git a/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py new file mode 100644 index 00000000000..36ef1fca074 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py @@ -0,0 +1,151 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import nowdate + +from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account + + +class TestAutoMatchParty(FrappeTestCase): + @classmethod + def setUpClass(cls): + create_bank_account() + frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1) + frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1) + return super().setUpClass() + + @classmethod + def tearDownClass(cls): + frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0) + frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0) + + def test_match_by_account_number(self): + create_supplier_for_match(account_no="000000003716541159") + doc = create_bank_transaction( + withdrawal=1200, + transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b", + account_no="000000003716541159", + iban="DE02000000003716541159", + ) + + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "John Doe & Co.") + + def test_match_by_iban(self): + create_supplier_for_match(iban="DE02000000003716541159") + doc = create_bank_transaction( + withdrawal=1200, + transaction_id="c5455a224602afaa51592a9d9250600d", + account_no="000000003716541159", + iban="DE02000000003716541159", + ) + + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "John Doe & Co.") + + def test_match_by_party_name(self): + create_supplier_for_match(supplier_name="Jackson Ella W.") + doc = create_bank_transaction( + withdrawal=1200, + transaction_id="1f6f661f347ff7b1ea588665f473adb1", + party_name="Ella Jackson", + iban="DE04000000003716545346", + ) + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "Jackson Ella W.") + + def test_match_by_description(self): + create_supplier_for_match(supplier_name="Microsoft") + doc = create_bank_transaction( + description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536", + withdrawal=1200, + transaction_id="8df880a2d09c3bed3fea358ca5168c5a", + party_name="", + ) + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "Microsoft") + + def test_skip_match_if_multiple_close_results(self): + create_supplier_for_match(supplier_name="Adithya Medical & General Stores") + create_supplier_for_match(supplier_name="Adithya Medical And General Stores") + + doc = create_bank_transaction( + description="Paracetamol Consignment, SINV-0009", + withdrawal=24.85, + transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9", + party_name="Adithya Medical & General", + ) + + # Mapping is skipped as both Supplier names have the same match score + self.assertEqual(doc.party_type, None) + self.assertEqual(doc.party, None) + + +def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None): + if frappe.db.exists("Supplier", {"supplier_name": supplier_name}): + # Update related Bank Account details + if not (iban or account_no): + return + + frappe.db.set_value( + dt="Bank Account", + dn={"party": supplier_name}, + field={"iban": iban, "bank_account_no": account_no}, + ) + return + + # Create Supplier and Bank Account for the same + supplier = frappe.new_doc("Supplier") + supplier.supplier_name = supplier_name + supplier.supplier_group = "Services" + supplier.supplier_type = "Company" + supplier.insert() + + if not frappe.db.exists("Bank", "TestBank"): + bank = frappe.new_doc("Bank") + bank.bank_name = "TestBank" + bank.insert(ignore_if_duplicate=True) + + if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"): + bank_account = frappe.new_doc("Bank Account") + bank_account.account_name = supplier.name + bank_account.bank = "TestBank" + bank_account.iban = iban + bank_account.bank_account_no = account_no + bank_account.party_type = "Supplier" + bank_account.party = supplier.name + bank_account.insert() + + +def create_bank_transaction( + description=None, + withdrawal=0, + deposit=0, + transaction_id=None, + party_name=None, + account_no=None, + iban=None, +): + doc = frappe.new_doc("Bank Transaction") + doc.update( + { + "doctype": "Bank Transaction", + "description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G", + "date": nowdate(), + "withdrawal": withdrawal, + "deposit": deposit, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank", + "transaction_id": transaction_id, + "bank_party_name": party_name, + "bank_party_account_number": account_no, + "bank_party_iban": iban, + } + ) + doc.insert() + doc.submit() + doc.reload() + + return doc diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index e5ae1e11f7f..dbc9a6dd602 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document -from frappe.utils import add_months, formatdate, getdate, sbool, today, cint +from frappe.utils import add_months, cint, formatdate, getdate, sbool, today from plaid.errors import ItemError from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account @@ -256,8 +256,8 @@ def new_bank_transaction(transaction): "doctype": "Bank Transaction", "date": getdate(transaction["date"]), "bank_account": bank_account, - "debit": deposit, - "credit": withdrawal, + "credit": deposit, + "debit": withdrawal, "currency": transaction["iso_currency_code"], "reference_number": transaction["transaction_id"], "transaction_type": ( diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 021eaa12094..c07583c8f8b 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -81,7 +81,9 @@ "mode_of_payment", "bank_details_section", "bank_name", + "column_break_heye", "bank_ac_no", + "iban", "personal_details", "marital_status", "family_background", @@ -808,6 +810,16 @@ "fieldname": "column_break_104", "fieldtype": "Column Break" }, + { + "fieldname": "column_break_heye", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.salary_mode == 'Bank'", + "fieldname": "iban", + "fieldtype": "Data", + "label": "IBAN" + }, { "fieldname": "column_break_deui", "fieldtype": "Column Break" @@ -823,7 +835,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2023-11-17 09:47:27.355960", + "modified": "2023-12-11 10:29:00.809067", "modified_by": "Administrator", "module": "Setup", "name": "Employee", diff --git a/pyproject.toml b/pyproject.toml index 88a915219a9..13cfd8899d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "pycountry~=22.3.5", "Unidecode~=1.3.6", "barcodenumber~=0.5.0", + "rapidfuzz~=2.15.0", "holidays~=0.28", # integration dependencies -- GitLab From 57c38e999eef8731b2dc85c7c2a3f32b395f97ac Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 10:38:45 +0100 Subject: [PATCH 090/155] Revert "feat: Party auto match in bank transactions" This reverts commit 7a79d14f7ac32100cdf7c946303470b25eb487ca. --- .../accounts_settings/accounts_settings.json | 30 +-- .../accounts_settings/accounts_settings.py | 48 ----- .../bank_transaction/auto_match_party.py | 183 ------------------ .../bank_transaction/bank_transaction.js | 21 +- .../bank_transaction/bank_transaction.json | 76 +------- .../bank_transaction/bank_transaction.py | 94 ++------- .../bank_transaction/test_auto_match_party.py | 151 --------------- .../doctype/plaid_settings/plaid_settings.py | 6 +- erpnext/setup/doctype/employee/employee.json | 14 +- pyproject.toml | 1 - 10 files changed, 34 insertions(+), 590 deletions(-) delete mode 100644 erpnext/accounts/doctype/bank_transaction/auto_match_party.py delete mode 100644 erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 3fa149ce7ca..138f4aea5ad 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -67,9 +67,6 @@ "frozen_accounts_modifier", "tab_break_dpet", "show_balance_in_coa", - "banking_tab", - "enable_party_matching", - "enable_fuzzy_matching", "reports_tab", "remarks_section", "general_ledger_remarks_length", @@ -414,30 +411,17 @@ "fieldtype": "Check", "label": "Show Taxes as Table in Print" }, - { - "fieldname": "banking_tab", - "fieldtype": "Tab Break", - "label": "Banking" - }, - { - "default": "0", - "description": "Auto match and set the Party in Bank Transactions", - "fieldname": "enable_party_matching", - "fieldtype": "Check", - "label": "Enable Automatic Party Matching" - }, { "default": "0", - "depends_on": "enable_party_matching", - "description": "Approximately match the description/party name against parties", - "fieldname": "enable_fuzzy_matching", + "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", + "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", - "label": "Enable Fuzzy Matching" + "label": "Ignore Account Closing Balance" }, { "default": "0", - "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", - "fieldname": "ignore_account_closing_balance", + "description": "Tax Amount will be rounded on a row(items) level", + "fieldname": "round_row_wise_tax", "fieldtype": "Check", "label": "Ignore Account Closing Balance" }, @@ -446,7 +430,7 @@ "description": "Tax Amount will be rounded on a row(items) level", "fieldname": "round_row_wise_tax", "fieldtype": "Check", - "label": "Ignore Account Closing Balance" + "label": "Round Tax Amount Row-wise" }, { "fieldname": "reports_tab", @@ -482,7 +466,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-12-11 10:22:18.348446", + "modified": "2023-12-08 16:03:01.935070", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 4048be10f61..ac3d44bb5e7 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,54 +14,6 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - acc_frozen_upto: DF.Date | None - add_taxes_from_item_tax_template: DF.Check - allow_multi_currency_invoices_against_single_party_account: DF.Check - allow_stale: DF.Check - auto_reconcile_payments: DF.Check - automatically_fetch_payment_terms: DF.Check - automatically_process_deferred_accounting_entry: DF.Check - book_asset_depreciation_entry_automatically: DF.Check - book_deferred_entries_based_on: DF.Literal["Days", "Months"] - book_deferred_entries_via_journal_entry: DF.Check - book_tax_discount_loss: DF.Check - check_supplier_invoice_uniqueness: DF.Check - credit_controller: DF.Link | None - default_payment_days: DF.Int - determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] - enable_common_party_accounting: DF.Check - enable_fuzzy_matching: DF.Check - enable_party_matching: DF.Check - frozen_accounts_modifier: DF.Link | None - general_ledger_remarks_length: DF.Int - ignore_account_closing_balance: DF.Check - make_payment_via_journal_entry: DF.Check - mandatory_accounting_journal: DF.Check - merge_similar_account_heads: DF.Check - over_billing_allowance: DF.Currency - post_change_gl_entries: DF.Check - receivable_payable_remarks_length: DF.Int - role_allowed_to_over_bill: DF.Link | None - round_row_wise_tax: DF.Check - show_balance_in_coa: DF.Check - show_inclusive_tax_in_print: DF.Check - show_payment_schedule_in_print: DF.Check - show_taxes_as_table_in_print: DF.Check - stale_days: DF.Int - submit_journal_entries: DF.Check - unlink_advance_payment_on_cancelation_of_order: DF.Check - unlink_payment_on_cancellation_of_invoice: DF.Check - validate_posting_date_chronology_in_sales_invoices: DF.Check - # end: auto-generated types - def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py deleted file mode 100644 index 04dab4c28a0..00000000000 --- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py +++ /dev/null @@ -1,183 +0,0 @@ -from typing import Tuple, Union - -import frappe -from frappe.utils import flt -from rapidfuzz import fuzz, process - - -class AutoMatchParty: - """ - Matches by Account/IBAN and then by Party Name/Description sequentially. - Returns when a result is obtained. - - Result (if present) is of the form: (Party Type, Party,) - """ - - def __init__(self, **kwargs) -> None: - self.__dict__.update(kwargs) - - def get(self, key): - return self.__dict__.get(key, None) - - def match(self) -> Union[Tuple, None]: - result = None - result = AutoMatchbyAccountIBAN( - bank_party_account_number=self.bank_party_account_number, - bank_party_iban=self.bank_party_iban, - deposit=self.deposit, - ).match() - - fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching") - if not result and fuzzy_matching_enabled: - result = AutoMatchbyPartyNameDescription( - bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit - ).match() - - return result - - -class AutoMatchbyAccountIBAN: - def __init__(self, **kwargs) -> None: - self.__dict__.update(kwargs) - - def get(self, key): - return self.__dict__.get(key, None) - - def match(self): - if not (self.bank_party_account_number or self.bank_party_iban): - return None - - result = self.match_account_in_party() - return result - - def match_account_in_party(self) -> Union[Tuple, None]: - """Check if there is a IBAN/Account No. match in Customer/Supplier/Employee""" - result = None - parties = get_parties_in_order(self.deposit) - or_filters = self.get_or_filters() - - for party in parties: - party_result = frappe.db.get_all( - "Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1 - ) - - if party == "Employee" and not party_result: - # Search in Bank Accounts first for Employee, and then Employee record - if "bank_account_no" in or_filters: - or_filters["bank_ac_no"] = or_filters.pop("bank_account_no") - - party_result = frappe.db.get_all( - party, or_filters=or_filters, pluck="name", limit_page_length=1 - ) - - if party_result: - result = ( - party, - party_result[0], - ) - break - - return result - - def get_or_filters(self) -> dict: - or_filters = {} - if self.bank_party_account_number: - or_filters["bank_account_no"] = self.bank_party_account_number - - if self.bank_party_iban: - or_filters["iban"] = self.bank_party_iban - - return or_filters - - -class AutoMatchbyPartyNameDescription: - def __init__(self, **kwargs) -> None: - self.__dict__.update(kwargs) - - def get(self, key): - return self.__dict__.get(key, None) - - def match(self) -> Union[Tuple, None]: - # fuzzy search by customer/supplier & employee - if not (self.bank_party_name or self.description): - return None - - result = self.match_party_name_desc_in_party() - return result - - def match_party_name_desc_in_party(self) -> Union[Tuple, None]: - """Fuzzy search party name and/or description against parties in the system""" - result = None - parties = get_parties_in_order(self.deposit) - - for party in parties: - filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} - field = party.lower() + "_name" - names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"]) - - for field in ["bank_party_name", "description"]: - if not self.get(field): - continue - - result, skip = self.fuzzy_search_and_return_result(party, names, field) - if result or skip: - break - - if result or skip: - # Skip If: It was hard to distinguish between close matches and so match is None - # OR if the right match was found - break - - return result - - def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]: - skip = False - result = process.extract( - query=self.get(field), - choices={row.get("name"): row.get("party_name") for row in names}, - scorer=fuzz.token_set_ratio, - ) - party_name, skip = self.process_fuzzy_result(result) - - if not party_name: - return None, skip - - return ( - party, - party_name, - ), skip - - def process_fuzzy_result(self, result: Union[list, None]): - """ - If there are multiple valid close matches return None as result may be faulty. - Return the result only if one accurate match stands out. - - Returns: Result, Skip (whether or not to discontinue matching) - """ - SCORE, PARTY_ID, CUTOFF = 1, 2, 80 - - if not result or not len(result): - return None, False - - first_result = result[0] - if len(result) == 1: - return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True - - second_result = result[1] - if first_result[SCORE] > CUTOFF: - # If multiple matches with the same score, return None but discontinue matching - # Matches were found but were too close to distinguish between - if first_result[SCORE] == second_result[SCORE]: - return None, True - - return first_result[PARTY_ID], True - else: - return None, False - - -def get_parties_in_order(deposit: float) -> list: - parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive - if flt(deposit) > 0: - parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay - - return parties diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 4c1052a69ab..6e504668d8a 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -42,12 +42,6 @@ frappe.ui.form.on('Bank Transaction', { } } }); - - frm.set_query('party_type', function() { - return { - query: "erpnext.setup.doctype.party_type.party_type.get_party_type" - }; - }); }, refresh(frm) { frm.page.clear_actions_menu(); @@ -60,19 +54,12 @@ frappe.ui.form.on('Bank Transaction', { if (frm.doc.status === "Unreconciled" && frm.doc.docstatus === 1) { frm.add_custom_button(__('Set status as Closed'), function () { frm.trigger("close_bank_transaction"); - }, __("Actions")); - } - - if (frm.doc.docstatus == 1) { - frm.add_custom_button(__('Bank reconciliation'), function () { - frappe.set_route("bank-reconciliation"); }); - - frm.add_custom_button(__('Unreconcile Transaction'), () => { - frm.call('remove_payment_entries') - .then( () => frm.refresh() ); - }, __("Actions")); } + + frm.add_custom_button(__('Bank reconciliation dashboard'), function () { + frappe.set_route("bank-reconciliation"); + }); }, close_bank_transaction(frm) { return frappe.call({ diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 119dab4ceac..1561e3f7b0b 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -21,10 +21,8 @@ "currency", "section_break_4", "debit", - "deposit", "column_break_7", "credit", - "withdrawal", "section_break_10", "description", "section_break_14", @@ -37,14 +35,7 @@ "allocated_amount", "amended_from", "column_break_17", - "unallocated_amount", - "party_section", - "party_type", - "party", - "column_break_3czf", - "bank_party_name", - "bank_party_account_number", - "bank_party_iban" + "unallocated_amount" ], "fields": [ { @@ -71,7 +62,6 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, "default": "Unreconciled", "fieldname": "status", "fieldtype": "Select", @@ -107,13 +97,13 @@ "fieldname": "debit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Deposit" + "label": "Debit" }, { "fieldname": "credit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Withdrawal" + "label": "Credit" }, { "fieldname": "column_break_7", @@ -197,7 +187,7 @@ "allow_on_submit": 1, "fieldname": "total_debit", "fieldtype": "Currency", - "label": "Total Allocated Debit", + "label": "Total Debit", "read_only": 1 }, { @@ -208,7 +198,7 @@ "allow_on_submit": 1, "fieldname": "total_credit", "fieldtype": "Currency", - "label": "Total Allocated Credit", + "label": "Total Credit", "read_only": 1 }, { @@ -241,65 +231,11 @@ "hidden": 1, "label": "Bank", "options": "Bank" - }, - { - "fieldname": "party_section", - "fieldtype": "Section Break", - "label": "Payment From / To" - }, - { - "allow_on_submit": 1, - "fieldname": "party_type", - "fieldtype": "Link", - "label": "Party Type", - "options": "DocType" - }, - { - "allow_on_submit": 1, - "fieldname": "party", - "fieldtype": "Dynamic Link", - "label": "Party", - "options": "party_type" - }, - { - "fieldname": "column_break_3czf", - "fieldtype": "Column Break" - }, - { - "fieldname": "bank_party_name", - "fieldtype": "Data", - "label": "Party Name/Account Holder (Bank Statement)" - }, - { - "fieldname": "bank_party_iban", - "fieldtype": "Data", - "label": "Party IBAN (Bank Statement)" - }, - { - "fieldname": "bank_party_account_number", - "fieldtype": "Data", - "label": "Party Account No. (Bank Statement)" - }, - { - "fieldname": "deposit", - "fieldtype": "Currency", - "hidden": 1, - "label": "Deposit", - "oldfieldname": "debit", - "options": "currency" - }, - { - "fieldname": "withdrawal", - "fieldtype": "Currency", - "hidden": 1, - "label": "Withdrawal", - "oldfieldname": "credit", - "options": "currency" } ], "is_submittable": 1, "links": [], - "modified": "2023-12-11 10:20:42.555879", + "modified": "2023-10-16 11:44:31.419057", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index a20f2c84b4f..5ed85904557 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -28,20 +28,14 @@ class BankTransaction(StatusUpdater): bank: DF.Link | None bank_account: DF.Link bank_account_head: DF.Link | None - bank_party_account_number: DF.Data | None - bank_party_iban: DF.Data | None - bank_party_name: DF.Data | None category: DF.Link | None company: DF.Link credit: DF.Currency currency: DF.Link date: DF.Date debit: DF.Currency - deposit: DF.Currency description: DF.SmallText | None naming_series: DF.Literal["ACC-BTN-.YYYY.-"] - party: DF.DynamicLink | None - party_type: DF.Link | None payment_entries: DF.Table[BankTransactionPayments] reference_number: DF.Data | None status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled", "Closed"] @@ -49,71 +43,37 @@ class BankTransaction(StatusUpdater): total_debit: DF.Currency transaction_type: DF.Data | None unallocated_amount: DF.Currency - withdrawal: DF.Currency # end: auto-generated types - def before_validate(self): - self.set_allocation_in_bank_transaction() + def after_insert(self): + self.unallocated_amount = flt(self.credit) - flt(self.debit) def before_insert(self): self.check_similar_entries() self.check_transaction_references() - def validate(self): - # Keep for backward compatibility - if not (self.debit or self.credit) and (self.deposit or self.withdrawals): - self.credit = self.deposit - self.debit = self.withdrawals - else: - self.deposit = self.credit - self.withdrawals = self.debit - - self.validate_duplicate_references() - - def validate_duplicate_references(self): - """Make sure the same voucher is not allocated twice within the same Bank Transaction""" - if not self.payment_entries: - return - - pe = [] - for row in self.payment_entries: - reference = (row.payment_document, row.payment_entry) - if reference in pe: - frappe.throw( - _("{0} {1} is allocated twice in this Bank Transaction").format( - row.payment_document, row.payment_entry - ) - ) - pe.append(reference) - def before_save(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() self.check_reconciliation_amounts() - def before_submit(self): - self.set_allocation_in_linked_docs() + def on_submit(self): self.check_reconciliation_amounts() + self.set_allocation_in_linked_docs() self.set_allocation_in_bank_transaction() - - def on_submit(self): self.set_payment_entries_clearance_date() self.set_status() - if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): - self.auto_set_party() - def before_update_after_submit(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() - self.set_allocation_in_linked_docs() + def on_update_after_submit(self): self.check_reconciliation_amounts() + self.set_allocation_in_linked_docs() self.set_allocation_in_bank_transaction() - - def on_update_after_submit(self): self.set_payment_entries_clearance_date() self.set_status(update=True) @@ -123,7 +83,6 @@ class BankTransaction(StatusUpdater): self.payment_entries = [] def on_cancel(self): - self.remove_payment_entries() self.set_status(update=True) def check_similar_entries(self): @@ -261,13 +220,16 @@ class BankTransaction(StatusUpdater): transaction_amount = flt(self.credit) - flt(self.debit) - self.allocated_amount = flt(allocated_amount) if allocated_amount else 0 - self.unallocated_amount = ( - (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount + self.db_set("allocated_amount", flt(allocated_amount) if allocated_amount else 0) + self.db_set( + "unallocated_amount", + (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount, ) if transaction_amount == self.allocated_amount: - self.status = "Reconciled" + self.db_set("status", "Reconciled") + + self.reload() def check_reconciliation_amounts(self): for payment_entry in self.payment_entries: @@ -368,36 +330,6 @@ class BankTransaction(StatusUpdater): self.date = auto_repeat_doc.next_schedule_date self.reference_number = f"""{formatdate(self.date, "YYYYMMDD")}-{frappe.generate_hash("", 10)}""" - @frappe.whitelist() - def remove_payment_entries(self, cancel=False): - for payment_entry in self.payment_entries: - self.remove(payment_entry) - - if cancel: - self.run_method("before_update_after_submit") - self.run_method("on_update_after_submit") - else: - self.save() - - def auto_set_party(self): - from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty - - if self.party_type and self.party: - return - - result = AutoMatchParty( - bank_party_account_number=self.bank_party_account_number, - bank_party_iban=self.bank_party_iban, - bank_party_name=self.bank_party_name, - description=self.description, - deposit=self.deposit, - ).match() - - if not result: - return - - self.party_type, self.party = result - def get_unreconciled_amount(payment_entry): return frappe.db.get_value( diff --git a/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py deleted file mode 100644 index 36ef1fca074..00000000000 --- a/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe.tests.utils import FrappeTestCase -from frappe.utils import nowdate - -from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account - - -class TestAutoMatchParty(FrappeTestCase): - @classmethod - def setUpClass(cls): - create_bank_account() - frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1) - frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1) - return super().setUpClass() - - @classmethod - def tearDownClass(cls): - frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0) - frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0) - - def test_match_by_account_number(self): - create_supplier_for_match(account_no="000000003716541159") - doc = create_bank_transaction( - withdrawal=1200, - transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b", - account_no="000000003716541159", - iban="DE02000000003716541159", - ) - - self.assertEqual(doc.party_type, "Supplier") - self.assertEqual(doc.party, "John Doe & Co.") - - def test_match_by_iban(self): - create_supplier_for_match(iban="DE02000000003716541159") - doc = create_bank_transaction( - withdrawal=1200, - transaction_id="c5455a224602afaa51592a9d9250600d", - account_no="000000003716541159", - iban="DE02000000003716541159", - ) - - self.assertEqual(doc.party_type, "Supplier") - self.assertEqual(doc.party, "John Doe & Co.") - - def test_match_by_party_name(self): - create_supplier_for_match(supplier_name="Jackson Ella W.") - doc = create_bank_transaction( - withdrawal=1200, - transaction_id="1f6f661f347ff7b1ea588665f473adb1", - party_name="Ella Jackson", - iban="DE04000000003716545346", - ) - self.assertEqual(doc.party_type, "Supplier") - self.assertEqual(doc.party, "Jackson Ella W.") - - def test_match_by_description(self): - create_supplier_for_match(supplier_name="Microsoft") - doc = create_bank_transaction( - description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536", - withdrawal=1200, - transaction_id="8df880a2d09c3bed3fea358ca5168c5a", - party_name="", - ) - self.assertEqual(doc.party_type, "Supplier") - self.assertEqual(doc.party, "Microsoft") - - def test_skip_match_if_multiple_close_results(self): - create_supplier_for_match(supplier_name="Adithya Medical & General Stores") - create_supplier_for_match(supplier_name="Adithya Medical And General Stores") - - doc = create_bank_transaction( - description="Paracetamol Consignment, SINV-0009", - withdrawal=24.85, - transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9", - party_name="Adithya Medical & General", - ) - - # Mapping is skipped as both Supplier names have the same match score - self.assertEqual(doc.party_type, None) - self.assertEqual(doc.party, None) - - -def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None): - if frappe.db.exists("Supplier", {"supplier_name": supplier_name}): - # Update related Bank Account details - if not (iban or account_no): - return - - frappe.db.set_value( - dt="Bank Account", - dn={"party": supplier_name}, - field={"iban": iban, "bank_account_no": account_no}, - ) - return - - # Create Supplier and Bank Account for the same - supplier = frappe.new_doc("Supplier") - supplier.supplier_name = supplier_name - supplier.supplier_group = "Services" - supplier.supplier_type = "Company" - supplier.insert() - - if not frappe.db.exists("Bank", "TestBank"): - bank = frappe.new_doc("Bank") - bank.bank_name = "TestBank" - bank.insert(ignore_if_duplicate=True) - - if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"): - bank_account = frappe.new_doc("Bank Account") - bank_account.account_name = supplier.name - bank_account.bank = "TestBank" - bank_account.iban = iban - bank_account.bank_account_no = account_no - bank_account.party_type = "Supplier" - bank_account.party = supplier.name - bank_account.insert() - - -def create_bank_transaction( - description=None, - withdrawal=0, - deposit=0, - transaction_id=None, - party_name=None, - account_no=None, - iban=None, -): - doc = frappe.new_doc("Bank Transaction") - doc.update( - { - "doctype": "Bank Transaction", - "description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G", - "date": nowdate(), - "withdrawal": withdrawal, - "deposit": deposit, - "currency": "INR", - "bank_account": "Checking Account - Citi Bank", - "transaction_id": transaction_id, - "bank_party_name": party_name, - "bank_party_account_number": account_no, - "bank_party_iban": iban, - } - ) - doc.insert() - doc.submit() - doc.reload() - - return doc diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index dbc9a6dd602..e5ae1e11f7f 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document -from frappe.utils import add_months, cint, formatdate, getdate, sbool, today +from frappe.utils import add_months, formatdate, getdate, sbool, today, cint from plaid.errors import ItemError from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account @@ -256,8 +256,8 @@ def new_bank_transaction(transaction): "doctype": "Bank Transaction", "date": getdate(transaction["date"]), "bank_account": bank_account, - "credit": deposit, - "debit": withdrawal, + "debit": deposit, + "credit": withdrawal, "currency": transaction["iso_currency_code"], "reference_number": transaction["transaction_id"], "transaction_type": ( diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index c07583c8f8b..021eaa12094 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -81,9 +81,7 @@ "mode_of_payment", "bank_details_section", "bank_name", - "column_break_heye", "bank_ac_no", - "iban", "personal_details", "marital_status", "family_background", @@ -810,16 +808,6 @@ "fieldname": "column_break_104", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_heye", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.salary_mode == 'Bank'", - "fieldname": "iban", - "fieldtype": "Data", - "label": "IBAN" - }, { "fieldname": "column_break_deui", "fieldtype": "Column Break" @@ -835,7 +823,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2023-12-11 10:29:00.809067", + "modified": "2023-11-17 09:47:27.355960", "modified_by": "Administrator", "module": "Setup", "name": "Employee", diff --git a/pyproject.toml b/pyproject.toml index 13cfd8899d5..88a915219a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ dependencies = [ "pycountry~=22.3.5", "Unidecode~=1.3.6", "barcodenumber~=0.5.0", - "rapidfuzz~=2.15.0", "holidays~=0.28", # integration dependencies -- GitLab From 9bd5af73f93ac9d8a70a23a723ed355d847f81e4 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 10:37:27 +0100 Subject: [PATCH 091/155] feat: Party auto match in bank transactions --- .../accounts_settings/accounts_settings.json | 30 ++- .../accounts_settings/accounts_settings.py | 48 +++++ .../bank_transaction/auto_match_party.py | 183 ++++++++++++++++++ .../bank_transaction/bank_transaction.js | 21 +- .../bank_transaction/bank_transaction.json | 76 +++++++- .../bank_transaction/bank_transaction.py | 94 +++++++-- .../bank_transaction/test_auto_match_party.py | 151 +++++++++++++++ .../doctype/plaid_settings/plaid_settings.py | 6 +- erpnext/setup/doctype/employee/employee.json | 14 +- pyproject.toml | 1 + 10 files changed, 590 insertions(+), 34 deletions(-) create mode 100644 erpnext/accounts/doctype/bank_transaction/auto_match_party.py create mode 100644 erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 138f4aea5ad..3fa149ce7ca 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -67,6 +67,9 @@ "frozen_accounts_modifier", "tab_break_dpet", "show_balance_in_coa", + "banking_tab", + "enable_party_matching", + "enable_fuzzy_matching", "reports_tab", "remarks_section", "general_ledger_remarks_length", @@ -411,17 +414,30 @@ "fieldtype": "Check", "label": "Show Taxes as Table in Print" }, + { + "fieldname": "banking_tab", + "fieldtype": "Tab Break", + "label": "Banking" + }, { "default": "0", - "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", - "fieldname": "ignore_account_closing_balance", + "description": "Auto match and set the Party in Bank Transactions", + "fieldname": "enable_party_matching", "fieldtype": "Check", - "label": "Ignore Account Closing Balance" + "label": "Enable Automatic Party Matching" }, { "default": "0", - "description": "Tax Amount will be rounded on a row(items) level", - "fieldname": "round_row_wise_tax", + "depends_on": "enable_party_matching", + "description": "Approximately match the description/party name against parties", + "fieldname": "enable_fuzzy_matching", + "fieldtype": "Check", + "label": "Enable Fuzzy Matching" + }, + { + "default": "0", + "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", + "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", "label": "Ignore Account Closing Balance" }, @@ -430,7 +446,7 @@ "description": "Tax Amount will be rounded on a row(items) level", "fieldname": "round_row_wise_tax", "fieldtype": "Check", - "label": "Round Tax Amount Row-wise" + "label": "Ignore Account Closing Balance" }, { "fieldname": "reports_tab", @@ -466,7 +482,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-12-08 16:03:01.935070", + "modified": "2023-12-11 10:22:18.348446", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ac3d44bb5e7..4048be10f61 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,6 +14,54 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + acc_frozen_upto: DF.Date | None + add_taxes_from_item_tax_template: DF.Check + allow_multi_currency_invoices_against_single_party_account: DF.Check + allow_stale: DF.Check + auto_reconcile_payments: DF.Check + automatically_fetch_payment_terms: DF.Check + automatically_process_deferred_accounting_entry: DF.Check + book_asset_depreciation_entry_automatically: DF.Check + book_deferred_entries_based_on: DF.Literal["Days", "Months"] + book_deferred_entries_via_journal_entry: DF.Check + book_tax_discount_loss: DF.Check + check_supplier_invoice_uniqueness: DF.Check + credit_controller: DF.Link | None + default_payment_days: DF.Int + determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] + enable_common_party_accounting: DF.Check + enable_fuzzy_matching: DF.Check + enable_party_matching: DF.Check + frozen_accounts_modifier: DF.Link | None + general_ledger_remarks_length: DF.Int + ignore_account_closing_balance: DF.Check + make_payment_via_journal_entry: DF.Check + mandatory_accounting_journal: DF.Check + merge_similar_account_heads: DF.Check + over_billing_allowance: DF.Currency + post_change_gl_entries: DF.Check + receivable_payable_remarks_length: DF.Int + role_allowed_to_over_bill: DF.Link | None + round_row_wise_tax: DF.Check + show_balance_in_coa: DF.Check + show_inclusive_tax_in_print: DF.Check + show_payment_schedule_in_print: DF.Check + show_taxes_as_table_in_print: DF.Check + stale_days: DF.Int + submit_journal_entries: DF.Check + unlink_advance_payment_on_cancelation_of_order: DF.Check + unlink_payment_on_cancellation_of_invoice: DF.Check + validate_posting_date_chronology_in_sales_invoices: DF.Check + # end: auto-generated types + def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py new file mode 100644 index 00000000000..04dab4c28a0 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -0,0 +1,183 @@ +from typing import Tuple, Union + +import frappe +from frappe.utils import flt +from rapidfuzz import fuzz, process + + +class AutoMatchParty: + """ + Matches by Account/IBAN and then by Party Name/Description sequentially. + Returns when a result is obtained. + + Result (if present) is of the form: (Party Type, Party,) + """ + + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self) -> Union[Tuple, None]: + result = None + result = AutoMatchbyAccountIBAN( + bank_party_account_number=self.bank_party_account_number, + bank_party_iban=self.bank_party_iban, + deposit=self.deposit, + ).match() + + fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching") + if not result and fuzzy_matching_enabled: + result = AutoMatchbyPartyNameDescription( + bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit + ).match() + + return result + + +class AutoMatchbyAccountIBAN: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self): + if not (self.bank_party_account_number or self.bank_party_iban): + return None + + result = self.match_account_in_party() + return result + + def match_account_in_party(self) -> Union[Tuple, None]: + """Check if there is a IBAN/Account No. match in Customer/Supplier/Employee""" + result = None + parties = get_parties_in_order(self.deposit) + or_filters = self.get_or_filters() + + for party in parties: + party_result = frappe.db.get_all( + "Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1 + ) + + if party == "Employee" and not party_result: + # Search in Bank Accounts first for Employee, and then Employee record + if "bank_account_no" in or_filters: + or_filters["bank_ac_no"] = or_filters.pop("bank_account_no") + + party_result = frappe.db.get_all( + party, or_filters=or_filters, pluck="name", limit_page_length=1 + ) + + if party_result: + result = ( + party, + party_result[0], + ) + break + + return result + + def get_or_filters(self) -> dict: + or_filters = {} + if self.bank_party_account_number: + or_filters["bank_account_no"] = self.bank_party_account_number + + if self.bank_party_iban: + or_filters["iban"] = self.bank_party_iban + + return or_filters + + +class AutoMatchbyPartyNameDescription: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def get(self, key): + return self.__dict__.get(key, None) + + def match(self) -> Union[Tuple, None]: + # fuzzy search by customer/supplier & employee + if not (self.bank_party_name or self.description): + return None + + result = self.match_party_name_desc_in_party() + return result + + def match_party_name_desc_in_party(self) -> Union[Tuple, None]: + """Fuzzy search party name and/or description against parties in the system""" + result = None + parties = get_parties_in_order(self.deposit) + + for party in parties: + filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} + field = party.lower() + "_name" + names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"]) + + for field in ["bank_party_name", "description"]: + if not self.get(field): + continue + + result, skip = self.fuzzy_search_and_return_result(party, names, field) + if result or skip: + break + + if result or skip: + # Skip If: It was hard to distinguish between close matches and so match is None + # OR if the right match was found + break + + return result + + def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]: + skip = False + result = process.extract( + query=self.get(field), + choices={row.get("name"): row.get("party_name") for row in names}, + scorer=fuzz.token_set_ratio, + ) + party_name, skip = self.process_fuzzy_result(result) + + if not party_name: + return None, skip + + return ( + party, + party_name, + ), skip + + def process_fuzzy_result(self, result: Union[list, None]): + """ + If there are multiple valid close matches return None as result may be faulty. + Return the result only if one accurate match stands out. + + Returns: Result, Skip (whether or not to discontinue matching) + """ + SCORE, PARTY_ID, CUTOFF = 1, 2, 80 + + if not result or not len(result): + return None, False + + first_result = result[0] + if len(result) == 1: + return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True + + second_result = result[1] + if first_result[SCORE] > CUTOFF: + # If multiple matches with the same score, return None but discontinue matching + # Matches were found but were too close to distinguish between + if first_result[SCORE] == second_result[SCORE]: + return None, True + + return first_result[PARTY_ID], True + else: + return None, False + + +def get_parties_in_order(deposit: float) -> list: + parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive + if flt(deposit) > 0: + parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay + + return parties diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 6e504668d8a..4c1052a69ab 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -42,6 +42,12 @@ frappe.ui.form.on('Bank Transaction', { } } }); + + frm.set_query('party_type', function() { + return { + query: "erpnext.setup.doctype.party_type.party_type.get_party_type" + }; + }); }, refresh(frm) { frm.page.clear_actions_menu(); @@ -54,12 +60,19 @@ frappe.ui.form.on('Bank Transaction', { if (frm.doc.status === "Unreconciled" && frm.doc.docstatus === 1) { frm.add_custom_button(__('Set status as Closed'), function () { frm.trigger("close_bank_transaction"); - }); + }, __("Actions")); } - frm.add_custom_button(__('Bank reconciliation dashboard'), function () { - frappe.set_route("bank-reconciliation"); - }); + if (frm.doc.docstatus == 1) { + frm.add_custom_button(__('Bank reconciliation'), function () { + frappe.set_route("bank-reconciliation"); + }); + + frm.add_custom_button(__('Unreconcile Transaction'), () => { + frm.call('remove_payment_entries') + .then( () => frm.refresh() ); + }, __("Actions")); + } }, close_bank_transaction(frm) { return frappe.call({ diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 1561e3f7b0b..119dab4ceac 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -21,8 +21,10 @@ "currency", "section_break_4", "debit", + "deposit", "column_break_7", "credit", + "withdrawal", "section_break_10", "description", "section_break_14", @@ -35,7 +37,14 @@ "allocated_amount", "amended_from", "column_break_17", - "unallocated_amount" + "unallocated_amount", + "party_section", + "party_type", + "party", + "column_break_3czf", + "bank_party_name", + "bank_party_account_number", + "bank_party_iban" ], "fields": [ { @@ -62,6 +71,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "default": "Unreconciled", "fieldname": "status", "fieldtype": "Select", @@ -97,13 +107,13 @@ "fieldname": "debit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Debit" + "label": "Deposit" }, { "fieldname": "credit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Credit" + "label": "Withdrawal" }, { "fieldname": "column_break_7", @@ -187,7 +197,7 @@ "allow_on_submit": 1, "fieldname": "total_debit", "fieldtype": "Currency", - "label": "Total Debit", + "label": "Total Allocated Debit", "read_only": 1 }, { @@ -198,7 +208,7 @@ "allow_on_submit": 1, "fieldname": "total_credit", "fieldtype": "Currency", - "label": "Total Credit", + "label": "Total Allocated Credit", "read_only": 1 }, { @@ -231,11 +241,65 @@ "hidden": 1, "label": "Bank", "options": "Bank" + }, + { + "fieldname": "party_section", + "fieldtype": "Section Break", + "label": "Payment From / To" + }, + { + "allow_on_submit": 1, + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType" + }, + { + "allow_on_submit": 1, + "fieldname": "party", + "fieldtype": "Dynamic Link", + "label": "Party", + "options": "party_type" + }, + { + "fieldname": "column_break_3czf", + "fieldtype": "Column Break" + }, + { + "fieldname": "bank_party_name", + "fieldtype": "Data", + "label": "Party Name/Account Holder (Bank Statement)" + }, + { + "fieldname": "bank_party_iban", + "fieldtype": "Data", + "label": "Party IBAN (Bank Statement)" + }, + { + "fieldname": "bank_party_account_number", + "fieldtype": "Data", + "label": "Party Account No. (Bank Statement)" + }, + { + "fieldname": "deposit", + "fieldtype": "Currency", + "hidden": 1, + "label": "Deposit", + "oldfieldname": "debit", + "options": "currency" + }, + { + "fieldname": "withdrawal", + "fieldtype": "Currency", + "hidden": 1, + "label": "Withdrawal", + "oldfieldname": "credit", + "options": "currency" } ], "is_submittable": 1, "links": [], - "modified": "2023-10-16 11:44:31.419057", + "modified": "2023-12-11 10:20:42.555879", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 5ed85904557..a20f2c84b4f 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -28,14 +28,20 @@ class BankTransaction(StatusUpdater): bank: DF.Link | None bank_account: DF.Link bank_account_head: DF.Link | None + bank_party_account_number: DF.Data | None + bank_party_iban: DF.Data | None + bank_party_name: DF.Data | None category: DF.Link | None company: DF.Link credit: DF.Currency currency: DF.Link date: DF.Date debit: DF.Currency + deposit: DF.Currency description: DF.SmallText | None naming_series: DF.Literal["ACC-BTN-.YYYY.-"] + party: DF.DynamicLink | None + party_type: DF.Link | None payment_entries: DF.Table[BankTransactionPayments] reference_number: DF.Data | None status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled", "Closed"] @@ -43,37 +49,71 @@ class BankTransaction(StatusUpdater): total_debit: DF.Currency transaction_type: DF.Data | None unallocated_amount: DF.Currency + withdrawal: DF.Currency # end: auto-generated types - def after_insert(self): - self.unallocated_amount = flt(self.credit) - flt(self.debit) + def before_validate(self): + self.set_allocation_in_bank_transaction() def before_insert(self): self.check_similar_entries() self.check_transaction_references() + def validate(self): + # Keep for backward compatibility + if not (self.debit or self.credit) and (self.deposit or self.withdrawals): + self.credit = self.deposit + self.debit = self.withdrawals + else: + self.deposit = self.credit + self.withdrawals = self.debit + + self.validate_duplicate_references() + + def validate_duplicate_references(self): + """Make sure the same voucher is not allocated twice within the same Bank Transaction""" + if not self.payment_entries: + return + + pe = [] + for row in self.payment_entries: + reference = (row.payment_document, row.payment_entry) + if reference in pe: + frappe.throw( + _("{0} {1} is allocated twice in this Bank Transaction").format( + row.payment_document, row.payment_entry + ) + ) + pe.append(reference) + def before_save(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() self.check_reconciliation_amounts() - def on_submit(self): - self.check_reconciliation_amounts() + def before_submit(self): self.set_allocation_in_linked_docs() + self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + + def on_submit(self): self.set_payment_entries_clearance_date() self.set_status() + if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): + self.auto_set_party() + def before_update_after_submit(self): self.check_bank_account_head() self.check_payment_types() self.calculate_totals() - def on_update_after_submit(self): - self.check_reconciliation_amounts() self.set_allocation_in_linked_docs() + self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + + def on_update_after_submit(self): self.set_payment_entries_clearance_date() self.set_status(update=True) @@ -83,6 +123,7 @@ class BankTransaction(StatusUpdater): self.payment_entries = [] def on_cancel(self): + self.remove_payment_entries() self.set_status(update=True) def check_similar_entries(self): @@ -220,16 +261,13 @@ class BankTransaction(StatusUpdater): transaction_amount = flt(self.credit) - flt(self.debit) - self.db_set("allocated_amount", flt(allocated_amount) if allocated_amount else 0) - self.db_set( - "unallocated_amount", - (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount, + self.allocated_amount = flt(allocated_amount) if allocated_amount else 0 + self.unallocated_amount = ( + (transaction_amount - flt(allocated_amount)) if allocated_amount else transaction_amount ) if transaction_amount == self.allocated_amount: - self.db_set("status", "Reconciled") - - self.reload() + self.status = "Reconciled" def check_reconciliation_amounts(self): for payment_entry in self.payment_entries: @@ -330,6 +368,36 @@ class BankTransaction(StatusUpdater): self.date = auto_repeat_doc.next_schedule_date self.reference_number = f"""{formatdate(self.date, "YYYYMMDD")}-{frappe.generate_hash("", 10)}""" + @frappe.whitelist() + def remove_payment_entries(self, cancel=False): + for payment_entry in self.payment_entries: + self.remove(payment_entry) + + if cancel: + self.run_method("before_update_after_submit") + self.run_method("on_update_after_submit") + else: + self.save() + + def auto_set_party(self): + from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty + + if self.party_type and self.party: + return + + result = AutoMatchParty( + bank_party_account_number=self.bank_party_account_number, + bank_party_iban=self.bank_party_iban, + bank_party_name=self.bank_party_name, + description=self.description, + deposit=self.deposit, + ).match() + + if not result: + return + + self.party_type, self.party = result + def get_unreconciled_amount(payment_entry): return frappe.db.get_value( diff --git a/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py new file mode 100644 index 00000000000..36ef1fca074 --- /dev/null +++ b/erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py @@ -0,0 +1,151 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import nowdate + +from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account + + +class TestAutoMatchParty(FrappeTestCase): + @classmethod + def setUpClass(cls): + create_bank_account() + frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1) + frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1) + return super().setUpClass() + + @classmethod + def tearDownClass(cls): + frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0) + frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0) + + def test_match_by_account_number(self): + create_supplier_for_match(account_no="000000003716541159") + doc = create_bank_transaction( + withdrawal=1200, + transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b", + account_no="000000003716541159", + iban="DE02000000003716541159", + ) + + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "John Doe & Co.") + + def test_match_by_iban(self): + create_supplier_for_match(iban="DE02000000003716541159") + doc = create_bank_transaction( + withdrawal=1200, + transaction_id="c5455a224602afaa51592a9d9250600d", + account_no="000000003716541159", + iban="DE02000000003716541159", + ) + + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "John Doe & Co.") + + def test_match_by_party_name(self): + create_supplier_for_match(supplier_name="Jackson Ella W.") + doc = create_bank_transaction( + withdrawal=1200, + transaction_id="1f6f661f347ff7b1ea588665f473adb1", + party_name="Ella Jackson", + iban="DE04000000003716545346", + ) + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "Jackson Ella W.") + + def test_match_by_description(self): + create_supplier_for_match(supplier_name="Microsoft") + doc = create_bank_transaction( + description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536", + withdrawal=1200, + transaction_id="8df880a2d09c3bed3fea358ca5168c5a", + party_name="", + ) + self.assertEqual(doc.party_type, "Supplier") + self.assertEqual(doc.party, "Microsoft") + + def test_skip_match_if_multiple_close_results(self): + create_supplier_for_match(supplier_name="Adithya Medical & General Stores") + create_supplier_for_match(supplier_name="Adithya Medical And General Stores") + + doc = create_bank_transaction( + description="Paracetamol Consignment, SINV-0009", + withdrawal=24.85, + transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9", + party_name="Adithya Medical & General", + ) + + # Mapping is skipped as both Supplier names have the same match score + self.assertEqual(doc.party_type, None) + self.assertEqual(doc.party, None) + + +def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None): + if frappe.db.exists("Supplier", {"supplier_name": supplier_name}): + # Update related Bank Account details + if not (iban or account_no): + return + + frappe.db.set_value( + dt="Bank Account", + dn={"party": supplier_name}, + field={"iban": iban, "bank_account_no": account_no}, + ) + return + + # Create Supplier and Bank Account for the same + supplier = frappe.new_doc("Supplier") + supplier.supplier_name = supplier_name + supplier.supplier_group = "Services" + supplier.supplier_type = "Company" + supplier.insert() + + if not frappe.db.exists("Bank", "TestBank"): + bank = frappe.new_doc("Bank") + bank.bank_name = "TestBank" + bank.insert(ignore_if_duplicate=True) + + if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"): + bank_account = frappe.new_doc("Bank Account") + bank_account.account_name = supplier.name + bank_account.bank = "TestBank" + bank_account.iban = iban + bank_account.bank_account_no = account_no + bank_account.party_type = "Supplier" + bank_account.party = supplier.name + bank_account.insert() + + +def create_bank_transaction( + description=None, + withdrawal=0, + deposit=0, + transaction_id=None, + party_name=None, + account_no=None, + iban=None, +): + doc = frappe.new_doc("Bank Transaction") + doc.update( + { + "doctype": "Bank Transaction", + "description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G", + "date": nowdate(), + "withdrawal": withdrawal, + "deposit": deposit, + "currency": "INR", + "bank_account": "Checking Account - Citi Bank", + "transaction_id": transaction_id, + "bank_party_name": party_name, + "bank_party_account_number": account_no, + "bank_party_iban": iban, + } + ) + doc.insert() + doc.submit() + doc.reload() + + return doc diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index e5ae1e11f7f..dbc9a6dd602 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document -from frappe.utils import add_months, formatdate, getdate, sbool, today, cint +from frappe.utils import add_months, cint, formatdate, getdate, sbool, today from plaid.errors import ItemError from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account @@ -256,8 +256,8 @@ def new_bank_transaction(transaction): "doctype": "Bank Transaction", "date": getdate(transaction["date"]), "bank_account": bank_account, - "debit": deposit, - "credit": withdrawal, + "credit": deposit, + "debit": withdrawal, "currency": transaction["iso_currency_code"], "reference_number": transaction["transaction_id"], "transaction_type": ( diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 021eaa12094..c07583c8f8b 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -81,7 +81,9 @@ "mode_of_payment", "bank_details_section", "bank_name", + "column_break_heye", "bank_ac_no", + "iban", "personal_details", "marital_status", "family_background", @@ -808,6 +810,16 @@ "fieldname": "column_break_104", "fieldtype": "Column Break" }, + { + "fieldname": "column_break_heye", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.salary_mode == 'Bank'", + "fieldname": "iban", + "fieldtype": "Data", + "label": "IBAN" + }, { "fieldname": "column_break_deui", "fieldtype": "Column Break" @@ -823,7 +835,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2023-11-17 09:47:27.355960", + "modified": "2023-12-11 10:29:00.809067", "modified_by": "Administrator", "module": "Setup", "name": "Employee", diff --git a/pyproject.toml b/pyproject.toml index 88a915219a9..13cfd8899d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "pycountry~=22.3.5", "Unidecode~=1.3.6", "barcodenumber~=0.5.0", + "rapidfuzz~=2.15.0", "holidays~=0.28", # integration dependencies -- GitLab From 50674295715d39932a19eda6846b9fe9610f3de4 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 12:22:08 +0100 Subject: [PATCH 092/155] fix: small fixes in bank transaction --- .../accounts/doctype/bank_transaction/bank_transaction.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index a20f2c84b4f..4256a39baef 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -61,12 +61,12 @@ class BankTransaction(StatusUpdater): def validate(self): # Keep for backward compatibility - if not (self.debit or self.credit) and (self.deposit or self.withdrawals): + if not (self.debit or self.credit) and (self.deposit or self.withdrawal): self.credit = self.deposit - self.debit = self.withdrawals + self.debit = self.withdrawal else: self.deposit = self.credit - self.withdrawals = self.debit + self.withdrawal = self.debit self.validate_duplicate_references() @@ -81,7 +81,7 @@ class BankTransaction(StatusUpdater): if reference in pe: frappe.throw( _("{0} {1} is allocated twice in this Bank Transaction").format( - row.payment_document, row.payment_entry + _(row.payment_document), row.payment_entry ) ) pe.append(reference) -- GitLab From 683d5c8aeed7f437b84a7a015f8ba4d95cff54fe Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 12:22:55 +0100 Subject: [PATCH 093/155] chore: french translations --- erpnext/translations/fr.csv | 44 ++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index e8dbbc25535..16c008aa6dc 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -820,6 +820,7 @@ Accounting Journal adjustment in progress,Ajustement de journal comptable en cou Accounting Journal in Dokos,Journaux comptables dans Dokos, Accounting Journal in FEC,Journaux comptables dans le FEC, Accounting Journal,Journal comptable, +Accounting Ledger Preview,Aperçu du grand livre, Accounting Ledger,Grand livre, Accounting Masters,Données de base, Accounting Period overlaps with {0},La période comptable chevauche {0}, @@ -846,6 +847,7 @@ Accounts Receivable Credit Account,Compte débiteur à créditer, Accounts Receivable Discounted Account,Compte débiteur d'affacturage, Accounts Receivable Summary,Récapitulatif des comptes clients, Accounts Receivable Unpaid Account,Compte débiteur d'impayés, +Accounts Receivable/Payable,Comptes clients/fournisseurs, Accounts Receivable,Comptes clients, Accounts Settings,Paramètres de comptabilité, Accounts User,Comptable, @@ -1049,6 +1051,7 @@ Additional Salary Component Exists.,Une composante de paie additionnelle existe, Additional Salary ,Salaire additionnel, Additional Salary for referral bonus can only be created against Employee Referral with status {0},Les salaires additionnels pour les bonus de recommandation ne peuvent être créé qu'en contrepartie d'une recommandation au statut {0}, Additional Salary,Salaire supplémentaire, +Additional Settings,Paramètres additionnels, Additional contact information,Informations de contact supplémentaires, Additional information regarding the customer.,Informations supplémentaires concernant le client., Address & Contact,Adresse & Contact, @@ -1486,6 +1489,7 @@ Approving Role (above authorized value),Rôle Approbateur (valeurs autorisées c Approving Role cannot be same as role the rule is Applicable To,Le Rôle Approbateur ne peut pas être identique au rôle dont la règle est Applicable, Approving User (above authorized value),Utilisateur Approbateur (valeurs autorisées ci-dessus), Approving User cannot be same as user the rule is Applicable To,L'Utilisateur Approbateur ne peut pas être identique à l'utilisateur dont la règle est Applicable, +Approximately match the description/party name against parties,Cherche une correspondance approximative entre la description/nom du tiers et les tiers disponibles dans Dokos, "Apps using current key won't be able to access, are you sure?","Les applications utilisant la clé actuelle ne pourront plus y accéder, êtes-vous sûr?", April,Avril, Arabic name missing for {} in the company document,Nom arabe manquant pour {} dans le document de l'entreprise, @@ -1638,6 +1642,8 @@ Associated Document,Document associé, At least one mode of payment is required for POS invoice.,Au moins un mode de paiement est nécessaire pour une facture de point de vente, At least one of the Applicable Modules should be selected,Sélectionnez au moins un module, At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2},A la ligne #{0}: le numéro de séquence {1} ne peut pas être inférieur au numéro de séquence de la ligne précédente {2}, +At row {0}: Batch No is mandatory for Item {1},A la ligne {0}: Le numéro de lot est obligatoire pour l'article {1}, +At row {0}: Serial No is mandatory for Item {1},A la ligne {0}: Le numéro de série est obligatoire pour l'article {1}, Atleast one asset has to be selected.,Au moins un actif doit être sélectionné, Atleast one interview has to be selected.,Au moins un entretien doit être sélectionné., Atleast one invoice has to be selected.,Au moins une facture doit être sélectionnée, @@ -1679,7 +1685,9 @@ Authorized Signatory,Signataire Autorisé, Authorized Value,Valeur Autorisée, Auto Create Assets on Purchase,Créer automatiquement des actifs à l'achat, Auto Create Exchange Rate Revaluation,Créer une réévaluation automatique du taux de change, +Auto Create Purchase Receipt,Création automatique des reçus d'achat, Auto Create Serial and Batch Bundle For Outward,Créer des ensembles de numéro de série et lot automatiquement pour les sorties de stock, +Auto Create Subcontracting Order,Création automatique des commandes de sous-traitances, Auto Created Serial and Batch Bundle,Créer des ensembles de numéro de série et lot automatiquement, Auto Created,Créé automatiquement, Auto Creation of Contact,Création automatique d'un contact, @@ -1704,6 +1712,7 @@ Auto close Opportunity Replied after the no. of days mentioned above,Fermeture a Auto close Opportunity after 15 days,Fermer automatiquement les opportunités après 15 jours, Auto close Opportunity after the no. of days mentioned above,Fermer l'opportunité automatiquement après le nombre de jours indiqués ci-dessus, Auto insert Price List rate if missing,Insérer automatiquemenet le prix dans la liste de prix s'il n'existe pas, +Auto match and set the Party in Bank Transactions,Chercher automatique une correspondance de tiers dans les transactions bancaires, Auto re-order,Réapprovisionnement automatique, Auto reconcile open entries,Réconcilier les écritures non réconciliées, Auto repeat document updated,Document de répétition automatique mis à jour, @@ -1927,6 +1936,7 @@ Bank account,Compte bancaire, Bank accounts added,Comptes bancaires ajoutés, Bank and Payments,Banque et Paiements, Bank reconciliation dashboard,Tableau de bord de rapprochement bancaire, +Bank reconciliation,Réconciliation bancaire, Bank statement balance,Solde du relevé bancaire, Bank transaction creation error,Erreur de création d'une transaction bancaire, Bank transactions creation in progress. Please wait...,Création des transactions bancaire en cours. Veuillez patienter..., @@ -1984,6 +1994,7 @@ Batch ID is mandatory,Le N° du lot est obligatoire, Batch ID,ID du Lot, Batch Identification,Identification par lots, Batch Item Expiry Status,Statut d'expiration des articles en lots, +Batch No is mandatory,Le numéro de lot est obligatoire, Batch No.,N° de lot, Batch No,N° du lot, Batch Nos are created successfully,Les n° de lot ont été créé avec succès, @@ -2647,6 +2658,7 @@ Column Labels : ,Libellés des colonnes, Column filters ignored,Filtres de colonne ignorés, Column in Bank File,Colonne dans le fichier de la banque, Combined invoice portion must equal 100%,La portion combinée de la facture doit être égale à 100%, +Comma,Virgule, Comments,Commentaires, Commercial,Commercial, Commission Rate %,Pourcentage de commission (%), @@ -3682,6 +3694,7 @@ Dependent Task {0} is not a Template Task,La tâche dépendant {0} n'est pas un Dependent Task,Tâche Dépendante, Dependent Tasks,Tâche dépendante, Depends on Tasks,Dépend des Tâches, +Deposit,Dépôt, Deprecated field. Will be removed in a future version.,Champ déprécié. Sera supprimé dans une future version., Deprecated: Kept for compatibility only but will be removed in the future,"Obsolète: Gardé uniquement pour compatibilité, mais sera supprimé à l'avenir", Depreciated Amount,Montant amorti, @@ -3737,6 +3750,7 @@ Difference Account,Compte d'écart, Difference Amount (Company Currency),Écart de montant (Devise de la Société), Difference Amount must be zero,L'Écart de montant doit être égal à zéro, Difference Amount,Écart de montant, +Difference In,Différence en, Difference Posting Date,Date de comptabilisation de la différence, Difference Qty,Qté de différence, Difference Value,Différence de valeur, @@ -3935,6 +3949,7 @@ Driver,Chauffeur, Driving License Categories,Catégories de permis de conduire, Driving License Category,Catégorie de permis de conduire, Drop Ship,Expédition Directe, +Drop a file,Déposez un fichier, Drop files here,Déposez des fichiers ici, Drug,Médicament, Due / Reference Date cannot be after {0},Date d'échéance / de référence ne peut pas être après le {0}, @@ -4121,6 +4136,7 @@ Enable Appointment Scheduling,Autoriser la prise de rendez-vous, Enable Attribute Filters,Autoriser les filtres d'attributs, Enable Auto Email,Autoriser les emails automatiques, Enable Auto Re-Order,Autoriser le réapprovisonnement automatique, +Enable Automatic Party Matching,Autoriers la correspondance automatique de tiers, Enable Capital Work in Progress Accounting,Autoriser la comptabilisation des immobilisations en cours, Enable Checkout,Activer Caisse, Enable Common Party Accounting,Activer la comptabilité des tiers communs, @@ -4133,6 +4149,7 @@ Enable Distributed Cost Center,Activer la distribution du centre de coût, Enable European Access,Autoriser l'accès aux pays européens, Enable Field Filters (Categories),Activer les filtres de champ (Catégories), Enable Field Filters,Autoriser les filtres de champ, +Enable Fuzzy Matching,Autoriser la recherche approximative, Enable Item Booking,Autoriser la réservation d'articles, Enable Perpetual Inventory For Non Stock Items,Autoriser l'inventaire permanent pour les articles non stockés, Enable Perpetual Inventory,Autoriser l'inventaire permanent, @@ -4492,6 +4509,7 @@ Fetched only {0} available serial numbers.,Seulement {0} numéros de série disp Fetching exchange rates ...,Récupération des taux de change..., Fetching...,Récupération..., Fichier des Ecritures Comptables,Fichier des Ecritures Comptables, +Field Delimiter,Délimiteur de champs, Field Name,Nom du champ, Field in Bank Transaction,Champ dans la transaction bancaire, Field,Champs, @@ -4754,6 +4772,7 @@ From date must be before To date,La date de début doit être avant la date de f From date must be within fiscal year {0},La date de début doit être dans l'exercice fiscal {0}, From duration (Mins),Durée minimum (Mins), From employee is required while receiving Asset {0} to a target location,L'employé source est nécessaire pour recevoir l'actif {0} dans le lieu cible, +From payment order,Depuis l'ordre de paiement, From value must be less than to value in row {0},De la Valeur doit être inférieure à la valeur de la ligne {0}, From {0} | {1} {2},Du {0} | {1} {2}, From(Year),Du (Année), @@ -5180,6 +5199,7 @@ How Pricing Rule is applied?,Comment est appliquée la règle de prix ?, How frequently?,A quelle fréquence ?, How often should Project and Company be updated based on Sales Transactions ?,A quelle fréquence le projet et la société doivent-ils être mis à jour sur la base des transactions de vente ?, How often should Project and Company be updated based on Sales Transactions?,À quelle fréquence le projet et la société doivent-ils être mis à jour en fonction des transactions de vente ?, +How often should Project be updated of Total Purchase Cost ?,A quelle fréquence doit-on mettre à jour le coût total des achats d'un projet ?, How often should project and company be updated based on Sales Transactions.,Fréquence de mise à jour des montants de transaction de vente dans les projets et la société., How to Navigate in ERPNext,Comment naviguer dans Dokos, Hrs,Hrs, @@ -5398,6 +5418,7 @@ Include Ageing Summary,Inclure un récapitulatif agé, Include Default Book Assets,Include les actifs du livre comptable par défaut, Include Default Book Entries,Inclure les écritures du livre comptable par défaut, Include Descendants,Inclure les descendants, +Include Disabled,Inclure les désactivés, Include Expired,Inclure les devis expirés, Include Exploded Items,Inclure les articles éclatés, Include Item In Manufacturing,Inclure l'article dans la production, @@ -6377,10 +6398,14 @@ Loss Value,Valeur de la perte, Lost Opportunities,Opportunités perdues, Lost Opportunity,Opportunités perdues, Lost Quotation,Devis Perdu, +Lost Quotations %,% de devis perdus, +Lost Quotations,Devis perdus, Lost Reason Detail,Détail de la raison de la perte, Lost Reason,Raison de la Perte, Lost Reasons are required in case opportunity is Lost.,Les raisons de la perte sont requises en cas de perte d'opportunité., Lost Reasons,Raisons pour avoir perdu le devis, +Lost Value %,% de valeur perdue, +Lost Value,Valeur perdue, Lost,Perdu, Low,Bas, Lower Deduction Certificate,Certificat de déduction minorée, @@ -7620,6 +7645,7 @@ Parent Detail docname,Nom de document du détail parent, Parent Document,Document parent, Parent Item Booking,Réservation d'article parente, Parent Item Group,Groupe d'articles parent, +Parent Item {0} must not be a Fixed Asset,L'article parent {0} ne doit pas être un actif immobilisé, Parent Item {0} must not be a Stock Item,L'article parent {0} ne doit pas être un article stocké, Parent Item,Article Parent, Parent Location,Localisation parente, @@ -7659,13 +7685,16 @@ Partner Type,Type de Partenaire, Partner website,Site Partenaire, Partnership,Partenariat, Party Account Currency,Devise du Compte de Tiers, +Party Account No. (Bank Statement),Compte bancaire du tiers (Relevé bancaire), Party Account {0} currency ({1}) and document currency ({2}) should be same,La devise ({1}) du compte de tiers {0} et la devise du document ({2}) doivent être identiques, Party Account,Compte de Tiers, Party Balance,Solde du Tiers, Party Bank Account,Compte bancaire du tiers, Party Details,Détails du tiers, +Party IBAN (Bank Statement),IBAN du tiers (Relevé bancaire), Party Information,Informations additionnelles, Party Link,Lien avec la partie, +Party Name/Account Holder (Bank Statement),Nom du tiers/Titulaire du compte (Relevé bancaire), Party Name,Nom du tiers, Party Specific Item,Articles liés au tiers, Party Type and Party is mandatory for {0} account,Le type de tiers et le tiers sont obligatoires pour le compte {0}, @@ -9248,6 +9277,7 @@ Relieving Date must be greater than or equal to Date of Joining,La date de dépa Relieving Date,Date de départ, Remaining Balance,Solde restant, Remark,Remarque, +Remarks Column Length,Longueur de la colonne Remarques, Remarks,Remarques, Remember to set {field_label}. It is required by {regulation}.,N'oubliez pas de définir {field_label}. C'est obligatoire d'après {regulation}, Reminder to update GSTIN Sent,Rappel pour mettre à jour GSTIN envoyé, @@ -10323,6 +10353,7 @@ Selling ,Vente, "Selling must be checked, if Applicable For is selected as {0}","Vente doit être vérifiée, si ""Applicable pour"" est sélectionné comme {0}", Selling rate for item {0} is lower than its {1}. Selling rate should be atleast {2},Le prix de vente pour l'élément {0} est inférieur à son {1}. Le prix de vente devrait être au moins {2}, Selling,Vente, +Semicolon,Point Virgule, Send After (days),Envoyer après (jours), Send Attached Files,Envoyer les pièces jointes, Send Document Print,Envoyer une impression de document, @@ -10360,6 +10391,7 @@ Sepa Direct Debit Settings,Paramètres de prélèvement Sepa, Sepa Direct Debit,Prélèvement Sepa, Sepa Mandate,Mandat Sepa, Sepa Mandates,Mandats SEPA, +Sepa Payments,Paiement SEPA, Sepa mandate retrieval error,Erreur de récupération d'un mandat Sepa, Sepa mandate status update error,Erreur de mise à jour du statut d'un mandat Sepa, Sepa mandate validation failed for {0},La validation du mandat Sepa échoué pour {0}, @@ -11315,7 +11347,6 @@ Supplier Naming By,Nommage du fournisseur selon, Supplier Part No,N° de Pièce du Fournisseur, Supplier Part Number,Numéro de Pièce du Fournisseur, Supplier Portal Users,Utilisateurs du portail fournisseur, -Supplier Unit Price,Prix unitaire du fournisseur, Supplier Price,Prix du fournisseur, Supplier Primary Address,Adresse principale du fournisseur, Supplier Primary Contact,Contact principal du fournisseur, @@ -11336,6 +11367,7 @@ Supplier Scorecard Variable,Variable de la Fiche d'Évaluation Fournisseur, Supplier Scorecard,Fiche d'évaluation des fournisseurs, Supplier Scorecards,Evaluation des fournisseurs, Supplier Type,Type de Fournisseur, +Supplier Unit Price,Prix unitaire du fournisseur, Supplier Warehouse mandatory for sub-contracted Purchase Receipt,Entrepôt Fournisseur obligatoire pour les Reçus d'Achat sous-traités, Supplier Warehouse mandatory for sub-contracted {0},Entrepôt fournisseur obligatoire pour le·la {0} sous-traité, Supplier Warehouse,Entrepôt Fournisseur, @@ -11415,6 +11447,7 @@ TLB-,TLB-, TS-.YYYY.-,TS-.YYYY.-, TS-,TS-, TVA-.YYYY.-.MM.-,TVA-.YYYY.-.MM.-, +Tab,Tabulation, Table for Item that will be shown in Web Site,Table pour l'Article qui sera affiché sur le site Web, Tabs,Séparateur d'onglet, Tag Line,Slogan, @@ -12099,6 +12132,8 @@ Total Additional Costs,Total des coûts additionnels, Total Advance,Acompte/avance total, Total Allocated Amount (Company Currency),Montant total alloué (Devise société), Total Allocated Amount,Montant total alloué, +Total Allocated Credit,Crédit alloué total, +Total Allocated Debit,Débit alloué total, Total Allocations,Allocations totales, Total Amount Credited,Montant total crédité, Total Amount Currency,Montant total en devise, @@ -12217,6 +12252,7 @@ Total Projected Qty,Qté Totale Prévue, Total Projected Quantity in Stock,Quantité projetée totale en stock, Total Purchase Amount,Montant total des achats, Total Purchase Cost (via Purchase Invoice),Coût d'Achat Total (via les factures d'achat), +Total Purchase Cost has been updated,Le coût d'achats total a été mis à jour, Total Purchases Last Month,Total Achats du mois dernier, Total Purchases This Month,Total Achats du mois en cours, Total Purchases This Year,Total Achats cette année, @@ -12486,7 +12522,9 @@ Unrealized Profit / Loss Account,Compte de bénéfice/perte non réalisée, Unrealized Profit / Loss account for intra-company transfers,Bénéfice/perte non réalisé pour les transferts intra-société, Unrealized Profit/Loss account for intra-company transfers,Compte de bénéfice/perte non réalisé pour les transferts intra-société, Unreconcile Payment Entries,Dé-réconcilier les écritures de paiement, +Unreconcile Payment,Dé-réconcilier le paiement, Unreconcile Payments,Dé-réconcilier les paiements, +Unreconcile Transaction,Dé-réconcilier la transaction, Unreconciled Amount,Montant non rapproché, Unreconciled Entries,Entrées non réconciliées, Unreconciled Payment Details,Détails des paiements non rapprochés, @@ -12552,6 +12590,7 @@ Update Total Purchase Cost,Mettre à jour le coût total des achats, Update Woocommerce products from Dokos items,Mettre à jour les produits WooCommerce à partir des articles Dokos, Update bank payment dates with journals.,Mettre à jour les dates de paiement bancaires avec les journaux., Update data every (Days),Mettre à jour tous les (Jours), +Update frequency of Project,Mettre à jour la fréquence du projet, Update in progress. It might take a while.,Mise à jour en cours. Ça peut prendre un moment., Update latest price in all BOMs,Mettre à jour le prix le plus récent dans toutes les nomenclatures, Update rate as per last purchase,Mettre à jour les prix selon le dernier prix achat, @@ -12946,6 +12985,7 @@ Wishlist is empty!,La liste de préférences est vide !, Wishlist,Liste de préférences, With Items,Avec Articles, With Operations,Avec des Opérations, +Withdrawal,Retrait, Without category,Sans catégorie, Won Opportunities,Opportunités gagnées, Won Opportunity (Last 1 Month),Opportunités gagnées (30 jours glissants), @@ -13324,6 +13364,7 @@ out of 5,sur 5, overwrite,écraser, pages,pages, paid_amount,paid_amount, +pain.001.001.03,pain.001.001.03, pain.008.001.02,pain.008.001.02, pain.008.002.02,pain.008.002.02, pain.008.003.02,pain.008.003.02, @@ -13540,6 +13581,7 @@ you must select Capital Work in Progress Account in accounts table,Vous devez s {0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts.,{0} {1} a déjà été partiellement payé·e. Veuillez utiliser le bouton 'Obtenir les factures en attente' ou 'Obtenir les commandes en attente' pour récupérer les derniers montants restant dûs., {0} {1} has been modified. Please refresh.,{0} {1} a été modifié. Veuillez actualiser., {0} {1} has not been submitted so the action cannot be completed,"{0} {1} n'a pas été soumis, donc l'action ne peut pas être complétée", +{0} {1} is allocated twice in this Bank Transaction,{0} {0} est alloué deux fois dans cette transaction bancaire, "{0} {1} is associated with {2}, but Party Account is {3}","{0} {1} est associé à {2}, mais le compte tiers est {3}", {0} {1} is cancelled or closed,{0} {1} est annulé ou fermé, {0} {1} is cancelled or stopped,{0} {1} est annulé ou arrêté, -- GitLab From b69a5690ce75acf2690d8171e294776c778a8222 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 12:27:49 +0100 Subject: [PATCH 094/155] fix: incorrect translations --- erpnext/translations/fr.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 16c008aa6dc..1212cf3c206 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -1489,7 +1489,7 @@ Approving Role (above authorized value),Rôle Approbateur (valeurs autorisées c Approving Role cannot be same as role the rule is Applicable To,Le Rôle Approbateur ne peut pas être identique au rôle dont la règle est Applicable, Approving User (above authorized value),Utilisateur Approbateur (valeurs autorisées ci-dessus), Approving User cannot be same as user the rule is Applicable To,L'Utilisateur Approbateur ne peut pas être identique à l'utilisateur dont la règle est Applicable, -Approximately match the description/party name against parties,Cherche une correspondance approximative entre la description/nom du tiers et les tiers disponibles dans Dokos, +Approximately match the description/party name against parties,Cherche une correspondance approximative entre la description/nom du tiers et les tiers disponibles, "Apps using current key won't be able to access, are you sure?","Les applications utilisant la clé actuelle ne pourront plus y accéder, êtes-vous sûr?", April,Avril, Arabic name missing for {} in the company document,Nom arabe manquant pour {} dans le document de l'entreprise, @@ -1712,7 +1712,7 @@ Auto close Opportunity Replied after the no. of days mentioned above,Fermeture a Auto close Opportunity after 15 days,Fermer automatiquement les opportunités après 15 jours, Auto close Opportunity after the no. of days mentioned above,Fermer l'opportunité automatiquement après le nombre de jours indiqués ci-dessus, Auto insert Price List rate if missing,Insérer automatiquemenet le prix dans la liste de prix s'il n'existe pas, -Auto match and set the Party in Bank Transactions,Chercher automatique une correspondance de tiers dans les transactions bancaires, +Auto match and set the Party in Bank Transactions,Cherche automatiquement une correspondance entre une transaction bancaire et un tiers, Auto re-order,Réapprovisionnement automatique, Auto reconcile open entries,Réconcilier les écritures non réconciliées, Auto repeat document updated,Document de répétition automatique mis à jour, @@ -4136,7 +4136,7 @@ Enable Appointment Scheduling,Autoriser la prise de rendez-vous, Enable Attribute Filters,Autoriser les filtres d'attributs, Enable Auto Email,Autoriser les emails automatiques, Enable Auto Re-Order,Autoriser le réapprovisonnement automatique, -Enable Automatic Party Matching,Autoriers la correspondance automatique de tiers, +Enable Automatic Party Matching,Autoriser la correspondance automatique de tiers, Enable Capital Work in Progress Accounting,Autoriser la comptabilisation des immobilisations en cours, Enable Checkout,Activer Caisse, Enable Common Party Accounting,Activer la comptabilité des tiers communs, -- GitLab From 771225d308c149eb7482070e469ceebb785fb2ee Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 13:40:01 +0100 Subject: [PATCH 095/155] fix: Pass argument for cancelled entries --- erpnext/accounts/doctype/bank_transaction/bank_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 4256a39baef..f72dc299cc1 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -123,7 +123,7 @@ class BankTransaction(StatusUpdater): self.payment_entries = [] def on_cancel(self): - self.remove_payment_entries() + self.remove_payment_entries(cancel=True) self.set_status(update=True) def check_similar_entries(self): -- GitLab From 63fa3693287dc36dfbdb9ce7b9b66acc1f5dc46f Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Mon, 11 Dec 2023 17:11:15 +0100 Subject: [PATCH 096/155] fix(BankReconciliation): Fix GoCardless when no description --- .../page/bank_reconciliation/gocardless_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py index 1df9868067f..3e744d3ecc7 100644 --- a/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/gocardless_reconciliation.py @@ -14,7 +14,7 @@ def reconcile_gocardless_payouts(bank_transactions): gocardless_transactions = [ transaction for transaction in bank_transactions - if "gocardless" in transaction.get("description").lower() + if "gocardless" in (transaction.get("description") or "").lower() ] if not gocardless_transactions: return -- GitLab From 99c20493c26c3cc3c8707e1a5d092b4252755736 Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Mon, 11 Dec 2023 17:34:18 +0100 Subject: [PATCH 097/155] feat(BankReconciliation): Improve filters and UI --- .../BankReconciliation.vue | 31 ++++++++- .../BankReconciliationActions.vue | 44 ++++++++----- .../BankReconciliationMatchingBox.vue | 38 ++++++++--- .../BankReconciliationTransactionList.vue | 63 ++++++++++++++----- 4 files changed, 135 insertions(+), 41 deletions(-) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliation.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliation.vue index b8ca6dae785..ffdb831c822 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliation.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliation.vue @@ -109,7 +109,7 @@ function reset_list() { } - diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue index 5947af3cb6b..92ce5177b06 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationActions.vue @@ -1,24 +1,25 @@ @@ -73,6 +74,9 @@ const documents_formatted_amount = computed(() => { }) function reconcile_entries() { + if (is_reconciliation_disabled.value || btn_clicked.value) { + return; + } btn_clicked.value = true; if ((props.selected_transactions.length == 1 && props.selected_documents.length >= 1) || (props.selected_transactions.length >= 1 && props.selected_documents.length == 1)) { @@ -118,5 +122,11 @@ function call_reconciliation() { .actions-wrapper { padding: 15px; width: 50%; + margin: 0 auto; + + background-color: var(--fg-color); + // position: sticky; + // top: var(--navbar-height); + // z-index: 99; } \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue index 24c0826d6a5..09f58cb5fe7 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationMatchingBox.vue @@ -10,10 +10,11 @@ v-for="(dt, i) in Object.keys(matching_documents)" :key="i" type="button" - class="btn btn-default" - :class="document_type==dt ? 'active': ''" - @click="change_doctype(dt)"> - {{ __(dt)}} + class="btn btn-xs px-2 py-1" + :class="document_type == dt ? 'btn-secondary-dark': 'btn-default'" + @click="change_doctype(dt)" + > + {{ __(dt)}} @@ -64,7 +65,15 @@ const columns = [ return frappe.datetime.str_to_user(cellValue) }, }, - { title: __('Party'), field: 'party', width: "20%" }, + { + title: __('Party'), + field: 'party', + width: 200, + headerFilter: "input", + headerSort: false, + headerFilterPlaceholder: __("Party"), + titleFormatter: () => "", + }, { title: __('Amount'), field: 'amount', width: "20%", formatter: function (cell, formatterParams, onRendered) { const cellValue = cell.getValue(); @@ -78,10 +87,20 @@ const columns = [ return format_currency(cellValue, cellRowData.currency) }, }, - { title: __('Reference'), field: 'reference_string', width: "30%" }, + { + title: __('Reference'), + field: 'reference_string', + width: 300, + headerFilter: "input", + headerSort: false, + headerFilterPlaceholder: __("Reference"), + titleFormatter: () => "", + }, { title: __('Reference Date'), field: 'reference_date' }, { - title: __("Link"), field: 'link', formatter: function (cell, formatterParams, onRendered) { + title: __("Link"), + field: 'link', + formatter: function (cell, formatterParams, onRendered) { const cellValue = cell.getValue(); if (!cellValue) { @@ -123,10 +142,11 @@ function get_linked_docs (match) { match: match } ).then((result) => { - const mapped_result = result.map(document => ({...document, + const mapped_result = result.map(document => ({ + ...document, date: document.posting_date, doctype: document_type.value, - link: `/app/Form/${document_type.value}/${document.name}` + link: frappe.utils.get_form_link(document_type.value, document.name), })) Object.assign(matching_documents, {[document_type.value]: mapped_result}); diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue index ada6da80089..e400e873d6d 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation_page/BankReconciliationTransactionList.vue @@ -7,9 +7,17 @@
@@ -20,8 +28,9 @@
- +
@@ -80,7 +89,7 @@ function get_transaction_list() { const mapped_transactions = r.map(transaction => ({ ...transaction, amount: transaction.unallocated_amount, - link: `/app/Form/Bank Transaction/${transaction.name}` + link: frappe.utils.get_form_link("Bank Transaction", transaction.name) })) tableData.value = mapped_transactions; @@ -110,12 +119,20 @@ function onSelectedRowsChange() { const columns = [ { - formatter: "rowSelection", titleFormatter: "rowSelection", titleFormatterParams: { + formatter: "rowSelection", + titleFormatter: "rowSelection", + cssClass: "text-center", + titleFormatterParams: { rowRange: "active" - }, hozAlign: "center", headerSort: false + }, + hozAlign: "center", + headerSort: false, }, { - title: __("Date"), field: "date", formatter: function (cell, formatterParams, onRendered) { + widthGrow: 2, + title: __("Date"), + field: "date", + formatter(cell, formatterParams, onRendered) { const cellValue = cell.getValue(); if (!cellValue) { @@ -125,9 +142,21 @@ const columns = [ return frappe.datetime.str_to_user(cellValue) }, }, - { title: __("Description"), field: "description", width: "60px", formatter: "html", headerFilter:"input" }, { - title: __("Amount"), field: "amount", formatter: function (cell, formatterParams, onRendered) { + widthGrow: 6, + title: __("Description"), + field: "description", + formatter: "html", + headerFilter: "input", + headerSort: false, + headerFilterPlaceholder: __("Description"), + titleFormatter: () => "", + }, + { + widthGrow: 2, + title: __("Amount"), + field: "amount", + formatter(cell, formatterParams, onRendered) { const cellValue = cell.getValue(); const row = cell.getRow(); const cellRowData = row.getData(); @@ -140,7 +169,10 @@ const columns = [ }, }, { - title: __("Link"), field: 'link', formatter: function (cell, formatterParams, onRendered) { + title: __("Link"), + field: 'link', + headerSort: false, + formatter(cell, formatterParams, onRendered) { const cellValue = cell.getValue(); if (!cellValue) { @@ -153,7 +185,7 @@ const columns = [ ] onMounted(() => { - tabulator.value = new Tabulator(transactionslist.value, { + const tabulatorInstance = new Tabulator(transactionslist.value, { data: tableData.value, columns: columns, selectable: true, @@ -162,6 +194,7 @@ onMounted(() => { paginationSizeSelector: [10, 20, 50, 100, true], placeholder: __("No data available for this period"), locale: true, + layout: "fitColumns", langs: { "default": { "pagination": { @@ -187,7 +220,9 @@ onMounted(() => { }, }); - tabulator.value.on("rowSelectionChanged", function (data, rows, selected, deselected) { + tabulator.value = tabulatorInstance; + + tabulatorInstance.on("rowSelectionChanged", function (data, rows, selected, deselected) { onSelectedRowsChange() }); -- GitLab From ff3c1cc91ee92a88b685959472285a79992f4e1c Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Mon, 11 Dec 2023 21:00:10 +0100 Subject: [PATCH 098/155] fix: match by party before submit --- .../accounts/doctype/bank_transaction/bank_transaction.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index f72dc299cc1..c9376e242a6 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -97,13 +97,13 @@ class BankTransaction(StatusUpdater): self.check_reconciliation_amounts() self.set_allocation_in_bank_transaction() + if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): + self.auto_set_party() + def on_submit(self): self.set_payment_entries_clearance_date() self.set_status() - if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): - self.auto_set_party() - def before_update_after_submit(self): self.check_bank_account_head() self.check_payment_types() -- GitLab From 739a5a9de0ad595c2889286dd728e03f8720a600 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:32:49 +0100 Subject: [PATCH 099/155] fix: get customers for leaderboard --- erpnext/startup/leaderboard.py | 47 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 6eaf00cdf3d..b2a93dfa9e2 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -73,12 +73,13 @@ def get_leaderboards(): @frappe.whitelist() def get_all_customers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) - return frappe.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Sales Invoice", fields=["customer as name", "sum(outstanding_amount) as value"], filters=filters, @@ -88,26 +89,20 @@ def get_all_customers(date_range, company, field, limit=None): ) else: if field == "total_sales_amount": - select_field = "sum(so_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_sold": - select_field = "sum(so_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "so.posting_date") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select so.customer as name, {0} as value - FROM `tabSales Invoice` as so JOIN `tabSales Invoice Item` as so_item - ON so.name = so_item.parent - where so.docstatus = 1 {1} and so.company = %s - group by so.customer - order by value DESC - limit %s - """.format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, + return frappe.get_list( + "Sales Order", + fields=["customer as name", f"sum({select_field}) as value"], + filters=filters, + group_by="customer", + order_by="value desc", + limit=limit, ) @@ -255,3 +250,11 @@ def get_date_condition(date_range, field): field, frappe.db.escape(from_date), frappe.db.escape(to_date) ) return date_condition + + +def parse_date_range(date_range): + if date_range: + date_range = frappe.parse_json(date_range) + return date_range[0], date_range[1] + + return None, None -- GitLab From 574c61fe2c7865e86ad5de8e41eac1c6ec014a74 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:34:24 +0100 Subject: [PATCH 100/155] fix: get items for leaderboard --- erpnext/startup/leaderboard.py | 53 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index b2a93dfa9e2..821add01562 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -110,45 +110,46 @@ def get_all_customers(date_range, company, field, limit=None): def get_all_items(date_range, company, field, limit=None): if field in ("available_stock_qty", "available_stock_value"): select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)" - return frappe.db.get_all( + results = frappe.db.get_all( "Bin", fields=["item_code as name", "{0} as value".format(select_field)], group_by="item_code", order_by="value desc", limit=limit, ) + readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name")) + return [item for item in results if item["name"] in readable_active_items] else: if field == "total_sales_amount": - select_field = "sum(order_item.base_net_amount)" - select_doctype = "Sales Invoice" + select_field = "base_net_amount" + select_doctype = "Sales Order" elif field == "total_purchase_amount": - select_field = "sum(order_item.base_net_amount)" - select_doctype = "Purchase Invoice" + select_field = "base_net_amount" + select_doctype = "Purchase Order" elif field == "total_qty_sold": - select_field = "sum(order_item.stock_qty)" - select_doctype = "Sales Invoice" + select_field = "stock_qty" + select_doctype = "Sales Order" elif field == "total_qty_purchased": - select_field = "sum(order_item.stock_qty)" - select_doctype = "Purchase Invoice" + select_field = "stock_qty" + select_doctype = "Purchase Order" - date_condition = get_date_condition(date_range, "sales_order.posting_date") + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select order_item.item_code as name, {0} as value - from `tab{1}` sales_order join `tab{1} Item` as order_item - on sales_order.name = order_item.parent - where sales_order.docstatus = 1 - and sales_order.company = %s {2} - group by order_item.item_code - order by value desc - limit %s - """.format( - select_field, select_doctype, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + child_doctype = f"{select_doctype} Item" + return frappe.get_list( + select_doctype, + fields=[ + f"`tab{child_doctype}`.item_code as name", + f"sum(`tab{child_doctype}`.{select_field}) as value", + ], + filters=filters, + order_by="value desc", + group_by=f"`tab{child_doctype}`.item_code", + limit=limit, + ) @frappe.whitelist() -- GitLab From dde38f66f045f2f5cc507d4572c682a9159465c0 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:34:54 +0100 Subject: [PATCH 101/155] fix: get suppliers for leaderboard --- erpnext/startup/leaderboard.py | 44 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 821add01562..8e919e34c45 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -154,12 +154,14 @@ def get_all_items(date_range, company, field, limit=None): @frappe.whitelist() def get_all_suppliers(date_range, company, field, limit=None): + filters = [["docstatus", "=", "1"], ["company", "=", company]] + from_date, to_date = parse_date_range(date_range) + if field == "outstanding_amount": - filters = [["docstatus", "=", "1"], ["company", "=", company]] - if date_range: - date_range = frappe.parse_json(date_range) - filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) - return frappe.db.get_all( + if from_date and to_date: + filters.append(["posting_date", "between", [from_date, to_date]]) + + return frappe.get_list( "Purchase Invoice", fields=["supplier as name", "sum(outstanding_amount) as value"], filters=filters, @@ -169,29 +171,21 @@ def get_all_suppliers(date_range, company, field, limit=None): ) else: if field == "total_purchase_amount": - select_field = "sum(purchase_order_item.base_net_amount)" + select_field = "base_net_total" elif field == "total_qty_purchased": - select_field = "sum(purchase_order_item.stock_qty)" + select_field = "total_qty" - date_condition = get_date_condition(date_range, "purchase_order.modified") + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) - return frappe.db.sql( - """ - select purchase_order.supplier as name, {0} as value - FROM `tabPurchase Invoice` as purchase_order LEFT JOIN `tabPurchase Invoice Item` - as purchase_order_item ON purchase_order.name = purchase_order_item.parent - where - purchase_order.docstatus = 1 - {1} - and purchase_order.company = %s - group by purchase_order.supplier - order by value DESC - limit %s""".format( - select_field, date_condition - ), - (company, cint(limit)), - as_dict=1, - ) # nosec + return frappe.get_list( + "Purchase Order", + fields=["supplier as name", f"sum({select_field}) as value"], + filters=filters, + group_by="supplier", + order_by="value desc", + limit=limit, + ) @frappe.whitelist() -- GitLab From 0a75b7d1e87aada89871d36afed8cfdc588a9db8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:44:24 +0100 Subject: [PATCH 102/155] fix: get sales person for leaderboard --- erpnext/startup/leaderboard.py | 40 ++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 8e919e34c45..74aed8f71ee 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,7 +1,4 @@ import frappe -from frappe import _ -from frappe.translate import get_dict, send_translations -from frappe.utils import cint def get_leaderboards(): @@ -215,24 +212,25 @@ def get_all_sales_partner(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_person(date_range, company, field=None, limit=0): - date_condition = get_date_condition(date_range, "sales_order.posting_date") - - return frappe.db.sql( - """ - select sales_team.sales_person as name, sum(sales_order.base_net_total) as value - from `tabSales Invoice` as sales_order join `tabSales Team` as sales_team - on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Invoice' - where sales_order.docstatus = 1 - and sales_order.company = %s - {date_condition} - group by sales_team.sales_person - order by value DESC - limit %s - """.format( - date_condition=date_condition - ), - (company, cint(limit)), - as_dict=1, + filters = [ + ["docstatus", "=", "1"], + ["company", "=", company], + ["Sales Team", "sales_person", "is", "set"], + ] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) + + return frappe.get_list( + "Sales Order", + fields=[ + "`tabSales Team`.sales_person as name", + "sum(`tabSales Team`.allocated_amount) as value", + ], + filters=filters, + group_by="`tabSales Team`.sales_person", + order_by="value desc", + limit=limit, ) -- GitLab From 97e07a715e11ae25600c3371137bec1a78d1380f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:45:32 +0100 Subject: [PATCH 103/155] fix: get sales partner for leaderboard --- erpnext/startup/leaderboard.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 74aed8f71ee..5df94343a6a 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -188,20 +188,20 @@ def get_all_suppliers(date_range, company, field, limit=None): @frappe.whitelist() def get_all_sales_partner(date_range, company, field, limit=None): if field == "total_sales_amount": - select_field = "sum(`base_net_total`)" + select_field = "base_net_total" elif field == "total_commission": - select_field = "sum(`total_commission`)" + select_field = "total_commission" - filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company} - if date_range: - date_range = frappe.parse_json(date_range) - filters["posting_date"] = ["between", [date_range[0], date_range[1]]] + filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]] + from_date, to_date = parse_date_range(date_range) + if from_date and to_date: + filters.append(["transaction_date", "between", [from_date, to_date]]) return frappe.get_list( "Sales Invoice", fields=[ - "`sales_partner` as name", - "{} as value".format(select_field), + "sales_partner as name", + f"sum({select_field}) as value", ], filters=filters, group_by="sales_partner", -- GitLab From e5360dc764bb4ac3edc87047d8b36783b9f7fc82 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:46:37 +0100 Subject: [PATCH 104/155] chore: deprecate unused method --- erpnext/startup/leaderboard.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 5df94343a6a..a938acfa2ce 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -1,4 +1,5 @@ import frappe +from frappe.utils.deprecations import deprecated def get_leaderboards(): @@ -234,6 +235,7 @@ def get_all_sales_person(date_range, company, field=None, limit=0): ) +@deprecated def get_date_condition(date_range, field): date_condition = "" if date_range: -- GitLab From cf8cf98e149ecc48fabf6b1ce9af4b618af7c2f4 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 12 Dec 2023 05:25:15 +0100 Subject: [PATCH 105/155] fix: typo in unittest (#38673) --- .../repost_accounting_ledger/test_repost_accounting_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index a7e4e637dd1..7c7030be49a 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -22,7 +22,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.create_item() self.update_repost_settings() - def teadDown(self): + def tearDown(self): frappe.db.rollback() def update_repost_settings(self): -- GitLab From c2efa9293f31f232bd5162548d46e24a5b513862 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:15:26 +0100 Subject: [PATCH 106/155] fix: attribute error --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 94db0b40a63..e8f2542495c 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -579,8 +579,8 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group - if args.child_doctype and item_tax_template: - out.update(get_fetch_values(args.child_doctype, "item_tax_template", item_tax_template)) + if args.get("child_doctype") and item_tax_template: + out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template)) def _get_item_tax_template(args, taxes, out=None, for_validate=False): -- GitLab From 95c939a1a3e9315fcd8e3ba40e111444345f3a4f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 11 Dec 2023 12:33:47 +0530 Subject: [PATCH 107/155] fix: close PO on SCO close --- .../doctype/subcontracting_order/subcontracting_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 6a658460da9..68c084547cb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -7,6 +7,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created +from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.stock.stock_balance import update_bin_qty from erpnext.stock.utils import get_bin @@ -218,6 +219,9 @@ class SubcontractingOrder(SubcontractingController): "Subcontracting Order", self.name, "status", status, update_modified=update_modified ) + if status == "Closed": + update_po_status("Closed", self.purchase_order) + @frappe.whitelist() def make_subcontracting_receipt(source_name, target_doc=None): -- GitLab From 2c3f7447f1d57e8992d5756b9f3578a3d71cf8ee Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Dec 2023 11:24:07 +0530 Subject: [PATCH 108/155] fix: serial no filter in the Serial No Ledger report (#38669) --- .../serial_no_ledger/serial_no_ledger.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index 8f92eab58e4..3f5216bae87 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -1,9 +1,12 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import copy + import frappe from frappe import _ +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle from erpnext.stock.stock_ledger import get_stock_ledger_entries @@ -15,8 +18,8 @@ def execute(filters=None): def get_columns(filters): columns = [ - {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"}, - {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"}, + {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120}, + {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90}, { "label": _("Voucher Type"), "fieldtype": "Data", @@ -28,7 +31,7 @@ def get_columns(filters): "fieldtype": "Dynamic Link", "fieldname": "voucher_no", "options": "voucher_type", - "width": 180, + "width": 230, }, { "label": _("Company"), @@ -48,7 +51,7 @@ def get_columns(filters): "label": _("Status"), "fieldtype": "Data", "fieldname": "status", - "width": 120, + "width": 90, }, { "label": _("Serial No"), @@ -61,7 +64,7 @@ def get_columns(filters): "label": _("Valuation Rate"), "fieldtype": "Float", "fieldname": "valuation_rate", - "width": 150, + "width": 130, }, { "label": _("Qty"), @@ -101,15 +104,29 @@ def get_data(filters): } ) - serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}] + serial_nos = [] + if row.serial_no: + parsed_serial_nos = get_serial_nos_from_sle(row.serial_no) + for serial_no in parsed_serial_nos: + if filters.get("serial_no") and filters.get("serial_no") != serial_no: + continue + + serial_nos.append( + { + "serial_no": serial_no, + "valuation_rate": abs(row.stock_value_difference / row.actual_qty), + } + ) + if row.serial_and_batch_bundle: - serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []) + serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])) for index, bundle_data in enumerate(serial_nos): if index == 0: - args.serial_no = bundle_data.get("serial_no") - args.valuation_rate = bundle_data.get("valuation_rate") - data.append(args) + new_args = copy.deepcopy(args) + new_args.serial_no = bundle_data.get("serial_no") + new_args.valuation_rate = bundle_data.get("valuation_rate") + data.append(new_args) else: data.append( { -- GitLab From 019294276119aa6c991f407e8d3f25e19d3eca5f Mon Sep 17 00:00:00 2001 From: Corentin Forler <8860073-cforler_dokos@users.noreply.gitlab.com> Date: Tue, 12 Dec 2023 11:40:42 +0100 Subject: [PATCH 109/155] fix(venue): Move venue-registration-form to Webshop --- erpnext/public/js/portal.bundle.js | 3 +- erpnext/public/js/venue_registration_form.js | 79 -------------------- 2 files changed, 1 insertion(+), 81 deletions(-) delete mode 100644 erpnext/public/js/venue_registration_form.js diff --git a/erpnext/public/js/portal.bundle.js b/erpnext/public/js/portal.bundle.js index bca8ab61871..36cd5e744ee 100644 --- a/erpnext/public/js/portal.bundle.js +++ b/erpnext/public/js/portal.bundle.js @@ -1,2 +1 @@ -import "./calendar_pages/item_booking_calendar.js" -import "./venue_registration_form.js" \ No newline at end of file +import "./calendar_pages/item_booking_calendar.js"; diff --git a/erpnext/public/js/venue_registration_form.js b/erpnext/public/js/venue_registration_form.js deleted file mode 100644 index d84c581d3a8..00000000000 --- a/erpnext/public/js/venue_registration_form.js +++ /dev/null @@ -1,79 +0,0 @@ -frappe.ready(() => { - Object.assign(frappe, { - show_subscription_template_selection: () => { - if (!frappe.web_form.get_field("subscription_templates")) { - return - } - - frappe.call({ - method: "erpnext.venue.doctype.venue_registration_form.venue_registration_form.get_recurring_website_items" - }).then(r => { - if (r.message && r.message.length) { - const template_cards = r.message.map(item => { - const buttonId = `${frappe.scrub(item.name).replace('"', '\\"')}_subscription` - const template_card = ` -
-
${item.web_item_name}
-
${item.short_description || ""}
-
- - ` - let template_image = '' - if (item.website_image) { - template_image = `${frappe.utils.escape_html(item.web_item_name)}` - } - - return `
- ${template_image} - ${template_card} -
` - }).join(""); - - frappe.web_form.get_field("subscription_templates").wrapper.innerHTML = `
- ${template_cards} - -
`; - - let prevButton = null; - const webform = document.getElementsByClassName("web-form")[0]; - webform.addEventListener("click", (e) => { - const card = e.target.closest(".subscription-template-card"); - if (!card) { return; } - - const itemName = card.getAttribute("data-subscription") - if (!itemName) { return; } - - const button = card.querySelector("button"); - if (!button) { return; } - - if (prevButton !== null && prevButton !== button) { - const prevCard = prevButton.closest(".subscription-template-card"); - prevCard.classList.remove("active"); - prevButton.classList.remove("active"); - prevButton.innerText = __("Select"); - } - - card.classList.add("active"); - button.classList.add("active"); - button.innerText = __("Selected"); - - prevButton = button; - frappe.web_form.set_value("website_item", itemName); - }) - } - }) - } - }) -}) \ No newline at end of file -- GitLab From bb0977ded95bd9cae6db5e0cdefc5a079f03f845 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 12 Dec 2023 12:06:18 +0100 Subject: [PATCH 110/155] feat: Option to set additional costs in buying price list --- .../stock/doctype/item_price/item_price.js | 20 ++++++++++ .../stock/doctype/item_price/item_price.json | 38 +++++++++++++++++-- .../stock/doctype/item_price/item_price.py | 31 +++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js index 6a7f0ba9573..fcf551afac7 100644 --- a/erpnext/stock/doctype/item_price/item_price.js +++ b/erpnext/stock/doctype/item_price/item_price.js @@ -33,5 +33,25 @@ frappe.ui.form.on("Item Price", { } } }); + }, + + additional_costs_percentage(frm) { + if (frm.no_recalculation) { + frm.no_recalculation = false + } else { + const amount = frm.doc.price_list_rate * frm.doc.additional_costs_percentage / 100.0; + frm.no_recalculation = true + frm.set_value("additional_costs_amount", amount) + } + }, + + additional_costs_amount(frm) { + if (frm.no_recalculation) { + frm.no_recalculation = false + } else { + const percent = frm.doc.price_list_rate ? frm.doc.additional_costs_amount / frm.doc.price_list_rate * 100.0 : 0; + frm.no_recalculation = true + frm.set_value("additional_costs_percentage", percent) + } } }); diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index d50d7d74645..94692f3a1a0 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -32,6 +32,11 @@ "lead_time_days", "column_break_18", "valid_upto", + "additional_costs_tab", + "additional_costs_percentage", + "column_break_tmpu", + "additional_costs_amount", + "note_tab", "section_break_24", "note", "reference" @@ -181,11 +186,11 @@ }, { "default": "0", + "description": "Deprecated field. Will be removed in a future version.", "fieldname": "lead_time_days", "fieldtype": "Int", - "label": "Lead Time in days", "hidden": 1, - "description": "Deprecated field. Will be removed in a future version." + "label": "Lead Time in days" }, { "fieldname": "column_break_18", @@ -217,13 +222,38 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "fieldname": "note_tab", + "fieldtype": "Tab Break", + "label": "Note" + }, + { + "depends_on": "eval:doc.buying", + "fieldname": "additional_costs_tab", + "fieldtype": "Tab Break", + "label": "Additional Costs" + }, + { + "fieldname": "additional_costs_percentage", + "fieldtype": "Percent", + "label": "Additional Costs Percentage" + }, + { + "fieldname": "column_break_tmpu", + "fieldtype": "Column Break" + }, + { + "fieldname": "additional_costs_amount", + "fieldtype": "Currency", + "label": "Additional Costs Amount" } ], "icon": "fa fa-flag", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-12-08 16:03:01.935070", + "modified": "2023-12-12 11:57:18.562754", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", @@ -259,7 +289,7 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC", + "sort_order": "ASC", "states": [], "title_field": "item_name", "track_changes": 1 diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 45ed41b2a63..bc2c09fc63d 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -15,6 +15,37 @@ class ItemPriceDuplicateItem(frappe.ValidationError): class ItemPrice(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + additional_costs_amount: DF.Currency + additional_costs_percentage: DF.Percent + batch_no: DF.Link | None + brand: DF.Link | None + buying: DF.Check + currency: DF.Link | None + customer: DF.Link | None + item_code: DF.Link + item_description: DF.Text | None + item_name: DF.Data | None + lead_time_days: DF.Int + note: DF.Text | None + packing_unit: DF.Int + price_list: DF.Link + price_list_rate: DF.Currency + reference: DF.Data | None + selling: DF.Check + supplier: DF.Link | None + uom: DF.Link | None + valid_from: DF.Date | None + valid_upto: DF.Date | None + # end: auto-generated types + def validate(self): self.validate_item() self.validate_dates() -- GitLab From a73d951998dc947fb13445d0b484ecb254098a4a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 10:20:26 +0530 Subject: [PATCH 111/155] refactor(test): repost utility deletion flag test --- .../test_repost_accounting_ledger.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 7c7030be49a..b3736122c70 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 @@ -175,30 +175,40 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): pe = get_payment_entry(si.doctype, si.name) pe.save().submit() - # without deletion flag set + # with deletion flag set ral = frappe.new_doc("Repost Accounting Ledger") ral.company = self.company + ral.delete_cancelled_entries = True ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) - ral.save() - - # assert preview data is generated - preview = ral.generate_preview() - self.assertIsNotNone(preview) - ral.save().submit() - # background jobs don't run on test cases. Manually triggering repost function. start_repost(ral.name) + self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) + self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) - self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) - self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + def test_05_without_deletion_flag(self): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + ) - # with deletion flag set + pe = get_payment_entry(si.doctype, si.name) + pe.save().submit() + + # without deletion flag set ral = frappe.new_doc("Repost Accounting Ledger") ral.company = self.company + ral.delete_cancelled_entries = False ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name}) ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.save().submit() start_repost(ral.name) + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) + self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) -- GitLab From 1614b673faa64760614f4f0d57ecbc76cd1c2505 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 10:34:51 +0530 Subject: [PATCH 112/155] refactor: remove explicit commit on repost --- .../repost_accounting_ledger/repost_accounting_ledger.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 95ca083d81d..547cfdd3b87 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -144,8 +144,6 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries(1) doc.make_gl_entries() - frappe.db.commit() - def get_allowed_types_from_settings(): return [ -- GitLab From 28752f34a74e6ffde94b0ae61d3805ff8ebcc22b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 11:30:33 +0530 Subject: [PATCH 113/155] refactor: increase limit and remove explicit call to start_repost --- .../repost_accounting_ledger/repost_accounting_ledger.py | 2 +- .../test_repost_accounting_ledger.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 547cfdd3b87..6f4f146a5e2 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -108,7 +108,7 @@ class RepostAccountingLedger(Document): return rendered_page def on_submit(self): - if len(self.vouchers) > 1: + if len(self.vouchers) > 5: job_name = "repost_accounting_ledger_" + self.name frappe.enqueue( method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index b3736122c70..8801737ea41 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -90,9 +90,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): # Submit repost document ral.save().submit() - # background jobs don't run on test cases. Manually triggering repost function. - start_repost(ral.name) - res = ( qb.from_(gl) .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) @@ -183,7 +180,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.save().submit() - start_repost(ral.name) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) @@ -209,6 +205,5 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.save().submit() - start_repost(ral.name) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) -- GitLab From 94429897b5515ca48a06b92fb1a510db191ef820 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 11:37:06 +0530 Subject: [PATCH 114/155] refactor(test): update repost settings before test case --- .../test_repost_accounting_ledger.py | 17 +++++++++-------- .../doctype/sales_invoice/test_sales_invoice.py | 6 ++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 8801737ea41..d95bbd412a4 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.create_company() self.create_customer() self.create_item() - self.update_repost_settings() + update_repost_settings() def tearDown(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, @@ -207,3 +200,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + + +def update_repost_settings(): + allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] + repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") + for x in allowed_types: + repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) + repost_settings.save() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 330a24599ba..3243018b122 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2801,6 +2801,12 @@ class TestSalesInvoice(FrappeTestCase): @change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self): + from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import ( + update_repost_settings, + ) + + update_repost_settings() + additional_discount_account = create_account( account_name="Discount Account", parent_account="Indirect Expenses - _TC", -- GitLab From f51c1d5fa3ff9e24210db2ac637846b4c7c219ba Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 13:35:41 +0530 Subject: [PATCH 115/155] refactor: add `get_list` for virtual child doctypes --- .../payment_reconciliation_allocation.py | 28 +++++++++++++++++++ .../payment_reconciliation_invoice.py | 20 +++++++++++++ .../payment_reconciliation_payment.py | 24 ++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py index d7b457a83a7..b57ebecbac2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py @@ -6,6 +6,34 @@ from frappe.model.document import Document class PaymentReconciliationAllocation(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + allocated_amount: DF.Currency + amount: DF.Currency + cost_center: DF.Link | None + currency: DF.Link | None + difference_account: DF.Link | None + difference_amount: DF.Currency + exchange_rate: DF.Float + gain_loss_posting_date: DF.Date | None + invoice_number: DF.DynamicLink + invoice_type: DF.Link + is_advance: DF.Data | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + reference_name: DF.DynamicLink + reference_row: DF.Data | None + reference_type: DF.Link + unreconciled_amount: DF.Currency + # end: auto-generated types + @staticmethod def get_list(args): pass diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py index b86027a294c..863f1ddf47d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py +++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py @@ -5,6 +5,26 @@ from frappe.model.document import Document class PaymentReconciliationInvoice(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + amount: DF.Currency + currency: DF.Link | None + exchange_rate: DF.Float + invoice_date: DF.Date | None + invoice_number: DF.DynamicLink | None + invoice_type: DF.Literal["Sales Invoice", "Purchase Invoice", "Journal Entry"] + outstanding_amount: DF.Currency + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + # end: auto-generated types + @staticmethod def get_list(args): pass diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py index eff7293d755..33cfd7685c8 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py @@ -5,6 +5,30 @@ from frappe.model.document import Document class PaymentReconciliationPayment(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + amount: DF.Currency + cost_center: DF.Link | None + currency: DF.Link | None + difference_amount: DF.Currency + exchange_rate: DF.Float + is_advance: DF.Data | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + posting_date: DF.Date | None + reference_name: DF.DynamicLink | None + reference_row: DF.Data | None + reference_type: DF.Link | None + remark: DF.SmallText | None + # end: auto-generated types + @staticmethod def get_list(args): pass -- GitLab From 98884d2673ae64a9c4cc4497f87ded9ba5681d69 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 12 Dec 2023 15:43:39 +0530 Subject: [PATCH 116/155] fix: typeerror on new sites (#38692) --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index a0752bb42db..f048e1bcaa0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -8,7 +8,7 @@ $.extend(erpnext, { if(!company && cur_frm) company = cur_frm.doc.company; if(company) - return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency; + return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency; else return frappe.boot.sysdefaults.currency; }, -- GitLab From 53e477c3ace70bc0176ef1f5b0a85ea0b530d4e6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Dec 2023 16:18:32 +0530 Subject: [PATCH 117/155] fix: negative batch issue (#38688) --- .../serial_and_batch_bundle.py | 7 +++- .../doctype/stock_entry/test_stock_entry.py | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index e76e79ff8e9..cb5387ed65e 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 @@ -673,6 +673,7 @@ class SerialandBatchBundle(Document): "item_code": self.item_code, "warehouse": self.warehouse, "batch_no": batches, + "consider_negative_batches": True, } ) ) @@ -683,6 +684,9 @@ class SerialandBatchBundle(Document): available_batches = get_available_batches_qty(available_batches) for batch_no in batches: if batch_no not in available_batches or available_batches[batch_no] < 0: + if flt(available_batches.get(batch_no)) < 0: + self.validate_negative_batch(batch_no, available_batches[batch_no]) + self.throw_error_message( f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}" ) @@ -1455,7 +1459,8 @@ def get_auto_batch_nos(kwargs): available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches ) - available_batches = list(filter(lambda x: x.qty > 0, available_batches)) + if not kwargs.consider_negative_batches: + available_batches = list(filter(lambda x: x.qty > 0, available_batches)) if not qty: return available_batches diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 025cd761be0..7273d45e21f 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1730,6 +1730,45 @@ class TestStockEntry(FrappeTestCase): self.assertFalse(doc.is_enqueue_action()) frappe.flags.in_test = True + def test_negative_batch(self): + item_code = "Test Negative Batch Item - 001" + make_item( + item_code, + {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"}, + ) + + se1 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se1.reload() + + batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + + se2 = make_stock_entry( + item_code=item_code, + purpose="Material Issue", + batch_no=batch_no, + qty=10, + source="_Test Warehouse - _TC", + ) + + se2.reload() + + se3 = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=100, + target="_Test Warehouse - _TC", + ) + + se3.reload() + + self.assertRaises(frappe.ValidationError, se1.cancel) + def make_serialized_item(**args): args = frappe._dict(args) -- GitLab From 3eba432a09bdd625783fd2d442ad2134c5b8bbf3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 12 Dec 2023 16:41:10 +0530 Subject: [PATCH 118/155] fix: show stock qty in popup (#38698) --- erpnext/public/js/utils/serial_no_batch_selector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 0de6774393a..3b9a551b431 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -31,7 +31,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { secondary_action: () => this.edit_full_form(), }); - this.dialog.set_value("qty", this.item.qty).then(() => { + let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; + this.dialog.set_value("qty", qty).then(() => { if (this.item.serial_no) { this.dialog.set_value("scan_serial_no", this.item.serial_no); frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', ''); -- GitLab From e71bd6135df7d5586146fa2e7b5176d12c3e2e3e Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 12 Dec 2023 20:53:37 +0530 Subject: [PATCH 119/155] fix: 1st row depr. sch. value of asset put to less than 180 days acc. to I.T. S. 32 (#38696) fix: 1st row value of asset put to less than 180 days acc. to IT S. 32 --- .../asset_depreciation_schedule.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 8d682b11383..5f0cec419c8 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -294,6 +294,10 @@ class AssetDepreciationSchedule(Document): n == 0 and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and not self.opening_accumulated_depreciation + and get_updated_rate_of_depreciation_for_wdv_and_dd( + asset_doc, value_after_depreciation, row, False + ) + == row.rate_of_depreciation ): from_date = add_days( asset_doc.available_for_use_date, -1 @@ -567,7 +571,9 @@ def get_depreciation_amount( @erpnext.allow_regional -def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row): +def get_updated_rate_of_depreciation_for_wdv_and_dd( + asset, depreciable_value, fb_row, show_msg=True +): return fb_row.rate_of_depreciation -- GitLab From bb486e08899b28b26fb490a6eaba31f1343f7525 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Dec 2023 14:06:45 +0530 Subject: [PATCH 120/155] fix: supplier removed on selection of item (#38712) --- erpnext/stock/get_item_details.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e8f2542495c..7e63f2578cd 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -367,7 +367,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "net_amount": 0.0, "discount_percentage": 0.0, "discount_amount": flt(args.discount_amount) or 0.0, - "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "update_stock": args.get("update_stock") if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"] else 0, @@ -390,6 +389,10 @@ def get_basic_details(args, item, overwrite_warehouse=True): } ) + default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults) + if default_supplier: + out.supplier = default_supplier + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): out.update(calculate_service_end_date(args, item)) -- GitLab From dd268694b5a720a1d83fd024c01804b758339953 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Dec 2023 15:08:16 +0530 Subject: [PATCH 121/155] fix: barcode scanning for the stock entry (#38716) --- erpnext/public/js/controllers/transaction.js | 1 + erpnext/public/js/utils/barcode_scanner.js | 12 ++++++------ erpnext/public/js/utils/serial_no_batch_selector.js | 5 ++++- erpnext/stock/doctype/stock_entry/stock_entry.js | 8 ++++---- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 446997c55ef..750f9a97800 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -404,6 +404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } scan_barcode() { + frappe.flags.dialog_set = false; const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); barcode_scanner.process_scan(); } diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 1f75cf0a953..140d45aca84 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -114,13 +114,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { frappe.run_serially([ () => this.set_selector_trigger_flag(data), + () => this.set_serial_no(row, serial_no), + () => this.set_batch_no(row, batch_no), + () => this.set_barcode(row, barcode), () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => { this.show_scan_message(row.idx, row.item_code, qty); }), () => this.set_barcode_uom(row, uom), - () => this.set_serial_no(row, serial_no), - () => this.set_batch_no(row, batch_no), - () => this.set_barcode(row, barcode), () => this.clean_up(), () => this.revert_selector_flag(), () => resolve(row) @@ -131,10 +131,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { // batch and serial selector is reduandant when all info can be added by scan // this flag on item row is used by transaction.js to avoid triggering selector set_selector_trigger_flag(data) { - const {batch_no, serial_no, has_batch_no, has_serial_no} = data; + const {has_batch_no, has_serial_no} = data; - const require_selecting_batch = has_batch_no && !batch_no; - const require_selecting_serial = has_serial_no && !serial_no; + const require_selecting_batch = has_batch_no; + const require_selecting_serial = has_serial_no; if (!(require_selecting_batch || require_selecting_serial)) { frappe.flags.hide_serial_batch_dialog = true; diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 3b9a551b431..7b9cdfef2a9 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -31,6 +31,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { secondary_action: () => this.edit_full_form(), }); + this.dialog.show(); + let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty; this.dialog.set_value("qty", qty).then(() => { if (this.item.serial_no) { @@ -40,9 +42,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { this.dialog.set_value("scan_batch_no", this.item.batch_no); frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', ''); } + + this.dialog.fields_dict.entries.grid.refresh(); }); - this.dialog.show(); this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); this.$scan_btn.css("display", "inline"); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 13620a3d28b..fd7105563b6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -780,10 +780,9 @@ frappe.ui.form.on('Stock Entry Detail', { }); refresh_field("items"); - let no_batch_serial_number_value = !d.serial_no; - if (d.has_batch_no && !d.has_serial_no) { - // check only batch_no for batched item - no_batch_serial_number_value = !d.batch_no; + let no_batch_serial_number_value = false; + if (d.has_serial_no || d.has_batch_no) { + no_batch_serial_number_value = true; } if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) { @@ -940,6 +939,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle } scan_barcode() { + frappe.flags.dialog_set = false; const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); barcode_scanner.process_scan(); } -- GitLab From 98d5eaff8b66014e225b852fe380b20209abf68c Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 13 Dec 2023 13:41:03 +0100 Subject: [PATCH 122/155] feat: Improve costs calculator in quotations --- erpnext/public/js/controllers/transaction.js | 12 ++-- erpnext/public/js/utils/sales_common.js | 58 +++++++++++++++-- .../quotation_item/quotation_item.json | 64 +++++++++++-------- .../doctype/quotation_item/quotation_item.py | 2 + erpnext/stock/doctype/item/item.json | 2 +- erpnext/stock/get_item_details.py | 33 +++++++--- 6 files changed, 125 insertions(+), 46 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 446997c55ef..2409b600623 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1973,10 +1973,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] || "valuation_rate"; const gross_profit_basis = item[gross_profit_basis_field]; - if (gross_profit_basis) { - const rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); - item.gross_profit = flt(((rate - gross_profit_basis) * item.stock_qty), precision("amount", item)); - } + const rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1); + const gross_profit = flt(((rate - gross_profit_basis) * item.stock_qty), precision("amount", item)); + frappe.model.set_value(item.doctype, item.name, "gross_profit", gross_profit); } } @@ -1986,7 +1985,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (item.gross_profit_percentage && item.gross_profit_calculation_rule) { const gross_profit_basis_field = gross_profit_calculation_rules[item.gross_profit_calculation_rule] const rate = item[gross_profit_basis_field] * (1 + (item.gross_profit_percentage / 100)) - frappe.model.set_value(cdt, cdn, "rate", rate); + + if (flt(item.rate, precision("rate", item)) != flt(rate, precision("rate", item))) { + frappe.model.set_value(cdt, cdn, "rate", flt(rate)); + } } } } diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 4fa6231404a..cff7bf2c5f4 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -3,6 +3,12 @@ frappe.provide("erpnext.selling"); +const base_amount_field_for_cost_price = { + "Valuation Rate": "valuation_rate", + "Last Purchase Rate": "last_purchase_rate", + "Supplier Cost Price": "unit_cost_price" +} + erpnext.sales_common = { setup_selling_controller:function() { erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController { @@ -377,6 +383,7 @@ erpnext.sales_common = { supplier(doc, cdt, cdn) { const item = locals[cdt][cdn]; + const fields_to_update = ["unit_cost_price", "additional_costs_percentage", "additional_costs_amount"] if (item.supplier) { frappe.call({ @@ -392,24 +399,65 @@ erpnext.sales_common = { } }).then(r => { if(!r.exc) { - frappe.model.set_value(cdt, cdn, "unit_cost_price", r.message?.unit_cost_price || 0.0); - frappe.model.set_value(cdt, cdn, "cost_price", r.message?.cost_price || 0.0); + fields_to_update.map(field => { + frappe.model.set_value(cdt, cdn, field, r.message[field] || 0.0); + }) } }); } else { - frappe.model.set_value(cdt, cdn, "unit_cost_price", 0.0); - frappe.model.set_value(cdt, cdn, "cost_price", 0.0); + fields_to_update.map(field => { + frappe.model.set_value(cdt, cdn, field, 0.0); + }) } } cost_price(doc, cdt, cdn) { - console.log("cost_price", doc, cdt, cdn) this.gross_profit_calculation_rule(doc, cdt, cdn); } + + unit_cost_price(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + } + + additional_costs_percentage(doc, cdt, cdn) { + const item = frappe.get_doc(cdt, cdn) + const base_amount_field = base_amount_field_for_cost_price[item.gross_profit_calculation_rule] + const amount = item[base_amount_field] * item.additional_costs_percentage / 100.0 + + if (flt(amount) != flt(item.additional_costs_amount)) { + frappe.model.set_value(cdt, cdn, "additional_costs_amount", amount); + } + } + + additional_costs_amount(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + const item = frappe.get_doc(cdt, cdn) + const base_amount_field = base_amount_field_for_cost_price[item.gross_profit_calculation_rule] + const percentage = item.additional_costs_amount / item[base_amount_field] * 100.0 + + if (flt(percentage) != flt(item.additional_costs_percentage)) { + frappe.model.set_value(cdt, cdn, "additional_costs_percentage", percentage); + } + + } + + valuation_rate(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + } + + last_purchase_rate(doc, cdt, cdn) { + calculate_cost_price(doc, cdt, cdn) + } }; } } +const calculate_cost_price = (doc, cdt, cdn) => { + const item = frappe.get_doc(cdt, cdn) + const calculation_field = base_amount_field_for_cost_price[item.gross_profit_calculation_rule]; + frappe.model.set_value(cdt, cdn, "cost_price", item.qty * (item[calculation_field] + item.additional_costs_amount)); +} + erpnext.pre_sales = { set_as_lost: function(doctype) { frappe.ui.form.on(doctype, { diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 41e473704a7..5d713b0c4de 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -52,18 +52,20 @@ "is_free_item", "is_alternative", "has_alternative_item", - "section_break_43", + "costs_section", "valuation_rate", "last_purchase_rate", "column_break_45", - "gross_profit_calculation_rule", - "gross_profit_percentage", "gross_profit", - "supplier_price_section", + "section_break_nqjy", "supplier", - "column_break_uapk", "unit_cost_price", + "additional_costs_percentage", + "additional_costs_amount", "cost_price", + "column_break_kcol", + "gross_profit_calculation_rule", + "gross_profit_percentage", "item_weight_details", "weight_per_unit", "total_weight", @@ -633,10 +635,6 @@ "no_copy": 1, "print_hide": 1 }, - { - "fieldname": "section_break_43", - "fieldtype": "Section Break" - }, { "fieldname": "valuation_rate", "fieldtype": "Currency", @@ -701,10 +699,6 @@ "label": "Last Purchase Rate", "read_only": 1 }, - { - "fieldname": "column_break_uapk", - "fieldtype": "Column Break" - }, { "fieldname": "supplier", "fieldtype": "Link", @@ -717,36 +711,54 @@ "label": "Supplier Unit Price", "read_only": 1 }, - { - "collapsible": 1, - "depends_on": "eval:doc.gross_profit_calculation_rule == \"Supplier Cost Price\"", - "fieldname": "supplier_price_section", - "fieldtype": "Section Break", - "label": "Supplier Price" - }, { "fieldname": "cost_price", "fieldtype": "Currency", - "label": "Supplier Price", + "label": "Cost Price", "read_only": 1 }, + { + "description": "Set a custom gross profit percentage to recalculate your item selling rate", + "fieldname": "gross_profit_percentage", + "fieldtype": "Percent", + "label": "Gross Profit Percentage" + }, + { + "fieldname": "additional_costs_percentage", + "fieldtype": "Percent", + "label": "Additional Costs Percentage" + }, + { + "fieldname": "additional_costs_amount", + "fieldtype": "Currency", + "label": "Additional Costs Amount" + }, + { + "fieldname": "costs_section", + "fieldtype": "Section Break", + "hide_border": 1, + "label": "Costs Calculation" + }, { "default": "Valuation Rate", "fieldname": "gross_profit_calculation_rule", "fieldtype": "Select", - "label": "Gross Profit Based On", + "label": "Cost Price Based On", "options": "Valuation Rate\nLast Purchase Rate\nSupplier Cost Price" }, { - "fieldname": "gross_profit_percentage", - "fieldtype": "Percent", - "label": "Gross Profit Percentage" + "fieldname": "section_break_nqjy", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_kcol", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-12-04 16:54:40.291026", + "modified": "2023-12-13 09:57:16.215710", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index e0367a572d1..2a011673ccb 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -16,6 +16,8 @@ class QuotationItem(Document): from frappe.types import DF actual_qty: DF.Float + additional_costs_amount: DF.Currency + additional_costs_percentage: DF.Percent additional_notes: DF.Text | None against_blanket_order: DF.Check amount: DF.Currency diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index f20176fe229..8ff9834d9ab 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1001,7 +1001,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-12-04 15:40:01.820792", + "modified": "2023-12-12 18:40:01.820792", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 94db0b40a63..38957f7f595 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -908,6 +908,7 @@ def get_item_price(args, item_code, ignore_party=False): query = ( frappe.qb.from_(ip) .select(ip.name, ip.price_list_rate, ip.uom) + .select(ip.additional_costs_percentage, ip.additional_costs_amount) .where( (ip.item_code == item_code) & (ip.price_list == args.get("price_list")) @@ -936,7 +937,7 @@ def get_item_price(args, item_code, ignore_party=False): return query.run() -def get_price_list_rate_for(args, item_code): +def get_price_list_rate_for(args, item_code, with_additional_costs=False): """ :param customer: link to Customer DocType :param supplier: link to Supplier DocType @@ -957,6 +958,7 @@ def get_price_list_rate_for(args, item_code): item_price_data = 0 price_list_rate = get_item_price(item_price_args, item_code) + if price_list_rate: desired_qty = args.get("qty") if desired_qty and check_packing_list(price_list_rate[0][0], desired_qty, item_code): @@ -980,11 +982,16 @@ def get_price_list_rate_for(args, item_code): if item_price_data: if item_price_data[0][2] == args.get("uom"): - return item_price_data[0][1] + price = item_price_data[0][1] elif not args.get("price_list_uom_dependant"): - return flt(item_price_data[0][1] * flt(args.get("conversion_factor", 1))) + price = flt(item_price_data[0][1] * flt(args.get("conversion_factor", 1))) else: - return item_price_data[0][1] + price = item_price_data[0][1] + + if with_additional_costs and len(item_price_data[0]) > 4: + return price, item_price_data[0][3], item_price_data[0][4] + + return price def check_packing_list(price_list_rate_name, desired_qty, item_code): @@ -1444,15 +1451,23 @@ def get_supplier_cost(out): price_list_args["price_list"] = buying_price_list - if not ( - unit_cost_price := get_price_list_rate_for(price_list_args, price_list_args.get("item_code")) - ): + unit_cost_price = get_price_list_rate_for( + price_list_args, price_list_args.get("item_code"), with_additional_costs=True + ) + + if not unit_cost_price: price_list_args["supplier"] = None - unit_cost_price = get_price_list_rate_for(price_list_args, price_list_args.get("item_code")) + unit_cost_price = get_price_list_rate_for( + price_list_args, price_list_args.get("item_code"), with_additional_costs=True + ) if unit_cost_price: out.update( - {"unit_cost_price": unit_cost_price, "cost_price": flt(unit_cost_price) * flt(out["qty"])} + { + "unit_cost_price": unit_cost_price[0], + "additional_costs_percentage": unit_cost_price[1], + "additional_costs_amount": unit_cost_price[2], + } ) return out -- GitLab From f150eca3db7bf796a1630fd68c78eb1e4cf2dcfa Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 13 Dec 2023 16:37:48 +0100 Subject: [PATCH 123/155] Revert "fix(ux): don't update qty blindly" This reverts commit 4a202697e29147495dca5f7fcc55e12f48e8454f. --- erpnext/public/js/controllers/transaction.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 446997c55ef..f809f19cc2b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -494,6 +494,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", + child: item, args: { doc: me.frm.doc, args: { @@ -543,19 +544,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function(r) { if(!r.exc) { frappe.run_serially([ - () => { - var child = locals[cdt][cdn]; - var std_field_list = ["doctype"] - .concat(frappe.model.std_fields_list) - .concat(frappe.model.child_table_field_list); - - for (var key in r.message) { - if (std_field_list.indexOf(key) === -1) { - if (key === "qty" && child[key]) continue; - child[key] = r.message[key]; - } - } - }, () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); -- GitLab From ed19e7bfee65347eb8b98f3a899fff5df42abd01 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 13 Dec 2023 18:02:45 +0100 Subject: [PATCH 124/155] chore: Migrate Bank transaction importer to Vue3 --- .../doctype/bank_account/bank_account.js | 10 + .../doctype/bank_account/bank_account.json | 19 +- .../doctype/bank_account/bank_account.py | 31 +++ .../BankAccountPreview.vue | 72 +++-- .../BankTransactionImporter.vue | 257 +++++++++--------- .../bank_transaction_import.js | 1 - 6 files changed, 216 insertions(+), 174 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.js b/erpnext/accounts/doctype/bank_account/bank_account.js index af68d0ffd86..d698bc28d54 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.js +++ b/erpnext/accounts/doctype/bank_account/bank_account.js @@ -12,11 +12,21 @@ frappe.ui.form.on('Bank Account', { } }; }); + frm.set_query("party_type", function() { return { query: "erpnext.setup.doctype.party_type.party_type.get_party_type", }; }); + + frm.set_query("linked_account", function() { + return { + filters: { + 'bank': frm.doc.bank, + 'disabled': 0, + } + }; + }); }, refresh: function(frm) { frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank Account' } diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 8121d4fb0e9..34be8844d79 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -17,6 +17,8 @@ "is_default", "is_company_account", "company", + "is_card", + "linked_account", "section_break_11", "party_type", "column_break_14", @@ -218,6 +220,20 @@ "fieldname": "disabled", "fieldtype": "Check", "label": "Disabled" + }, + { + "default": "0", + "fieldname": "is_card", + "fieldtype": "Check", + "label": "Is a debit/credit card" + }, + { + "depends_on": "eval:doc.is_card", + "fieldname": "linked_account", + "fieldtype": "Link", + "label": "Linked Account", + "mandatory_depends_on": "eval:doc.is_card", + "options": "Bank Account" } ], "links": [ @@ -227,7 +243,7 @@ "link_fieldname": "bank_account" } ], - "modified": "2023-09-22 21:31:34.763977", + "modified": "2023-12-13 17:11:35.859773", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", @@ -264,5 +280,6 @@ "search_fields": "bank,account", "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 6017a6235b8..5bd2c1ccaab 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -13,6 +13,37 @@ from schwifty import IBAN class BankAccount(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + account: DF.Link | None + account_name: DF.Data + account_subtype: DF.Link | None + account_type: DF.Link | None + bank: DF.Link + bank_account_no: DF.Data | None + branch_code: DF.Data | None + company: DF.Link | None + disabled: DF.Check + iban: DF.Data | None + integration_id: DF.Data | None + is_card: DF.Check + is_company_account: DF.Check + is_default: DF.Check + last_integration_date: DF.Date | None + linked_account: DF.Link | None + mask: DF.Data | None + party: DF.DynamicLink | None + party_type: DF.Link | None + swift_number: DF.Data | None + website: DF.Data | None + # end: auto-generated types + def onload(self): """Load address and contacts in `__onload`""" load_address_and_contact(self) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue index c9cd0174292..e8df911a528 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankAccountPreview.vue @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue index 992424e516b..f62e5b30e7e 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_import/BankTransactionImporter.vue @@ -1,173 +1,160 @@ -