From f46d42daa4eee725ca1338f35c7b4746019d699f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 11:54:12 +0530 Subject: [PATCH 01/24] refactor: remove balance fields from jv account --- .../journal_entry_account.json | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 66425cc9a2f..e231f1acf1c 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -10,12 +10,10 @@ "account", "accounting_journal", "account_type", - "balance", "col_break1", "bank_account", "party_type", "party", - "party_balance", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -65,17 +63,6 @@ "label": "Account Type", "print_hide": 1 }, - { - "fieldname": "balance", - "fieldtype": "Currency", - "label": "Account Balance", - "no_copy": 1, - "oldfieldname": "balance", - "oldfieldtype": "Data", - "options": "account_currency", - "print_hide": 1, - "read_only": 1 - }, { "default": ":Company", "description": "If Income or Expense", @@ -109,14 +96,6 @@ "label": "Party", "options": "party_type" }, - { - "fieldname": "party_balance", - "fieldtype": "Currency", - "label": "Party Balance", - "options": "account_currency", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "currency_section", "fieldtype": "Section Break", @@ -304,7 +283,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-23 11:44:25.841187", + "modified": "2023-10-12 11:52:46.397186", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", -- GitLab From 667fdd871b90bfddf30f12abd43e00fa4cf69ec2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:04:44 +0530 Subject: [PATCH 02/24] refactor: remove balance formatter --- .../doctype/journal_entry/journal_entry.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index c2fc905d96f..b47a0856ee9 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -246,7 +246,6 @@ var update_jv_details = function(doc, r) { $.each(r, function(i, d) { var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); frappe.model.set_value(row.doctype, row.name, "account", d.account) - frappe.model.set_value(row.doctype, row.name, "balance", d.balance) }); refresh_field("accounts"); } @@ -255,7 +254,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro onload() { this.load_defaults(); this.setup_queries(); - this.setup_balance_formatter(); erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype); } @@ -359,19 +357,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } - setup_balance_formatter() { - const formatter = function(value, df, options, doc) { - var currency = frappe.meta.get_field_currency(df, doc); - var dr_or_cr = value ? ('') : ""; - return "
" - + ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency)) - + " " + dr_or_cr - + "
"; - }; - this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter); - this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter); - } - reference_name(doc, cdt, cdn) { var d = frappe.get_doc(cdt, cdn); -- GitLab From 00cbdfa870f4b593accf7fbb29eb887aec41cec3 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:07:36 +0530 Subject: [PATCH 03/24] refactor: exclude balance while setting acc details --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 9 ++++----- erpnext/accounts/doctype/journal_entry/journal_entry.py | 5 ++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index b47a0856ee9..9cb4d050ad8 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -464,11 +464,11 @@ frappe.ui.form.on("Journal Entry Account", { } }, cost_center: function(frm, dt, dn) { - erpnext.journal_entry.set_account_balance(frm, dt, dn); + erpnext.journal_entry.set_account_details(frm, dt, dn); }, account: function(frm, dt, dn) { - erpnext.journal_entry.set_account_balance(frm, dt, dn); + erpnext.journal_entry.set_account_details(frm, dt, dn); }, debit_in_account_currency: function(frm, cdt, cdn) { @@ -657,14 +657,14 @@ $.extend(erpnext.journal_entry, { }); $.extend(erpnext.journal_entry, { - set_account_balance: function(frm, dt, dn) { + set_account_details: function(frm, dt, dn) { var d = locals[dt][dn]; if(d.account) { if(!frm.doc.company) frappe.throw(__("Please select Company first")); if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first")); return frappe.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type", args: { account: d.account, date: frm.doc.posting_date, @@ -672,7 +672,6 @@ $.extend(erpnext.journal_entry, { debit: flt(d.debit_in_account_currency), credit: flt(d.credit_in_account_currency), exchange_rate: d.exchange_rate, - cost_center: d.cost_center }, callback: function(r) { if(r.message) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 7ffc547906d..9a58dc4cc07 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1551,8 +1551,8 @@ def get_party_account_and_balance(company, party_type, party, cost_center=None): @frappe.whitelist() -def get_account_balance_and_party_type( - account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None +def get_account_details_and_party_type( + account, date, company, debit=None, credit=None, exchange_rate=None ): """Returns dict of account balance and party type to be set in Journal Entry on selection of account.""" if not frappe.has_permission("Account"): @@ -1574,7 +1574,6 @@ def get_account_balance_and_party_type( party_type = "" grid_values = { - "balance": get_balance_on(account, date, cost_center=cost_center), "party_type": party_type, "account_type": account_details.account_type, "account_currency": account_details.account_currency or company_currency, -- GitLab From a05c159b69ba8338e0c05d6defec45bcbf990fd4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:09:58 +0530 Subject: [PATCH 04/24] refactor: exclude balances while setting currency --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 3 +-- erpnext/accounts/doctype/journal_entry/journal_entry.py | 9 +-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 9cb4d050ad8..9a4f2a30a6b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -452,13 +452,12 @@ frappe.ui.form.on("Journal Entry Account", { if(!d.account && d.party_type && d.party) { if(!frm.doc.company) frappe.throw(__("Please select Company")); return frm.call({ - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance", + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency", child: d, args: { company: frm.doc.company, party_type: d.party_type, party: d.party, - cost_center: d.cost_center } }); } diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 9a58dc4cc07..f653a196d3d 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1531,21 +1531,14 @@ def get_outstanding(args): @frappe.whitelist() -def get_party_account_and_balance(company, party_type, party, cost_center=None): +def get_party_account_and_currency(company, party_type, party): if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) account = get_party_account(party_type, party, company) - account_balance = get_balance_on(account=account, cost_center=cost_center) - party_balance = get_balance_on( - party_type=party_type, party=party, company=company, cost_center=cost_center - ) - return { "account": account, - "balance": account_balance, - "party_balance": party_balance, "account_currency": frappe.get_cached_value("Account", account, "account_currency"), } -- GitLab From 37ff9162360941e8bd788e917530fe31f6ff55b8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:13:21 +0530 Subject: [PATCH 05/24] refactor: remove controller logic for setting balances --- .../doctype/journal_entry/journal_entry.py | 55 +------------------ 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index f653a196d3d..c3e96ce77c0 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -143,7 +143,6 @@ class JournalEntry(AccountsController): self.set_print_format_fields() self.validate_credit_debit_note() self.validate_empty_accounts_table() - self.set_account_and_party_balance() self.validate_inter_company_accounts() self.validate_accounting_journals() self.validate_depr_entry_voucher_type() @@ -1165,56 +1164,6 @@ class JournalEntry(AccountsController): if not self.get("accounts"): frappe.throw(_("Accounts table cannot be blank.")) - def set_account_and_party_balance(self): - account_balance = {} - party_balance = {} - for d in self.get("accounts"): - if d.account not in account_balance: - account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date) - - if (d.party_type, d.party) not in party_balance: - party_balance[(d.party_type, d.party)] = get_balance_on( - party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company - ) - - d.account_balance = account_balance[d.account] - d.party_balance = party_balance[(d.party_type, d.party)] - - def update_unreconciled_amount(self): - amount = 0 - cash_bank_accounts = [ - x.get("name") for x in frappe.get_all("Account", {"account_type": ["in", ["Bank", "Cash"]]}) - ] - for line in self.accounts: - if line.account in cash_bank_accounts: - amount += abs(flt(line.debit_in_account_currency) - flt(line.credit_in_account_currency)) - frappe.db.set_value( - "Journal Entry Account", - line.name, - "unreconciled_amount", - abs(flt(line.debit_in_account_currency) - flt(line.credit_in_account_currency)), - update_modified=False, - ) - - self.db_set("unreconciled_amount", abs(amount), update_modified=False) - - def validate_accounting_journals(self): - raise_exception = frappe.db.get_single_value( - "Accounts Settings", "force_unique_journal_in_transaction" - ) - accounting_journals = set(account.accounting_journal for account in self.accounts) - if len(accounting_journals) > 1: - frappe.msgprint( - _("Your entries are linked to different journals. Please make sure it is correct."), - raise_exception=raise_exception, - ) - - def set_advance_for_down_payment_entries(self): - for account in self.accounts: - if account.reference_type == "Sales Invoice": - if frappe.db.get_value("Sales Invoice", account.reference_name, "is_down_payment_invoice"): - account.is_advance = "Yes" - @frappe.whitelist() def get_default_bank_cash_account( @@ -1382,8 +1331,6 @@ def get_payment_entry(ref_doc, args): "account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"), "account_currency": args.get("party_account_currency") or get_account_currency(args.get("party_account")), - "balance": get_balance_on(args.get("party_account")), - "party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")), "exchange_rate": exchange_rate, args.get("amount_field_party"): args.get("amount"), "is_advance": args.get("is_advance"), @@ -1547,7 +1494,7 @@ def get_party_account_and_currency(company, party_type, party): def get_account_details_and_party_type( account, date, company, debit=None, credit=None, exchange_rate=None ): - """Returns dict of account balance and party type to be set in Journal Entry on selection of account.""" + """Returns dict of account details and party type to be set in Journal Entry on selection of account.""" if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) -- GitLab From f79c9a934c6bbb3a2633ce4c287886f461a12286 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:20:01 +0530 Subject: [PATCH 06/24] feat: allow on submit for selected fields --- .../journal_entry_account/journal_entry_account.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index e231f1acf1c..35c54791439 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -41,6 +41,7 @@ ], "fields": [ { + "allow_on_submit": 1, "bold": 1, "columns": 2, "fieldname": "account", @@ -57,6 +58,7 @@ "width": "250px" }, { + "allow_on_submit": 1, "fieldname": "account_type", "fieldtype": "Data", "hidden": 1, @@ -64,6 +66,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "default": ":Company", "description": "If Income or Expense", "fieldname": "cost_center", @@ -204,6 +207,7 @@ "no_copy": 1 }, { + "allow_on_submit": 1, "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -251,6 +255,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "fieldname": "bank_account", "fieldtype": "Link", "label": "Bank Account", @@ -283,7 +288,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-12 11:52:46.397186", + "modified": "2023-10-12 12:18:49.224840", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", -- GitLab From 8f23ecf338106badc2495b473729c9c892b4d988 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:46:50 +0530 Subject: [PATCH 07/24] feat: update after submit in JV --- .../doctype/journal_entry/journal_entry.json | 25 ++++++++++--------- .../doctype/journal_entry/journal_entry.py | 6 +++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index fd9b218069a..8fd8a1065ed 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -66,7 +66,8 @@ "stock_entry", "subscription_section", "auto_repeat", - "amended_from" + "amended_from", + "repost_required" ], "fields": [ { @@ -565,22 +566,22 @@ "label": "Is System Generated", "no_copy": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "repost_required", + "fieldtype": "Check", + "hidden": 1, + "label": "Repost Required", + "print_hide": 1, + "read_only": 1 } ], "icon": "uil uil-file-alt", "idx": 176, "is_submittable": 1, - "links": [ - { - "group": "Bank Statement", - "is_child_table": 1, - "link_doctype": "Bank Transaction Payments", - "link_fieldname": "payment_entry", - "parent_doctype": "Bank Transaction", - "table_fieldname": "payment_entries" - } - ], - "modified": "2023-12-28 17:19:46.544879", + "links": [], + "modified": "2023-10-12 12:32:34.234167", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index c3e96ce77c0..21d833c762f 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -177,6 +177,12 @@ class JournalEntry(AccountsController): self.update_invoice_discounting() self.update_unreconciled_amount() + def on_update_after_submit(self): + if hasattr(self, "repost_required"): + child_tables = {"accounts": ("account", "account_type", "bank_account")} + self.needs_repost = self.check_if_fields_updated([], child_tables) + self.db_set("repost_required", self.needs_repost) + def on_cancel(self): # References for this Journal are removed on the `on_cancel` event in accounts_controller super(JournalEntry, self).on_cancel() -- GitLab From 862d7720609f164c5fa63b90fe28ecc36f94abaf Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 12:48:14 +0530 Subject: [PATCH 08/24] refactor: better abstraction for controller code --- .../purchase_invoice/purchase_invoice.py | 1 + .../doctype/sales_invoice/sales_invoice.py | 1 + erpnext/controllers/accounts_controller.py | 31 +++++++------------ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d93c63d1d9c..c4268dee1a1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -760,6 +760,7 @@ class PurchaseInvoice(BuyingController): "cash_bank_account", "write_off_account", "unrealized_profit_loss_account", + "is_opening", ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9ba0a07c0be..7d0267d500b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -744,6 +744,7 @@ class SalesInvoice(SellingController): "write_off_account", "loyalty_redemption_account", "unrealized_profit_loss_account", + "is_opening", ] child_tables = { "items": ("income_account", "expense_account", "discount_account"), diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 477b7df13c7..7bce43f542f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2551,27 +2551,20 @@ class AccountsController(TransactionBase): doc_before_update = self.get_doc_before_save() accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] - # Check if opening entry check updated - needs_repost = doc_before_update.get("is_opening") != self.is_opening - - if not needs_repost: - # Parent Level Accounts excluding party account - fields_to_check += accounting_dimensions - for field in fields_to_check: - if doc_before_update.get(field) != self.get(field): - needs_repost = 1 - break + # Parent Level Accounts excluding party account + fields_to_check += accounting_dimensions + for field in fields_to_check: + if doc_before_update.get(field) != self.get(field): + return True - if not needs_repost: - # Check for child tables - for table in child_tables: - needs_repost = check_if_child_table_updated( - doc_before_update.get(table), self.get(table), child_tables[table] - ) - if needs_repost: - break + # Check for child tables + for table in child_tables: + if check_if_child_table_updated( + doc_before_update.get(table), self.get(table), child_tables[table] + ): + return True - return needs_repost + return False @frappe.whitelist() def repost_accounting_entries(self): -- GitLab From c368e3a7161d138965e6231de4ef61ba6bde650a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 13:10:59 +0530 Subject: [PATCH 09/24] feat: validate before allowing repost --- .../doctype/journal_entry/journal_entry.py | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 21d833c762f..ffe38057ff1 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -153,19 +153,12 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() - def submit(self): - if len(self.accounts) > 100: - msgprint(_("The task has been enqueued as a background job."), alert=True) - self.queue_action("submit", timeout=4600) - else: - return self._submit() - - def cancel(self): - if len(self.accounts) > 100: - msgprint(_("The task has been enqueued as a background job."), alert=True) - self.queue_action("cancel", timeout=4600) - else: - return self._cancel() + def validate_for_repost(self): + self.validate_party() + self.validate_multi_currency() + if not frappe.flags.is_reverse_depr_entry: + self.validate_against_jv() + self.validate_stock_accounts() def on_submit(self): self.validate_cheque_info() @@ -181,6 +174,7 @@ class JournalEntry(AccountsController): if hasattr(self, "repost_required"): child_tables = {"accounts": ("account", "account_type", "bank_account")} self.needs_repost = self.check_if_fields_updated([], child_tables) + self.validate_for_repost() self.db_set("repost_required", self.needs_repost) def on_cancel(self): -- GitLab From 48aa6ae87ebde0ae0cf0eff481a85c5fd1d3cee3 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 13:31:15 +0530 Subject: [PATCH 10/24] feat: repost ledger button in JV --- .../doctype/journal_entry/journal_entry.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 9a4f2a30a6b..b7909f1ed66 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -43,6 +43,25 @@ frappe.ui.form.on("Journal Entry", { refresh: function(frm) { erpnext.toggle_naming_series(); + if (frm.doc.repost_required && frm.doc.docstatus===1) { + frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update.")); + frm.add_custom_button(__('Repost Accounting Entries'), + () => { + frm.call({ + doc: frm.doc, + method: 'repost_accounting_entries', + freeze: true, + freeze_message: __('Reposting...'), + callback: (r) => { + if (!r.exc) { + frappe.msgprint(__('Accounting Entries are reposted.')); + frm.refresh(); + } + } + }); + }).removeClass('btn-default').addClass('btn-warning'); + } + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { -- GitLab From d66f291e663279ca30168e83dd92e2305fcbe9c8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 15:04:56 +0530 Subject: [PATCH 11/24] refactor: use qb for JV tests --- .../journal_entry/test_journal_entry.py | 163 +++++++++--------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index a6e920b7ef6..edf0c2e7ffd 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -166,43 +166,37 @@ class TestJournalEntry(unittest.TestCase): jv.get("accounts")[1].credit_in_account_currency = 5000 jv.submit() - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.voucher_no = jv.name - self.assertTrue(gl_entries) + self.fields = [ + "account", + "account_currency", + "debit", + "debit_in_account_currency", + "credit", + "credit_in_account_currency", + ] - expected_values = { - "_Test Bank USD - _TC": { - "account_currency": "USD", - "debit": 5000, - "debit_in_account_currency": 100, - "credit": 0, - "credit_in_account_currency": 0, - }, - "_Test Bank - _TC": { + self.expected_gle = [ + { + "account": "_Test Bank - _TC", "account_currency": "INR", "debit": 0, "debit_in_account_currency": 0, "credit": 5000, "credit_in_account_currency": 5000, }, - } + { + "account": "_Test Bank USD - _TC", + "account_currency": "USD", + "debit": 5000, + "debit_in_account_currency": 100, + "credit": 0, + "credit_in_account_currency": 0, + }, + ] - for field in ( - "account_currency", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ): - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][field], gle[field]) + self.check_gl_entries() # cancel jv.cancel() @@ -228,43 +222,37 @@ class TestJournalEntry(unittest.TestCase): rjv.posting_date = nowdate() rjv.submit() - gl_entries = frappe.db.sql( - """select account, account_currency, debit, credit, - debit_in_account_currency, credit_in_account_currency - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - rjv.name, - as_dict=1, - ) + self.voucher_no = rjv.name - self.assertTrue(gl_entries) + self.fields = [ + "account", + "account_currency", + "debit", + "credit", + "debit_in_account_currency", + "credit_in_account_currency", + ] - expected_values = { - "_Test Bank USD - _TC": { + self.expected_gle = [ + { + "account": "_Test Bank USD - _TC", "account_currency": "USD", "debit": 0, "debit_in_account_currency": 0, "credit": 5000, "credit_in_account_currency": 100, }, - "Sales - _TC": { + { + "account": "Sales - _TC", "account_currency": "INR", "debit": 5000, "debit_in_account_currency": 5000, "credit": 0, "credit_in_account_currency": 0, }, - } + ] - for field in ( - "account_currency", - "debit", - "debit_in_account_currency", - "credit", - "credit_in_account_currency", - ): - for i, gle in enumerate(gl_entries): - self.assertEqual(expected_values[gle.account][field], gle[field]) + self.check_gl_entries() def test_disallow_change_in_account_currency_for_a_party(self): # create jv in USD @@ -344,23 +332,25 @@ class TestJournalEntry(unittest.TestCase): jv.insert() jv.submit() - expected_values = { - "_Test Cash - _TC": {"cost_center": cost_center}, - "_Test Bank - _TC": {"cost_center": cost_center}, - } + self.voucher_no = jv.name - gl_entries = frappe.db.sql( - """select account, cost_center, debit, credit - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.fields = [ + "account", + "cost_center", + ] - self.assertTrue(gl_entries) + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "cost_center": cost_center, + }, + { + "account": "_Test Cash - _TC", + "cost_center": cost_center, + }, + ] - for gle in gl_entries: - self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) + self.check_gl_entries() def test_jv_with_project(self): from erpnext.projects.doctype.project.test_project import make_project @@ -387,23 +377,22 @@ class TestJournalEntry(unittest.TestCase): jv.insert() jv.submit() - expected_values = { - "_Test Cash - _TC": {"project": project_name}, - "_Test Bank - _TC": {"project": project_name}, - } + self.voucher_no = jv.name - gl_entries = frappe.db.sql( - """select account, project, debit, credit - from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s - order by account asc""", - jv.name, - as_dict=1, - ) + self.fields = ["account", "project"] - self.assertTrue(gl_entries) + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "project": project_name, + }, + { + "account": "_Test Cash - _TC", + "project": project_name, + }, + ] - for gle in gl_entries: - self.assertEqual(expected_values[gle.account]["project"], gle.project) + self.check_gl_entries() def test_jv_account_and_party_balance_with_cost_centre(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -426,6 +415,24 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) + def check_gl_entries(self): + gl = frappe.qb.DocType("GL Entry") + query = frappe.qb.from_(gl) + for field in self.fields: + query = query.select(gl[field]) + + query = query.where( + (gl.voucher_type == "Journal Entry") + & (gl.voucher_no == self.voucher_no) + & (gl.is_cancelled == 0) + ).orderby(gl.account) + + gl_entries = query.run(as_dict=True) + + for i in range(len(self.expected_gle)): + for field in self.fields: + self.assertEqual(self.expected_gle[i][field], gl_entries[i][field]) + def make_journal_entry( account1, -- GitLab From 719e0556874d237ff4f0fd631efee76e036b018a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 15:06:04 +0530 Subject: [PATCH 12/24] test: reposting entries for JV --- .../journal_entry/test_journal_entry.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index edf0c2e7ffd..7106d9a1bcf 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -415,6 +415,68 @@ class TestJournalEntry(unittest.TestCase): account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) self.assertEqual(expected_account_balance, account_balance) + def test_repost_accounting_entries(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + + jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) + jv.multi_currency = 0 + jv.submit() + + self.voucher_no = jv.name + + self.fields = [ + "account", + "debit_in_account_currency", + "credit_in_account_currency", + "cost_center", + ] + + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "debit_in_account_currency": 0, + "credit_in_account_currency": 100, + "cost_center": "_Test Cost Center - _TC", + }, + { + "account": "_Test Cash - _TC", + "debit_in_account_currency": 100, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center - _TC", + }, + ] + + self.check_gl_entries() + + cost_center = "_Test Cost Center for BS Account - _TC" + create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") + + jv.accounts[0].account = "_Test Bank - _TC" + jv.accounts[0].cost_center = "_Test Cost Center for BS Account - _TC" + jv.accounts[1].account = "_Test Cash - _TC" + + jv.save() + jv.load_from_db() + self.assertTrue(jv.repost_required) + jv.repost_accounting_entries() + + self.expected_gle = [ + { + "account": "_Test Bank - _TC", + "debit_in_account_currency": 100, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center for BS Account - _TC", + }, + { + "account": "_Test Cash - _TC", + "debit_in_account_currency": 0, + "credit_in_account_currency": 100, + "cost_center": "_Test Cost Center - _TC", + }, + ] + + self.check_gl_entries() + def check_gl_entries(self): gl = frappe.qb.DocType("GL Entry") query = frappe.qb.from_(gl) -- GitLab From 78eb32089da777e19c8ffb2abd7fa11e04f2c889 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 15:57:38 +0530 Subject: [PATCH 13/24] chore: correct typo --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index b7909f1ed66..eb872be1cff 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -44,7 +44,7 @@ frappe.ui.form.on("Journal Entry", { erpnext.toggle_naming_series(); if (frm.doc.repost_required && frm.doc.docstatus===1) { - frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update.")); + frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update.")); frm.add_custom_button(__('Repost Accounting Entries'), () => { frm.call({ -- GitLab From 4ba5190753dbdf6a603499071f48733c6734f6fb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 16:54:41 +0530 Subject: [PATCH 14/24] fix: check child rows before update --- erpnext/controllers/accounts_controller.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7bce43f542f..df72b92db9a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3686,17 +3686,14 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil def check_if_child_table_updated( child_table_before_update, child_table_after_update, fields_to_check ): - accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] + fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"] + # Check if any field affecting accounting entry is altered - for index, item in enumerate(child_table_after_update): + for index, item in enumerate(child_table_before_update): for field in fields_to_check: if child_table_before_update[index].get(field) != item.get(field): return True - for dimension in accounting_dimensions: - if child_table_before_update[index].get(dimension) != item.get(dimension): - return True - return False -- GitLab From 7b210c6d429249b3fcb195a195eeb94b980e3ba1 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 12 Oct 2023 17:40:57 +0530 Subject: [PATCH 15/24] fix: test for reposting pi --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index df72b92db9a..2baf72f68b3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3691,7 +3691,7 @@ def check_if_child_table_updated( # Check if any field affecting accounting entry is altered for index, item in enumerate(child_table_before_update): for field in fields_to_check: - if child_table_before_update[index].get(field) != item.get(field): + if child_table_after_update[index].get(field) != item.get(field): return True return False -- GitLab From 085684ff7df20683ebdb101995424064f2c83510 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 15:20:50 +0530 Subject: [PATCH 16/24] chore: rebase --- README.md | 6 +- .../accounts/doctype/account/test_account.py | 1 - .../doctype/bank_account/bank_account.py | 1 - .../bank_statement_import.py | 261 ++++++++++++++++++ .../bank_transaction/test_bank_transaction.py | 254 +++++++++++++++-- .../pos_invoice_item/pos_invoice_item.json | 2 +- .../purchase_invoice_item.json | 13 - .../doctype/sales_invoice/sales_invoice.py | 6 +- .../sales_invoice_item.json | 4 +- erpnext/accounts/general_ledger.py | 7 - erpnext/controllers/stock_controller.py | 17 +- erpnext/hooks.py | 1 - erpnext/public/js/controllers/transaction.js | 3 +- .../doctype/sales_order/test_sales_order.py | 3 +- erpnext/setup/install.py | 21 -- .../doctype/delivery_note/delivery_note.py | 7 +- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 6 +- .../test_repost_item_valuation.py | 6 +- .../doctype/stock_entry/stock_entry_utils.py | 9 +- .../doctype/stock_entry/test_stock_entry.py | 45 --- .../stock_entry_detail.json | 2 +- .../stock_settings/stock_settings.json | 12 +- erpnext/stock/get_item_details.py | 1 - erpnext/stock/serial_batch_bundle.py | 2 + erpnext/stock/stock_ledger.py | 2 +- pyproject.toml | 3 + 27 files changed, 528 insertions(+), 169 deletions(-) create mode 100644 erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py diff --git a/README.md b/README.md index da2c080700f..c63f2102356 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Dokos -Dokos is a 100% open-source management software that is based on ERPNext. -It is distributed under the GPLv3 license. +[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml) +[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) +[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) +[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker) The 100% web architecture of Dokos allows you to use it in a public cloud as well as in a private cloud. You can install it on your own servers. diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index cd10f646d10..d0ba284f6f8 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -3,7 +3,6 @@ import frappe from frappe.test_runner import make_test_records -from frappe.tests.utils import FrappeTestCase from frappe.utils import nowdate from erpnext.accounts.doctype.account.account import ( diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 6e85d71996a..ee3ebde8ae3 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -58,7 +58,6 @@ class BankAccount(Document): def validate(self): self.validate_company() self.validate_iban() - self.get_iban_details() self.validate_account() def validate_account(self): diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py new file mode 100644 index 00000000000..30e564c8031 --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -0,0 +1,261 @@ +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + + +import csv +import json +import re + +import frappe +import openpyxl +from frappe import _ +from frappe.core.doctype.data_import.data_import import DataImport +from frappe.core.doctype.data_import.importer import Importer, ImportFile +from frappe.utils.background_jobs import enqueue +from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html +from openpyxl.styles import Font +from openpyxl.utils import get_column_letter + +INVALID_VALUES = ("", None) + + +class BankStatementImport(DataImport): + # 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 + + bank: DF.Link | None + bank_account: DF.Link + company: DF.Link + google_sheets_url: DF.Data | None + import_file: DF.Attach | None + import_type: DF.Literal["", "Insert New Records", "Update Existing Records"] + mute_emails: DF.Check + reference_doctype: DF.Link + show_failed_logs: DF.Check + statement_import_log: DF.Code | None + status: DF.Literal["Pending", "Success", "Partial Success", "Error"] + submit_after_import: DF.Check + template_options: DF.Code | None + template_warnings: DF.Code | None + # end: auto-generated types + + def __init__(self, *args, **kwargs): + super(BankStatementImport, self).__init__(*args, **kwargs) + + def validate(self): + doc_before_save = self.get_doc_before_save() + if ( + not (self.import_file or self.google_sheets_url) + or (doc_before_save and doc_before_save.import_file != self.import_file) + or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url) + ): + + template_options_dict = {} + column_to_field_map = {} + bank = frappe.get_doc("Bank", self.bank) + for i in bank.bank_transaction_mapping: + column_to_field_map[i.file_field] = i.bank_transaction_field + template_options_dict["column_to_field_map"] = column_to_field_map + self.template_options = json.dumps(template_options_dict) + + self.template_warnings = "" + + self.validate_import_file() + self.validate_google_sheets_url() + + def start_import(self): + + preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( + self.import_file, self.google_sheets_url + ) + + if "Bank Account" not in json.dumps(preview["columns"]): + frappe.throw(_("Please add the Bank Account column")) + + from frappe.utils.background_jobs import is_job_enqueued + from frappe.utils.scheduler import is_scheduler_inactive + + run_now = frappe.flags.in_test or frappe.conf.developer_mode + if is_scheduler_inactive() and not run_now: + frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive")) + + job_id = f"bank_statement_import::{self.name}" + if not is_job_enqueued(job_id): + enqueue( + start_import, + queue="default", + timeout=6000, + event="data_import", + job_id=job_id, + data_import=self.name, + bank_account=self.bank_account, + import_file_path=self.import_file, + google_sheets_url=self.google_sheets_url, + bank=self.bank, + template_options=self.template_options, + now=run_now, + ) + return True + + return False + + +@frappe.whitelist() +def get_preview_from_template(data_import, import_file=None, google_sheets_url=None): + return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template( + import_file, google_sheets_url + ) + + +@frappe.whitelist() +def form_start_import(data_import): + return frappe.get_doc("Bank Statement Import", data_import).start_import() + + +@frappe.whitelist() +def download_errored_template(data_import_name): + data_import = frappe.get_doc("Bank Statement Import", data_import_name) + data_import.export_errored_rows() + + +def parse_data_from_template(raw_data): + data = [] + + for i, row in enumerate(raw_data): + if all(v in INVALID_VALUES for v in row): + # empty row + continue + + data.append(row) + + return data + + +def start_import( + data_import, bank_account, import_file_path, google_sheets_url, bank, template_options +): + """This method runs in background job""" + + update_mapping_db(bank, template_options) + + data_import = frappe.get_doc("Bank Statement Import", data_import) + file = import_file_path if import_file_path else google_sheets_url + + import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records") + + data = parse_data_from_template(import_file.raw_data) + + if import_file_path: + add_bank_account(data, bank_account) + write_files(import_file, data) + + try: + i = Importer(data_import.reference_doctype, data_import=data_import) + i.import_data() + except Exception: + frappe.db.rollback() + data_import.db_set("status", "Error") + data_import.log_error("Bank Statement Import failed") + finally: + frappe.flags.in_import = False + + frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name}) + + +def update_mapping_db(bank, template_options): + bank = frappe.get_doc("Bank", bank) + for d in bank.bank_transaction_mapping: + d.delete() + + for d in json.loads(template_options)["column_to_field_map"].items(): + bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1], "file_field": d[0]}) + + bank.save() + + +def add_bank_account(data, bank_account): + bank_account_loc = None + if "Bank Account" not in data[0]: + data[0].append("Bank Account") + else: + for loc, header in enumerate(data[0]): + if header == "Bank Account": + bank_account_loc = loc + + for row in data[1:]: + if bank_account_loc: + row[bank_account_loc] = bank_account + else: + row.append(bank_account) + + +def write_files(import_file, data): + full_file_path = import_file.file_doc.get_full_path() + parts = import_file.file_doc.get_extension() + extension = parts[1] + extension = extension.lstrip(".") + + if extension == "csv": + with open(full_file_path, "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(data) + elif extension == "xlsx" or "xls": + write_xlsx(data, "trans", file_path=full_file_path) + + +def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None): + # from xlsx utils with changes + column_widths = column_widths or [] + if wb is None: + wb = openpyxl.Workbook(write_only=True) + + ws = wb.create_sheet(sheet_name, 0) + + for i, column_width in enumerate(column_widths): + if column_width: + ws.column_dimensions[get_column_letter(i + 1)].width = column_width + + row1 = ws.row_dimensions[1] + row1.font = Font(name="Calibri", bold=True) + + for row in data: + clean_row = [] + for item in row: + if isinstance(item, str) and (sheet_name not in ["Data Import Template", "Data Export"]): + value = handle_html(item) + else: + value = item + + if isinstance(item, str) and next(ILLEGAL_CHARACTERS_RE.finditer(value), None): + # Remove illegal characters from the string + value = re.sub(ILLEGAL_CHARACTERS_RE, "", value) + + clean_row.append(value) + + ws.append(clean_row) + + wb.save(file_path) + return True + + +@frappe.whitelist() +def upload_bank_statement(**args): + args = frappe._dict(args) + bsi = frappe.new_doc("Bank Statement Import") + + if args.company: + bsi.update( + { + "company": args.company, + } + ) + + if args.bank_account: + bsi.update({"bank_account": args.bank_account}) + + return bsi diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 84ee592d4c0..f6b64874291 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -13,8 +13,210 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal test_dependencies = ["Item", "Cost Center"] -class TestBankTransaction(unittest.TestCase): - pass +class TestBankTransaction(FrappeTestCase): + def setUp(self): + for dt in [ + "Bank Transaction", + "Payment Entry", + "Payment Entry Reference", + "POS Profile", + ]: + frappe.db.delete(dt) + clear_loan_transactions() + make_pos_profile() + + # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error + uniq_identifier = frappe.generate_hash(length=10) + gl_account = create_gl_account("_Test Bank " + uniq_identifier) + bank_account = create_bank_account( + gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier + ) + + add_transactions(bank_account=bank_account) + add_vouchers(gl_account=gl_account) + + # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. + def test_linked_payments(self): + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"), + ) + linked_payments = get_linked_payments( + bank_transaction.name, + ["payment_entry", "exact_match"], + from_date=bank_transaction.date, + to_date=utils.today(), + ) + self.assertTrue(linked_payments[0]["party"] == "Conrad Electronic") + + # This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment + def test_reconcile(self): + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"), + ) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700)) + vouchers = json.dumps( + [ + { + "payment_doctype": "Payment Entry", + "payment_name": payment.name, + "amount": bank_transaction.unallocated_amount, + } + ] + ) + reconcile_vouchers(bank_transaction.name, vouchers) + + unallocated_amount = frappe.db.get_value( + "Bank Transaction", bank_transaction.name, "unallocated_amount" + ) + self.assertTrue(unallocated_amount == 0) + + clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") + self.assertTrue(clearance_date is not None) + + bank_transaction.reload() + bank_transaction.cancel() + + clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") + self.assertFalse(clearance_date) + + def test_cancel_voucher(self): + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"), + ) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700)) + vouchers = json.dumps( + [ + { + "payment_doctype": "Payment Entry", + "payment_name": payment.name, + "amount": bank_transaction.unallocated_amount, + } + ] + ) + reconcile_vouchers(bank_transaction.name, vouchers) + payment.reload() + payment.cancel() + bank_transaction.reload() + self.assertEqual(bank_transaction.docstatus, DocStatus.submitted()) + self.assertEqual(bank_transaction.unallocated_amount, 1700) + self.assertEqual(bank_transaction.payment_entries, []) + + # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount + def test_debit_credit_output(self): + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"), + ) + linked_payments = get_linked_payments( + bank_transaction.name, + ["payment_entry", "exact_match"], + from_date=bank_transaction.date, + to_date=utils.today(), + ) + self.assertTrue(linked_payments[0]["paid_amount"]) + + # Check error if already reconciled + def test_already_reconciled(self): + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"), + ) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) + vouchers = json.dumps( + [ + { + "payment_doctype": "Payment Entry", + "payment_name": payment.name, + "amount": bank_transaction.unallocated_amount, + } + ] + ) + reconcile_vouchers(bank_transaction.name, vouchers) + + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"), + ) + payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) + vouchers = json.dumps( + [ + { + "payment_doctype": "Payment Entry", + "payment_name": payment.name, + "amount": bank_transaction.unallocated_amount, + } + ] + ) + self.assertRaises( + frappe.ValidationError, + reconcile_vouchers, + bank_transaction_name=bank_transaction.name, + vouchers=vouchers, + ) + + # Raise an error if debitor transaction vs debitor payment + def test_clear_sales_invoice(self): + bank_transaction = frappe.get_doc( + "Bank Transaction", + dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"), + ) + payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"])) + vouchers = json.dumps( + [ + { + "payment_doctype": "Sales Invoice", + "payment_name": payment.name, + "amount": bank_transaction.unallocated_amount, + } + ] + ) + reconcile_vouchers(bank_transaction.name, vouchers=vouchers) + + self.assertEqual( + frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0 + ) + self.assertTrue( + frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") + is not None + ) + + @if_lending_app_installed + def test_matching_loan_repayment(self): + from lending.loan_management.doctype.loan.test_loan import create_loan_accounts + + create_loan_accounts() + bank_account = frappe.get_doc( + { + "doctype": "Bank Account", + "account_name": "Payment Account", + "bank": "Citi Bank", + "account": "Payment Account - _TC", + } + ).insert(ignore_if_duplicate=True) + + bank_transaction = frappe.get_doc( + { + "doctype": "Bank Transaction", + "description": "Loan Repayment - OPSKATTUZWXXX AT776000000098709837 Herr G", + "date": "2018-10-27", + "deposit": 500, + "currency": "INR", + "bank_account": bank_account.name, + } + ).submit() + + repayment_entry = create_loan_and_repayment() + + linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"]) + self.assertEqual(linked_payments[0]["name"], repayment_entry.name) + + +@if_lending_app_installed +def clear_loan_transactions(): + frappe.db.delete("Loan Repayment") def create_bank_account( @@ -44,6 +246,7 @@ def create_bank_account( return bank_account.name + def create_gl_account(gl_account_name="_Test Bank - _TC"): gl_account = frappe.get_doc( { @@ -58,9 +261,7 @@ def create_gl_account(gl_account_name="_Test Bank - _TC"): return gl_account.name -def add_transactions(): - create_bank_account() - +def add_transactions(bank_account="_Test Bank - _TC"): doc = frappe.get_doc( { "doctype": "Bank Transaction", @@ -68,7 +269,7 @@ def add_transactions(): "date": "2018-10-23", "debit": 1200, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -80,7 +281,7 @@ def add_transactions(): "date": "2018-10-23", "debit": 1700, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -92,7 +293,7 @@ def add_transactions(): "date": "2018-10-26", "debit": 690, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -104,7 +305,7 @@ def add_transactions(): "date": "2018-10-27", "debit": 3900, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() @@ -116,13 +317,13 @@ def add_transactions(): "date": "2018-10-27", "credit": 109080, "currency": "INR", - "bank_account": "Checking Account - Citi Bank", + "bank_account": bank_account, } ).insert() doc.submit() -def add_payments(): +def add_vouchers(gl_account="_Test Bank - _TC"): try: frappe.get_doc( { @@ -137,7 +338,8 @@ def add_payments(): pass pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Conrad Oct 18" pe.reference_date = "2018-10-24" pe.insert() @@ -156,14 +358,14 @@ def add_payments(): pass pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Herr G Oct 18" pe.reference_date = "2018-10-24" pe.insert() pe.submit() pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe.reference_no = "Herr G Nov 18" pe.reference_date = "2018-11-01" pe.insert() @@ -193,8 +395,20 @@ def add_payments(): except frappe.DuplicateEntryError: pass - pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") + pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1) + pi.cash_bank_account = gl_account + pi.insert() + pi.submit() + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) + pe.reference_no = "Poore Simon's Oct 18" + pe.reference_date = "2018-10-28" + pe.paid_amount = 690 + pe.received_amount = 690 + pe.insert() + pe.submit() + + si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) + pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account) pe.reference_no = "Poore Simon's Oct 18" pe.reference_date = "2018-10-28" pe.insert() @@ -224,15 +438,11 @@ def add_payments(): if not frappe.db.get_value( "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"} ): - mode_of_payment.append( - "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"} - ) + mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account}) mode_of_payment.save() si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) si.is_pos = 1 - si.append( - "payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080} - ) + si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080}) si.insert() si.submit() diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index 4a289cf47fb..d832ada0f6d 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -854,7 +854,7 @@ ], "istable": 1, "links": [], - "modified": "2024-02-25 15:50:17.140269", + "modified": "2024-02-04 16:36:25.665743", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 11222000fbf..ce179581f4f 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -939,19 +939,6 @@ { "fieldname": "column_break_vbbb", "fieldtype": "Column Break" - }, - { - "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, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7d0267d500b..4dabc92a2a4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -459,11 +459,7 @@ class SalesInvoice(SellingController): # Updating stock ledger should always be called after updating prevdoc status, # because updating reserved qty in bin depends upon updated delivered qty in SO if self.update_stock == 1: - for table_name in ["items", "packed_items"]: - if not self.get(table_name): - continue - - self.make_bundle_using_old_serial_batch_fields(table_name) + self.make_bundle_using_old_serial_batch_fields() self.update_stock_ledger() self.render_remarks() 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 4283b961c7c..6378ee5de4c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -630,7 +630,7 @@ { "depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1", "fieldname": "serial_no", - "fieldtype": "Text", + "fieldtype": "Small Text", "label": "Serial No", "oldfieldname": "serial_no", "oldfieldtype": "Small Text" @@ -952,7 +952,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-25 15:56:44.828634", + "modified": "2024-02-04 11:52:16.106541", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 19f7cdbaec7..84cc6ef7c39 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -373,13 +373,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): accounting_number = get_accounting_number(gl_map[0]) for entry in gl_map: - # @dokos: Make sure that every entry has successive numbers - entry["accounting_entry_number"] = accounting_number - - # @dokos: Add an accounting journal to the entry - if not entry.get("accounting_journal"): - get_accounting_journal(entry) - validate_allowed_dimensions(entry, dimension_filter_map) make_entry(entry, adv_adj, update_outstanding, from_repost) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2f05ec36413..b7dd2106c5b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -158,34 +158,26 @@ class StockController(AccountsController): # remove extra whitespace and store one serial no on each line row.serial_no = clean_serial_no_string(row.serial_no) - def make_bundle_using_old_serial_batch_fields(self, table_name=None): + def make_bundle_using_old_serial_batch_fields(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.serial_batch_bundle import SerialBatchCreation - if self.get("_action") == "update_after_submit": - return - # To handle test cases if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields: return - if not table_name: - table_name = "items" - + table_name = "items" if self.doctype == "Asset Capitalization": table_name = "stock_items" for row in self.get(table_name): - if row.serial_and_batch_bundle and (row.serial_no or row.batch_no): - self.validate_serial_nos_and_batches_with_bundle(row) - if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"): continue if not row.use_serial_batch_fields and ( row.serial_no or row.batch_no or row.get("rejected_serial_no") ): - row.use_serial_batch_fields = 1 + frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle")) if row.use_serial_batch_fields and ( not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle") @@ -213,6 +205,7 @@ class StockController(AccountsController): row.db_set( { "rejected_serial_and_batch_bundle": sn_doc.name, + "rejected_serial_no": "", } ) else: @@ -220,6 +213,8 @@ class StockController(AccountsController): row.db_set( { "serial_and_batch_bundle": sn_doc.name, + "serial_no": "", + "batch_no": "", } ) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c65e5c82dc8..bfdc9410e7a 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -49,7 +49,6 @@ setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wiza before_install = [ "erpnext.setup.install.check_setup_wizard_not_completed", - "erpnext.setup.install.check_frappe_version", ] after_install = "erpnext.setup.install.after_install" diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 90b8f8bee22..b616ac63d2c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -114,6 +114,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe frappe.ui.form.on(this.frm.doctype + " Item", { items_add: function(frm, cdt, cdn) { + debugger var item = frappe.get_doc(cdt, cdn); if (!item.warehouse && frm.doc.set_warehouse) { item.warehouse = frm.doc.set_warehouse; @@ -261,7 +262,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } set_fields_onload_for_line_item() { - if (this.frm.is_new() && this.frm.doc?.items) { + if (this.frm.is_new && this.frm.doc?.items) { this.frm.doc.items.forEach(item => { if (item.docstatus === 0 && frappe.meta.has_field(item.doctype, "use_serial_batch_fields") diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 29db0d490f8..cff720db5cd 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2057,7 +2057,7 @@ class TestSalesOrder(FrappeTestCase): rejected_warehouse = "_Test Dummy Rejected Warehouse - _TC" if not frappe.db.exists("Warehouse", rejected_warehouse): - wh = frappe.get_doc( + frappe.get_doc( { "doctype": "Warehouse", "warehouse_name": rejected_warehouse, @@ -2066,7 +2066,6 @@ class TestSalesOrder(FrappeTestCase): "is_rejected_warehouse": 1, } ).insert() - rejected_warehouse = wh.name se = make_stock_entry(item_code=normal_item, qty=1, to_warehouse=warehouse, do_not_submit=True) for item in [serial_and_batch_item, serial_item, batch_item]: diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 6ad66cf7134..a13377dc53e 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -1,7 +1,6 @@ # Copyright (c) 2019, Dokos SAS and Contributors # License: See license.txt -import click import frappe from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields @@ -9,7 +8,6 @@ from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.modules.utils import sync_customizations_for_doctype from frappe.utils import cint -import erpnext from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules from erpnext.setup.doctype.incoterm.incoterm import create_incoterms @@ -44,25 +42,6 @@ def check_setup_wizard_not_completed(): frappe.throw(message) # nosemgrep -def check_frappe_version(): - def major_version(v: str) -> str: - return v.split(".")[0] - - frappe_version = major_version(frappe.__version__) - erpnext_version = major_version(erpnext.__version__) - - if frappe_version == erpnext_version: - return - - click.secho( - f"You're attempting to install Dokos version {erpnext_version} with Dodock version {frappe_version}. " - "This is not supported and will result in broken install. Switch to correct branch before installing.", - fg="red", - ) - - raise SystemExit(1) - - def set_single_defaults(): for dt in ( "Accounts Settings", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 5347eaf65a8..f7deec93e2f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -279,12 +279,7 @@ class DeliveryNote(SellingController): elif self.issue_credit_note: self.make_return_invoice() - for table_name in ["items", "packed_items"]: - if not self.get(table_name): - continue - - self.make_bundle_using_old_serial_batch_fields(table_name) - + self.make_bundle_using_old_serial_batch_fields() # Updating stock ledger should always be called after updating prevdoc status, # because updating reserved qty in bin depends upon updated delivered qty in SO self.update_stock_ledger() diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index d3b7d2ec89d..de2de119f90 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -112,7 +112,7 @@ class PickList(Document): continue if not row.use_serial_batch_fields and (row.serial_no or row.batch_no): - frappe.throw(_("Please enable Use Old Serial / Batch Fields to make bundle")) + frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle")) if row.use_serial_batch_fields and (not row.serial_and_batch_bundle): sn_doc = SerialBatchCreation( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 284fce9bfa8..13c3b124094 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2233,10 +2233,6 @@ class TestPurchaseReceipt(FrappeTestCase): create_stock_reconciliation, ) - frappe.db.set_single_value( - "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0 - ) - item_code = make_item( "_Test Use Serial Fields Item Serial Item", properties={"has_serial_no": 1, "serial_no_series": "SNU-TSFISI-.#####"}, @@ -2259,7 +2255,7 @@ class TestPurchaseReceipt(FrappeTestCase): ) self.assertEqual(pr.items[0].use_serial_batch_fields, 1) - self.assertTrue(pr.items[0].serial_no) + self.assertFalse(pr.items[0].serial_no) self.assertTrue(pr.items[0].serial_and_batch_bundle) sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle) diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 2cdf6c7dad3..770904038a6 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -455,16 +455,16 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): doc.cancel() def test_remove_attached_file(self): - item = make_item("_Test Remove Attached File Item", properties={"is_stock_item": 1}) + item_code = make_item("_Test Remove Attached File Item", properties={"is_stock_item": 1}) make_purchase_receipt( - item_code=item.name, + item_code=item_code, qty=1, rate=100, ) pr1 = make_purchase_receipt( - item_code=item.name, + item_code=item_code, qty=1, rate=100, posting_date=add_days(today(), days=-1), diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index 84b256f6c8f..c42ba2a0f71 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -91,6 +91,9 @@ def make_stock_entry(**args): else: args.qty = cint(args.qty) + if args.serial_no or args.batch_no: + args.use_serial_batch_fields = True + # purpose if not args.purpose: if args.source and args.target: @@ -160,11 +163,7 @@ def make_stock_entry(**args): .name ) - args["serial_no"] = "" - args["batch_no"] = "" - - else: - args.serial_no = serial_number + args.serial_no = serial_number s.append( "items", diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 5f796bfdfcf..eb8df641e9e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1720,51 +1720,6 @@ class TestStockEntry(FrappeTestCase): mr.cancel() mr.delete() - def test_use_serial_and_batch_fields(self): - item = make_item( - "Test Use Serial and Batch Item SN Item", - {"has_serial_no": 1, "is_stock_item": 1}, - ) - - serial_nos = [ - "Test Use Serial and Batch Item SN Item - SN 001", - "Test Use Serial and Batch Item SN Item - SN 002", - ] - - se = make_stock_entry( - item_code=item.name, - qty=2, - to_warehouse="_Test Warehouse - _TC", - use_serial_batch_fields=1, - serial_no="\n".join(serial_nos), - ) - - self.assertTrue(se.items[0].use_serial_batch_fields) - self.assertTrue(se.items[0].serial_no) - self.assertTrue(se.items[0].serial_and_batch_bundle) - - for serial_no in serial_nos: - self.assertTrue(frappe.db.exists("Serial No", serial_no)) - self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active") - - se1 = make_stock_entry( - item_code=item.name, - qty=2, - from_warehouse="_Test Warehouse - _TC", - use_serial_batch_fields=1, - serial_no="\n".join(serial_nos), - ) - - se1.reload() - - self.assertTrue(se1.items[0].use_serial_batch_fields) - self.assertTrue(se1.items[0].serial_no) - self.assertTrue(se1.items[0].serial_and_batch_bundle) - - for serial_no in serial_nos: - self.assertTrue(frappe.db.exists("Serial No", serial_no)) - self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered") - def make_serialized_item(**args): args = frappe._dict(args) 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 fc0f57c550e..4a6daafcae2 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -610,7 +610,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-02-25 15:58:40.982582", + "modified": "2024-02-04 16:16:47.606270", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index e07e052ff99..00d53e7af3b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -51,7 +51,6 @@ "use_naming_series", "naming_series_prefix", "use_serial_batch_fields", - "do_not_update_serial_batch_on_creation_of_auto_bundle", "stock_planning_tab", "auto_material_request", "auto_indent", @@ -425,18 +424,9 @@ }, { "default": "1", - "description": "On submission of the stock transaction, system will auto create the Serial and Batch Bundle based on the Serial No / Batch fields.", "fieldname": "use_serial_batch_fields", "fieldtype": "Check", "label": "Use Serial / Batch Fields" - }, - { - "default": "1", - "depends_on": "use_serial_batch_fields", - "description": "If enabled, do not update serial / batch values in the stock transactions on creation of auto Serial \n / Batch Bundle. ", - "fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle", - "fieldtype": "Check", - "label": "Do Not Update Serial / Batch on Creation of Auto Bundle" } ], "icon": "uil uil-setting", @@ -444,7 +434,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-02-25 16:32:01.084453", + "modified": "2024-02-04 12:01:31.931864", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1f507d6b57d..0d7ad5a4741 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -555,7 +555,6 @@ def get_item_tax_info( "company": company, "tax_category": tax_category, "base_net_rate": item_rates.get(item_code[1]), - "doctype": doctype, } if item_tax_templates: diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 4510d76e205..4f9bd0d255e 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -925,6 +925,8 @@ class SerialBatchCreation: self.batches = get_available_batches(kwargs) def set_auto_serial_batch_entries_for_inward(self): + print(self.get("serial_nos")) + if (self.get("batches") and self.has_batch_no) or ( self.get("serial_nos") and self.has_serial_no ): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 2b62baf42b3..d2d495383fc 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -902,7 +902,7 @@ class update_entries_after(object): precision = doc.precision("total_qty") self.wh_data.qty_after_transaction += flt(doc.total_qty, precision) - if flt(self.wh_data.qty_after_transaction, precision): + if self.wh_data.qty_after_transaction: self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt( self.wh_data.qty_after_transaction, precision ) diff --git a/pyproject.toml b/pyproject.toml index 7d3614b2837..a9adcecee8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,3 +45,6 @@ force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true indent = "\t" + +[tool.bench.frappe-dependencies] +frappe = ">=16.0.0-dev,<17.0.0" -- GitLab From bdf6ce71303598f2fdda1aa822955156806f6cd9 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 15:35:10 +0530 Subject: [PATCH 17/24] fix: disable editable account heads --- .../doctype/journal_entry/journal_entry.py | 25 ++++++++++++------- .../journal_entry_account.json | 5 +--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ffe38057ff1..3b039bf2c0e 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -153,12 +153,19 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() - def validate_for_repost(self): - self.validate_party() - self.validate_multi_currency() - if not frappe.flags.is_reverse_depr_entry: - self.validate_against_jv() - self.validate_stock_accounts() + def submit(self): + if len(self.accounts) > 100: + msgprint(_("The task has been enqueued as a background job."), alert=True) + self.queue_action("submit", timeout=4600) + else: + return self._submit() + + def cancel(self): + if len(self.accounts) > 100: + msgprint(_("The task has been enqueued as a background job."), alert=True) + self.queue_action("cancel", timeout=4600) + else: + return self._cancel() def on_submit(self): self.validate_cheque_info() @@ -172,9 +179,9 @@ class JournalEntry(AccountsController): def on_update_after_submit(self): if hasattr(self, "repost_required"): - child_tables = {"accounts": ("account", "account_type", "bank_account")} - self.needs_repost = self.check_if_fields_updated([], child_tables) - self.validate_for_repost() + self.needs_repost = self.check_if_fields_updated( + fields_to_check=[], child_tables={"accounts": []} + ) self.db_set("repost_required", self.needs_repost) def on_cancel(self): diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 35c54791439..2db65f48a98 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -41,7 +41,6 @@ ], "fields": [ { - "allow_on_submit": 1, "bold": 1, "columns": 2, "fieldname": "account", @@ -58,7 +57,6 @@ "width": "250px" }, { - "allow_on_submit": 1, "fieldname": "account_type", "fieldtype": "Data", "hidden": 1, @@ -255,7 +253,6 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, "fieldname": "bank_account", "fieldtype": "Link", "label": "Bank Account", @@ -288,7 +285,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-12 12:18:49.224840", + "modified": "2024-02-05 01:10:50.224840", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", -- GitLab From 4d79fbb6a74dd74cad8a6fbefc13b0601a36c797 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 16:09:28 +0530 Subject: [PATCH 18/24] fix: allow editable accounting dimensions for repostable doctypes --- .../accounting_dimension/accounting_dimension.py | 6 ++++++ .../accounts/doctype/journal_entry/journal_entry.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 2b01e2411d6..d29e7b9969f 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -11,6 +11,10 @@ from frappe.model import core_doctypes_list from frappe.model.document import Document from frappe.utils import cstr +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + get_allowed_types_from_settings, +) + class AccountingDimension(Document): def before_insert(self): @@ -86,6 +90,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None): doc_count = len(get_accounting_dimensions()) count = 0 + repostable_doctypes = get_allowed_types_from_settings() for doctype in doclist: @@ -101,6 +106,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None): "options": doc.document_type, "insert_after": insert_after_field, "owner": "Administrator", + "allow_on_submit": 1 if doctype in repostable_doctypes else 0, } meta = frappe.get_meta(doctype, cached=False) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 3b039bf2c0e..5239ac86ce5 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -13,6 +13,10 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( get_party_account_based_on_invoice_discounting, ) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, + validate_docs_for_voucher_types, +) from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) @@ -153,6 +157,10 @@ class JournalEntry(AccountsController): if not self.title: self.title = self.get_title() + def validate_for_repost(self): + validate_docs_for_voucher_types(["Journal Entry"]) + validate_docs_for_deferred_accounting([self.name], []) + def submit(self): if len(self.accounts) > 100: msgprint(_("The task has been enqueued as a background job."), alert=True) @@ -182,7 +190,9 @@ class JournalEntry(AccountsController): self.needs_repost = self.check_if_fields_updated( fields_to_check=[], child_tables={"accounts": []} ) - self.db_set("repost_required", self.needs_repost) + if self.needs_repost: + self.validate_for_repost() + self.db_set("repost_required", self.needs_repost) def on_cancel(self): # References for this Journal are removed on the `on_cancel` event in accounts_controller -- GitLab From da8e52791c097ca15c228a932e4a5c9ee483dc90 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Feb 2024 16:36:30 +0530 Subject: [PATCH 19/24] feat: add patch for making repostable dimension fields editable --- erpnext/patches.txt | 27 +------------------ ...bmit_dimensions_for_repostable_doctypes.py | 14 ++++++++++ 2 files changed, 15 insertions(+), 26 deletions(-) create mode 100644 erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 36f9dce0919..e46681d8895 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -355,32 +355,7 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency" execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format")) erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v15_0.create_advance_payment_status -erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool - -# @dokos -erpnext.patches.dokos.v3_0.add_expiration_date_to_booking_legder -erpnext.patches.dokos.v3_0.setup_item_wise_tax_info_for_france #2023-05-02 -execute:frappe.delete_doc('Report', 'GoCardless Payments', ignore_missing=True) -erpnext.patches.dokos.v3_0.update_custom_fields_for_france #2024-02-14 -erpnext.accounts.doctype.subscription.patches.set_an_invoicing_day_on_existing_subscriptions -erpnext.patches.dokos.v3_0.retry_gocardless_payout_webhooks -erpnext.patches.dokos.v3_0.gocardless_payments_corrections -erpnext.patches.dokos.v3_0.migrate_to_new_booking_credit_system -erpnext.patches.dokos.v3_0.fix_french_success_action_message_for_quotation_and_others -erpnext.patches.dokos.v3_0.set_venue_settings_defaults -erpnext.patches.dokos.v3_0.set_units_of_measure_in_venue_settings -erpnext.accounts.doctype.mode_of_payment.patches.update_mode_of_payment_data_from_payment_gateway -execute:frappe.delete_doc_if_exists("DocType", "Subscription Gateway Plans") -execute:frappe.delete_doc_if_exists("DocType", "Subscription Template") -execute:frappe.delete_doc_if_exists("DocType", "Portal Payment Gateways Template") -execute:frappe.delete_doc_if_exists("DocType", "Portal Payment Gateways") -execute:frappe.delete_doc_if_exists("DocType", "Subscription Event") -erpnext.patches.dokos.v4_0.set_mode_of_payment_in_payment_requests -erpnext.patches.dokos.v4_0.add_customer_to_payment_request -erpnext.accounts.doctype.mode_of_payment.patches.migrate_fees_and_cost_center_to_account_table -# @dokos - - +erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 diff --git a/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py new file mode 100644 index 00000000000..e75610d0a53 --- /dev/null +++ b/erpnext/patches/v15_0/allow_on_submit_dimensions_for_repostable_doctypes.py @@ -0,0 +1,14 @@ +import frappe + +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, +) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + get_allowed_types_from_settings, +) + + +def execute(): + for dt in get_allowed_types_from_settings(): + for dimension in get_accounting_dimensions(): + frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1) -- GitLab From c5f75d39fd6a35c8f433a0bb3986c06b21d3da16 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 9 Feb 2024 13:11:51 +0530 Subject: [PATCH 20/24] fix: test for repost accounting in JVs --- .../journal_entry/test_journal_entry.py | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 7106d9a1bcf..798d3bb6c82 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -418,10 +418,18 @@ class TestJournalEntry(unittest.TestCase): def test_repost_accounting_entries(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + # Configure Repost Accounting Ledger for JVs + settings = frappe.get_doc("Repost Accounting Ledger Settings") + if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]: + settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True}) + settings.save() + + # Create JV with defaut cost center - _Test Cost Center jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False) jv.multi_currency = 0 jv.submit() + # Check GL entries before reposting self.voucher_no = jv.name self.fields = [ @@ -448,33 +456,18 @@ class TestJournalEntry(unittest.TestCase): self.check_gl_entries() - cost_center = "_Test Cost Center for BS Account - _TC" + # Change cost center for bank account - _Test Cost Center for BS Account create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") - - jv.accounts[0].account = "_Test Bank - _TC" - jv.accounts[0].cost_center = "_Test Cost Center for BS Account - _TC" - jv.accounts[1].account = "_Test Cash - _TC" - + jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC" jv.save() - jv.load_from_db() + + # Check if repost flag gets set on update after submit self.assertTrue(jv.repost_required) jv.repost_accounting_entries() - self.expected_gle = [ - { - "account": "_Test Bank - _TC", - "debit_in_account_currency": 100, - "credit_in_account_currency": 0, - "cost_center": "_Test Cost Center for BS Account - _TC", - }, - { - "account": "_Test Cash - _TC", - "debit_in_account_currency": 0, - "credit_in_account_currency": 100, - "cost_center": "_Test Cost Center - _TC", - }, - ] - + # Check GL entries after reposting + jv.load_from_db() + self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC" self.check_gl_entries() def check_gl_entries(self): -- GitLab From f24c3cd30061e9e59f5529a3692cab6922f6a099 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 7 Mar 2024 09:38:46 +0100 Subject: [PATCH 21/24] fix: merge conflicts --- .../accounts/doctype/account/test_account.py | 1 + .../doctype/bank_account/bank_account.py | 1 + .../bank_statement_import.py | 261 ------------------ .../bank_transaction/test_bank_transaction.py | 234 +--------------- .../doctype/journal_entry/journal_entry.json | 11 +- .../doctype/journal_entry/journal_entry.py | 35 +++ .../purchase_invoice_item.json | 13 + .../doctype/sales_invoice/sales_invoice.py | 6 +- 8 files changed, 67 insertions(+), 495 deletions(-) delete mode 100644 erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index d0ba284f6f8..cd10f646d10 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -3,6 +3,7 @@ import frappe from frappe.test_runner import make_test_records +from frappe.tests.utils import FrappeTestCase from frappe.utils import nowdate from erpnext.accounts.doctype.account.account import ( diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index ee3ebde8ae3..6e85d71996a 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -58,6 +58,7 @@ class BankAccount(Document): def validate(self): self.validate_company() self.validate_iban() + self.get_iban_details() self.validate_account() def validate_account(self): diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py deleted file mode 100644 index 30e564c8031..00000000000 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt - - -import csv -import json -import re - -import frappe -import openpyxl -from frappe import _ -from frappe.core.doctype.data_import.data_import import DataImport -from frappe.core.doctype.data_import.importer import Importer, ImportFile -from frappe.utils.background_jobs import enqueue -from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html -from openpyxl.styles import Font -from openpyxl.utils import get_column_letter - -INVALID_VALUES = ("", None) - - -class BankStatementImport(DataImport): - # 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 - - bank: DF.Link | None - bank_account: DF.Link - company: DF.Link - google_sheets_url: DF.Data | None - import_file: DF.Attach | None - import_type: DF.Literal["", "Insert New Records", "Update Existing Records"] - mute_emails: DF.Check - reference_doctype: DF.Link - show_failed_logs: DF.Check - statement_import_log: DF.Code | None - status: DF.Literal["Pending", "Success", "Partial Success", "Error"] - submit_after_import: DF.Check - template_options: DF.Code | None - template_warnings: DF.Code | None - # end: auto-generated types - - def __init__(self, *args, **kwargs): - super(BankStatementImport, self).__init__(*args, **kwargs) - - def validate(self): - doc_before_save = self.get_doc_before_save() - if ( - not (self.import_file or self.google_sheets_url) - or (doc_before_save and doc_before_save.import_file != self.import_file) - or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url) - ): - - template_options_dict = {} - column_to_field_map = {} - bank = frappe.get_doc("Bank", self.bank) - for i in bank.bank_transaction_mapping: - column_to_field_map[i.file_field] = i.bank_transaction_field - template_options_dict["column_to_field_map"] = column_to_field_map - self.template_options = json.dumps(template_options_dict) - - self.template_warnings = "" - - self.validate_import_file() - self.validate_google_sheets_url() - - def start_import(self): - - preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( - self.import_file, self.google_sheets_url - ) - - if "Bank Account" not in json.dumps(preview["columns"]): - frappe.throw(_("Please add the Bank Account column")) - - from frappe.utils.background_jobs import is_job_enqueued - from frappe.utils.scheduler import is_scheduler_inactive - - run_now = frappe.flags.in_test or frappe.conf.developer_mode - if is_scheduler_inactive() and not run_now: - frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive")) - - job_id = f"bank_statement_import::{self.name}" - if not is_job_enqueued(job_id): - enqueue( - start_import, - queue="default", - timeout=6000, - event="data_import", - job_id=job_id, - data_import=self.name, - bank_account=self.bank_account, - import_file_path=self.import_file, - google_sheets_url=self.google_sheets_url, - bank=self.bank, - template_options=self.template_options, - now=run_now, - ) - return True - - return False - - -@frappe.whitelist() -def get_preview_from_template(data_import, import_file=None, google_sheets_url=None): - return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template( - import_file, google_sheets_url - ) - - -@frappe.whitelist() -def form_start_import(data_import): - return frappe.get_doc("Bank Statement Import", data_import).start_import() - - -@frappe.whitelist() -def download_errored_template(data_import_name): - data_import = frappe.get_doc("Bank Statement Import", data_import_name) - data_import.export_errored_rows() - - -def parse_data_from_template(raw_data): - data = [] - - for i, row in enumerate(raw_data): - if all(v in INVALID_VALUES for v in row): - # empty row - continue - - data.append(row) - - return data - - -def start_import( - data_import, bank_account, import_file_path, google_sheets_url, bank, template_options -): - """This method runs in background job""" - - update_mapping_db(bank, template_options) - - data_import = frappe.get_doc("Bank Statement Import", data_import) - file = import_file_path if import_file_path else google_sheets_url - - import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records") - - data = parse_data_from_template(import_file.raw_data) - - if import_file_path: - add_bank_account(data, bank_account) - write_files(import_file, data) - - try: - i = Importer(data_import.reference_doctype, data_import=data_import) - i.import_data() - except Exception: - frappe.db.rollback() - data_import.db_set("status", "Error") - data_import.log_error("Bank Statement Import failed") - finally: - frappe.flags.in_import = False - - frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name}) - - -def update_mapping_db(bank, template_options): - bank = frappe.get_doc("Bank", bank) - for d in bank.bank_transaction_mapping: - d.delete() - - for d in json.loads(template_options)["column_to_field_map"].items(): - bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1], "file_field": d[0]}) - - bank.save() - - -def add_bank_account(data, bank_account): - bank_account_loc = None - if "Bank Account" not in data[0]: - data[0].append("Bank Account") - else: - for loc, header in enumerate(data[0]): - if header == "Bank Account": - bank_account_loc = loc - - for row in data[1:]: - if bank_account_loc: - row[bank_account_loc] = bank_account - else: - row.append(bank_account) - - -def write_files(import_file, data): - full_file_path = import_file.file_doc.get_full_path() - parts = import_file.file_doc.get_extension() - extension = parts[1] - extension = extension.lstrip(".") - - if extension == "csv": - with open(full_file_path, "w", newline="") as file: - writer = csv.writer(file) - writer.writerows(data) - elif extension == "xlsx" or "xls": - write_xlsx(data, "trans", file_path=full_file_path) - - -def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None): - # from xlsx utils with changes - column_widths = column_widths or [] - if wb is None: - wb = openpyxl.Workbook(write_only=True) - - ws = wb.create_sheet(sheet_name, 0) - - for i, column_width in enumerate(column_widths): - if column_width: - ws.column_dimensions[get_column_letter(i + 1)].width = column_width - - row1 = ws.row_dimensions[1] - row1.font = Font(name="Calibri", bold=True) - - for row in data: - clean_row = [] - for item in row: - if isinstance(item, str) and (sheet_name not in ["Data Import Template", "Data Export"]): - value = handle_html(item) - else: - value = item - - if isinstance(item, str) and next(ILLEGAL_CHARACTERS_RE.finditer(value), None): - # Remove illegal characters from the string - value = re.sub(ILLEGAL_CHARACTERS_RE, "", value) - - clean_row.append(value) - - ws.append(clean_row) - - wb.save(file_path) - return True - - -@frappe.whitelist() -def upload_bank_statement(**args): - args = frappe._dict(args) - bsi = frappe.new_doc("Bank Statement Import") - - if args.company: - bsi.update( - { - "company": args.company, - } - ) - - if args.bank_account: - bsi.update({"bank_account": args.bank_account}) - - return bsi diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index f6b64874291..e7adb93920b 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -13,238 +13,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal test_dependencies = ["Item", "Cost Center"] -class TestBankTransaction(FrappeTestCase): - def setUp(self): - for dt in [ - "Bank Transaction", - "Payment Entry", - "Payment Entry Reference", - "POS Profile", - ]: - frappe.db.delete(dt) - clear_loan_transactions() - make_pos_profile() - - # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error - uniq_identifier = frappe.generate_hash(length=10) - gl_account = create_gl_account("_Test Bank " + uniq_identifier) - bank_account = create_bank_account( - gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier - ) - - add_transactions(bank_account=bank_account) - add_vouchers(gl_account=gl_account) - - # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. - def test_linked_payments(self): - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"), - ) - linked_payments = get_linked_payments( - bank_transaction.name, - ["payment_entry", "exact_match"], - from_date=bank_transaction.date, - to_date=utils.today(), - ) - self.assertTrue(linked_payments[0]["party"] == "Conrad Electronic") - - # This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment - def test_reconcile(self): - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"), - ) - payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700)) - vouchers = json.dumps( - [ - { - "payment_doctype": "Payment Entry", - "payment_name": payment.name, - "amount": bank_transaction.unallocated_amount, - } - ] - ) - reconcile_vouchers(bank_transaction.name, vouchers) - - unallocated_amount = frappe.db.get_value( - "Bank Transaction", bank_transaction.name, "unallocated_amount" - ) - self.assertTrue(unallocated_amount == 0) - - clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") - self.assertTrue(clearance_date is not None) - - bank_transaction.reload() - bank_transaction.cancel() - - clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") - self.assertFalse(clearance_date) - - def test_cancel_voucher(self): - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"), - ) - payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700)) - vouchers = json.dumps( - [ - { - "payment_doctype": "Payment Entry", - "payment_name": payment.name, - "amount": bank_transaction.unallocated_amount, - } - ] - ) - reconcile_vouchers(bank_transaction.name, vouchers) - payment.reload() - payment.cancel() - bank_transaction.reload() - self.assertEqual(bank_transaction.docstatus, DocStatus.submitted()) - self.assertEqual(bank_transaction.unallocated_amount, 1700) - self.assertEqual(bank_transaction.payment_entries, []) - - # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount - def test_debit_credit_output(self): - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"), - ) - linked_payments = get_linked_payments( - bank_transaction.name, - ["payment_entry", "exact_match"], - from_date=bank_transaction.date, - to_date=utils.today(), - ) - self.assertTrue(linked_payments[0]["paid_amount"]) - - # Check error if already reconciled - def test_already_reconciled(self): - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"), - ) - payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) - vouchers = json.dumps( - [ - { - "payment_doctype": "Payment Entry", - "payment_name": payment.name, - "amount": bank_transaction.unallocated_amount, - } - ] - ) - reconcile_vouchers(bank_transaction.name, vouchers) - - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"), - ) - payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) - vouchers = json.dumps( - [ - { - "payment_doctype": "Payment Entry", - "payment_name": payment.name, - "amount": bank_transaction.unallocated_amount, - } - ] - ) - self.assertRaises( - frappe.ValidationError, - reconcile_vouchers, - bank_transaction_name=bank_transaction.name, - vouchers=vouchers, - ) - - # Raise an error if debitor transaction vs debitor payment - def test_clear_sales_invoice(self): - bank_transaction = frappe.get_doc( - "Bank Transaction", - dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"), - ) - payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"])) - vouchers = json.dumps( - [ - { - "payment_doctype": "Sales Invoice", - "payment_name": payment.name, - "amount": bank_transaction.unallocated_amount, - } - ] - ) - reconcile_vouchers(bank_transaction.name, vouchers=vouchers) - - self.assertEqual( - frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0 - ) - self.assertTrue( - frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") - is not None - ) - - @if_lending_app_installed - def test_matching_loan_repayment(self): - from lending.loan_management.doctype.loan.test_loan import create_loan_accounts - - create_loan_accounts() - bank_account = frappe.get_doc( - { - "doctype": "Bank Account", - "account_name": "Payment Account", - "bank": "Citi Bank", - "account": "Payment Account - _TC", - } - ).insert(ignore_if_duplicate=True) - - bank_transaction = frappe.get_doc( - { - "doctype": "Bank Transaction", - "description": "Loan Repayment - OPSKATTUZWXXX AT776000000098709837 Herr G", - "date": "2018-10-27", - "deposit": 500, - "currency": "INR", - "bank_account": bank_account.name, - } - ).submit() - - repayment_entry = create_loan_and_repayment() - - linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"]) - self.assertEqual(linked_payments[0]["name"], repayment_entry.name) - - -@if_lending_app_installed -def clear_loan_transactions(): - frappe.db.delete("Loan Repayment") - - -def create_bank_account( - bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account" -): - try: - frappe.get_doc( - { - "doctype": "Bank", - "bank_name": bank_name, - } - ).insert() - except frappe.DuplicateEntryError: - pass - - try: - bank_account = frappe.get_doc( - { - "doctype": "Bank Account", - "account_name": bank_account_name, - "bank": bank_name, - "account": gl_account, - } - ).insert(ignore_if_duplicate=True) - except frappe.DuplicateEntryError: - pass - - return bank_account.name +class TestBankTransaction(unittest.TestCase): + pass def create_gl_account(gl_account_name="_Test Bank - _TC"): diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 8fd8a1065ed..915a5f79868 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -580,7 +580,16 @@ "icon": "uil uil-file-alt", "idx": 176, "is_submittable": 1, - "links": [], + "links": [ + { + "group": "Bank Statement", + "is_child_table": 1, + "link_doctype": "Bank Transaction Payments", + "link_fieldname": "payment_entry", + "parent_doctype": "Bank Transaction", + "table_fieldname": "payment_entries" + } + ], "modified": "2023-10-12 12:32:34.234167", "modified_by": "Administrator", "module": "Accounts", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 5239ac86ce5..95d0494f0b8 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1181,6 +1181,41 @@ class JournalEntry(AccountsController): if not self.get("accounts"): frappe.throw(_("Accounts table cannot be blank.")) + def update_unreconciled_amount(self): + amount = 0 + cash_bank_accounts = [ + x.get("name") for x in frappe.get_all("Account", {"account_type": ["in", ["Bank", "Cash"]]}) + ] + for line in self.accounts: + if line.account in cash_bank_accounts: + amount += abs(flt(line.debit_in_account_currency) - flt(line.credit_in_account_currency)) + frappe.db.set_value( + "Journal Entry Account", + line.name, + "unreconciled_amount", + abs(flt(line.debit_in_account_currency) - flt(line.credit_in_account_currency)), + update_modified=False, + ) + + self.db_set("unreconciled_amount", abs(amount), update_modified=False) + + def validate_accounting_journals(self): + raise_exception = frappe.db.get_single_value( + "Accounts Settings", "force_unique_journal_in_transaction" + ) + accounting_journals = set(account.accounting_journal for account in self.accounts) + if len(accounting_journals) > 1: + frappe.msgprint( + _("Your entries are linked to different journals. Please make sure it is correct."), + raise_exception=raise_exception, + ) + + def set_advance_for_down_payment_entries(self): + for account in self.accounts: + if account.reference_type == "Sales Invoice": + if frappe.db.get_value("Sales Invoice", account.reference_name, "is_down_payment_invoice"): + account.is_advance = "Yes" + @frappe.whitelist() def get_default_bank_cash_account( 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 ce179581f4f..11222000fbf 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -939,6 +939,19 @@ { "fieldname": "column_break_vbbb", "fieldtype": "Column Break" + }, + { + "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, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4dabc92a2a4..7d0267d500b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -459,7 +459,11 @@ class SalesInvoice(SellingController): # Updating stock ledger should always be called after updating prevdoc status, # because updating reserved qty in bin depends upon updated delivered qty in SO if self.update_stock == 1: - self.make_bundle_using_old_serial_batch_fields() + for table_name in ["items", "packed_items"]: + if not self.get(table_name): + continue + + self.make_bundle_using_old_serial_batch_fields(table_name) self.update_stock_ledger() self.render_remarks() -- GitLab From cbf4179f0ebfa540405111e94e612d8ac0ce9f2f Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 7 Mar 2024 09:57:53 +0100 Subject: [PATCH 22/24] fix: merge conflicts --- README.md | 7 +- .../sales_invoice_item.json | 2 +- erpnext/accounts/general_ledger.py | 7 ++ erpnext/controllers/stock_controller.py | 68 +++++++++++-------- erpnext/hooks.py | 1 + erpnext/patches.txt | 29 +++++++- erpnext/public/js/controllers/transaction.js | 3 +- .../doctype/sales_order/test_sales_order.py | 3 +- erpnext/setup/install.py | 21 ++++++ .../doctype/delivery_note/delivery_note.py | 7 +- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 6 +- .../test_repost_item_valuation.py | 6 +- .../doctype/stock_entry/stock_entry_utils.py | 10 +-- .../doctype/stock_entry/test_stock_entry.py | 47 ++++++++++++- .../stock_entry_detail.json | 2 +- .../stock_settings/stock_settings.json | 12 +++- erpnext/stock/get_item_details.py | 1 + erpnext/stock/serial_batch_bundle.py | 1 - erpnext/stock/stock_ledger.py | 2 +- pyproject.toml | 3 - 21 files changed, 186 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c63f2102356..637e49e5619 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Dokos -[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml) -[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) -[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) -[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker) + +Dokos is a 100% open-source management software that is based on ERPNext. +It is distributed under the GPLv3 license. The 100% web architecture of Dokos allows you to use it in a public cloud as well as in a private cloud. You can install it on your own servers. 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 6378ee5de4c..18c4bf8b2ba 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -630,7 +630,7 @@ { "depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1", "fieldname": "serial_no", - "fieldtype": "Small Text", + "fieldtype": "Text", "label": "Serial No", "oldfieldname": "serial_no", "oldfieldtype": "Small Text" diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 84cc6ef7c39..19f7cdbaec7 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -373,6 +373,13 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): accounting_number = get_accounting_number(gl_map[0]) for entry in gl_map: + # @dokos: Make sure that every entry has successive numbers + entry["accounting_entry_number"] = accounting_number + + # @dokos: Add an accounting journal to the entry + if not entry.get("accounting_journal"): + get_accounting_journal(entry) + validate_allowed_dimensions(entry, dimension_filter_map) make_entry(entry, adv_adj, update_outstanding, from_repost) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b7dd2106c5b..a3fbdda0e9e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -158,26 +158,31 @@ class StockController(AccountsController): # remove extra whitespace and store one serial no on each line row.serial_no = clean_serial_no_string(row.serial_no) - def make_bundle_using_old_serial_batch_fields(self): - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - from erpnext.stock.serial_batch_bundle import SerialBatchCreation + def make_bundle_using_old_serial_batch_fields(self, table_name=None): + if self.get("_action") == "update_after_submit": + return # To handle test cases if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields: return - table_name = "items" + if not table_name: + table_name = "items" + if self.doctype == "Asset Capitalization": table_name = "stock_items" for row in self.get(table_name): + if row.serial_and_batch_bundle and (row.serial_no or row.batch_no): + self.validate_serial_nos_and_batches_with_bundle(row) + if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"): continue if not row.use_serial_batch_fields and ( row.serial_no or row.batch_no or row.get("rejected_serial_no") ): - frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle")) + row.use_serial_batch_fields = 1 if row.use_serial_batch_fields and ( not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle") @@ -191,34 +196,21 @@ class StockController(AccountsController): "voucher_detail_no": row.name, "company": self.company, "is_rejected": 1 if row.get("rejected_warehouse") else 0, - "serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None, - "batch_no": row.batch_no, "use_serial_batch_fields": row.use_serial_batch_fields, "do_not_submit": True, } - self.update_bundle_details(bundle_details, table_name, row) - sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle() + if row.qty: + self.update_bundle_details(bundle_details, table_name, row) + self.create_serial_batch_bundle(bundle_details, row) - if sn_doc.is_rejected: - row.rejected_serial_and_batch_bundle = sn_doc.name - row.db_set( - { - "rejected_serial_and_batch_bundle": sn_doc.name, - "rejected_serial_no": "", - } - ) - else: - row.serial_and_batch_bundle = sn_doc.name - row.db_set( - { - "serial_and_batch_bundle": sn_doc.name, - "serial_no": "", - "batch_no": "", - } - ) + if row.get("rejected_qty"): + self.update_bundle_details(bundle_details, table_name, row, is_rejected=True) + self.create_serial_batch_bundle(bundle_details, row) + + def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - def update_bundle_details(self, bundle_details, table_name, row): # Since qty field is different for different doctypes qty = row.get("qty") warehouse = row.get("warehouse") @@ -237,15 +229,37 @@ class StockController(AccountsController): qty = row.transfer_qty warehouse = row.s_warehouse or row.t_warehouse + serial_nos = row.serial_no + if is_rejected: + serial_nos = row.get("rejected_serial_no") + type_of_transaction = "Inward" if not self.is_return else "Outward" + qty = row.get("rejected_qty") + warehouse = row.get("rejected_warehouse") + bundle_details.update( { "qty": qty, + "is_rejected": is_rejected, "type_of_transaction": type_of_transaction, "warehouse": warehouse, "batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None, + "serial_nos": get_serial_nos(serial_nos) if serial_nos else None, + "batch_no": row.batch_no, } ) + def create_serial_batch_bundle(self, bundle_details, row): + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + + sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle() + + field = "serial_and_batch_bundle" + if bundle_details.get("is_rejected"): + field = "rejected_serial_and_batch_bundle" + + row.set(field, sn_doc.name) + row.db_set({field: sn_doc.name}) + def validate_serial_nos_and_batches_with_bundle(self, row): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bfdc9410e7a..c65e5c82dc8 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -49,6 +49,7 @@ setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wiza before_install = [ "erpnext.setup.install.check_setup_wizard_not_completed", + "erpnext.setup.install.check_frappe_version", ] after_install = "erpnext.setup.install.after_install" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e46681d8895..f84fce96267 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -356,7 +356,34 @@ execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System erpnext.patches.v14_0.update_total_asset_cost_field erpnext.patches.v15_0.create_advance_payment_status erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes +erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool + + +# @dokos +erpnext.patches.dokos.v3_0.add_expiration_date_to_booking_legder +erpnext.patches.dokos.v3_0.setup_item_wise_tax_info_for_france #2023-05-02 +execute:frappe.delete_doc('Report', 'GoCardless Payments', ignore_missing=True) +erpnext.patches.dokos.v3_0.update_custom_fields_for_france #2024-02-14 +erpnext.accounts.doctype.subscription.patches.set_an_invoicing_day_on_existing_subscriptions +erpnext.patches.dokos.v3_0.retry_gocardless_payout_webhooks +erpnext.patches.dokos.v3_0.gocardless_payments_corrections +erpnext.patches.dokos.v3_0.migrate_to_new_booking_credit_system +erpnext.patches.dokos.v3_0.fix_french_success_action_message_for_quotation_and_others +erpnext.patches.dokos.v3_0.set_venue_settings_defaults +erpnext.patches.dokos.v3_0.set_units_of_measure_in_venue_settings +erpnext.accounts.doctype.mode_of_payment.patches.update_mode_of_payment_data_from_payment_gateway +execute:frappe.delete_doc_if_exists("DocType", "Subscription Gateway Plans") +execute:frappe.delete_doc_if_exists("DocType", "Subscription Template") +execute:frappe.delete_doc_if_exists("DocType", "Portal Payment Gateways Template") +execute:frappe.delete_doc_if_exists("DocType", "Portal Payment Gateways") +execute:frappe.delete_doc_if_exists("DocType", "Subscription Event") +erpnext.patches.dokos.v4_0.set_mode_of_payment_in_payment_requests +erpnext.patches.dokos.v4_0.add_customer_to_payment_request +erpnext.accounts.doctype.mode_of_payment.patches.migrate_fees_and_cost_center_to_account_table +# @dokos + + # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 -erpnext.patches.v14_0.set_maintain_stock_for_bom_item +erpnext.patches.v14_0.set_maintain_stock_for_bom_item \ No newline at end of file diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b616ac63d2c..90b8f8bee22 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -114,7 +114,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe frappe.ui.form.on(this.frm.doctype + " Item", { items_add: function(frm, cdt, cdn) { - debugger var item = frappe.get_doc(cdt, cdn); if (!item.warehouse && frm.doc.set_warehouse) { item.warehouse = frm.doc.set_warehouse; @@ -262,7 +261,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } set_fields_onload_for_line_item() { - if (this.frm.is_new && this.frm.doc?.items) { + if (this.frm.is_new() && this.frm.doc?.items) { this.frm.doc.items.forEach(item => { if (item.docstatus === 0 && frappe.meta.has_field(item.doctype, "use_serial_batch_fields") diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index cff720db5cd..29db0d490f8 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2057,7 +2057,7 @@ class TestSalesOrder(FrappeTestCase): rejected_warehouse = "_Test Dummy Rejected Warehouse - _TC" if not frappe.db.exists("Warehouse", rejected_warehouse): - frappe.get_doc( + wh = frappe.get_doc( { "doctype": "Warehouse", "warehouse_name": rejected_warehouse, @@ -2066,6 +2066,7 @@ class TestSalesOrder(FrappeTestCase): "is_rejected_warehouse": 1, } ).insert() + rejected_warehouse = wh.name se = make_stock_entry(item_code=normal_item, qty=1, to_warehouse=warehouse, do_not_submit=True) for item in [serial_and_batch_item, serial_item, batch_item]: diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index a13377dc53e..6ad66cf7134 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -1,6 +1,7 @@ # Copyright (c) 2019, Dokos SAS and Contributors # License: See license.txt +import click import frappe from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields @@ -8,6 +9,7 @@ from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.modules.utils import sync_customizations_for_doctype from frappe.utils import cint +import erpnext from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules from erpnext.setup.doctype.incoterm.incoterm import create_incoterms @@ -42,6 +44,25 @@ def check_setup_wizard_not_completed(): frappe.throw(message) # nosemgrep +def check_frappe_version(): + def major_version(v: str) -> str: + return v.split(".")[0] + + frappe_version = major_version(frappe.__version__) + erpnext_version = major_version(erpnext.__version__) + + if frappe_version == erpnext_version: + return + + click.secho( + f"You're attempting to install Dokos version {erpnext_version} with Dodock version {frappe_version}. " + "This is not supported and will result in broken install. Switch to correct branch before installing.", + fg="red", + ) + + raise SystemExit(1) + + def set_single_defaults(): for dt in ( "Accounts Settings", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index f7deec93e2f..5347eaf65a8 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -279,7 +279,12 @@ class DeliveryNote(SellingController): elif self.issue_credit_note: self.make_return_invoice() - self.make_bundle_using_old_serial_batch_fields() + for table_name in ["items", "packed_items"]: + if not self.get(table_name): + continue + + self.make_bundle_using_old_serial_batch_fields(table_name) + # Updating stock ledger should always be called after updating prevdoc status, # because updating reserved qty in bin depends upon updated delivered qty in SO self.update_stock_ledger() diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index de2de119f90..d3b7d2ec89d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -112,7 +112,7 @@ class PickList(Document): continue if not row.use_serial_batch_fields and (row.serial_no or row.batch_no): - frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle")) + frappe.throw(_("Please enable Use Old Serial / Batch Fields to make bundle")) if row.use_serial_batch_fields and (not row.serial_and_batch_bundle): sn_doc = SerialBatchCreation( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 13c3b124094..284fce9bfa8 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2233,6 +2233,10 @@ class TestPurchaseReceipt(FrappeTestCase): create_stock_reconciliation, ) + frappe.db.set_single_value( + "Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle", 0 + ) + item_code = make_item( "_Test Use Serial Fields Item Serial Item", properties={"has_serial_no": 1, "serial_no_series": "SNU-TSFISI-.#####"}, @@ -2255,7 +2259,7 @@ class TestPurchaseReceipt(FrappeTestCase): ) self.assertEqual(pr.items[0].use_serial_batch_fields, 1) - self.assertFalse(pr.items[0].serial_no) + self.assertTrue(pr.items[0].serial_no) self.assertTrue(pr.items[0].serial_and_batch_bundle) sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle) diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index 770904038a6..2cdf6c7dad3 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -455,16 +455,16 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): doc.cancel() def test_remove_attached_file(self): - item_code = make_item("_Test Remove Attached File Item", properties={"is_stock_item": 1}) + item = make_item("_Test Remove Attached File Item", properties={"is_stock_item": 1}) make_purchase_receipt( - item_code=item_code, + item_code=item.name, qty=1, rate=100, ) pr1 = make_purchase_receipt( - item_code=item_code, + item_code=item.name, qty=1, rate=100, posting_date=add_days(today(), days=-1), diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index c42ba2a0f71..271cbbc007f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt + from typing import TYPE_CHECKING, Optional, overload import frappe @@ -91,9 +92,6 @@ def make_stock_entry(**args): else: args.qty = cint(args.qty) - if args.serial_no or args.batch_no: - args.use_serial_batch_fields = True - # purpose if not args.purpose: if args.source and args.target: @@ -163,7 +161,11 @@ def make_stock_entry(**args): .name ) - args.serial_no = serial_number + args["serial_no"] = "" + args["batch_no"] = "" + + else: + args.serial_no = serial_number s.append( "items", diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index eb8df641e9e..fd0eb874498 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,7 +5,7 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today +from frappe.utils import add_days, flt, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.accounts_controller import InvalidQtyError @@ -1720,6 +1720,51 @@ class TestStockEntry(FrappeTestCase): mr.cancel() mr.delete() + def test_use_serial_and_batch_fields(self): + item = make_item( + "Test Use Serial and Batch Item SN Item", + {"has_serial_no": 1, "is_stock_item": 1}, + ) + + serial_nos = [ + "Test Use Serial and Batch Item SN Item - SN 001", + "Test Use Serial and Batch Item SN Item - SN 002", + ] + + se = make_stock_entry( + item_code=item.name, + qty=2, + to_warehouse="_Test Warehouse - _TC", + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos), + ) + + self.assertTrue(se.items[0].use_serial_batch_fields) + self.assertTrue(se.items[0].serial_no) + self.assertTrue(se.items[0].serial_and_batch_bundle) + + for serial_no in serial_nos: + self.assertTrue(frappe.db.exists("Serial No", serial_no)) + self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active") + + se1 = make_stock_entry( + item_code=item.name, + qty=2, + from_warehouse="_Test Warehouse - _TC", + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos), + ) + + se1.reload() + + self.assertTrue(se1.items[0].use_serial_batch_fields) + self.assertTrue(se1.items[0].serial_no) + self.assertTrue(se1.items[0].serial_and_batch_bundle) + + for serial_no in serial_nos: + self.assertTrue(frappe.db.exists("Serial No", serial_no)) + self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered") + def make_serialized_item(**args): args = frappe._dict(args) 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 4a6daafcae2..fc0f57c550e 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -610,7 +610,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-02-04 16:16:47.606270", + "modified": "2024-02-25 15:58:40.982582", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 00d53e7af3b..e07e052ff99 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -51,6 +51,7 @@ "use_naming_series", "naming_series_prefix", "use_serial_batch_fields", + "do_not_update_serial_batch_on_creation_of_auto_bundle", "stock_planning_tab", "auto_material_request", "auto_indent", @@ -424,9 +425,18 @@ }, { "default": "1", + "description": "On submission of the stock transaction, system will auto create the Serial and Batch Bundle based on the Serial No / Batch fields.", "fieldname": "use_serial_batch_fields", "fieldtype": "Check", "label": "Use Serial / Batch Fields" + }, + { + "default": "1", + "depends_on": "use_serial_batch_fields", + "description": "If enabled, do not update serial / batch values in the stock transactions on creation of auto Serial \n / Batch Bundle. ", + "fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle", + "fieldtype": "Check", + "label": "Do Not Update Serial / Batch on Creation of Auto Bundle" } ], "icon": "uil uil-setting", @@ -434,7 +444,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-02-04 12:01:31.931864", + "modified": "2024-02-25 16:32:01.084453", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 0d7ad5a4741..1f507d6b57d 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -555,6 +555,7 @@ def get_item_tax_info( "company": company, "tax_category": tax_category, "base_net_rate": item_rates.get(item_code[1]), + "doctype": doctype, } if item_tax_templates: diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 4f9bd0d255e..7758f1e5dd0 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -925,7 +925,6 @@ class SerialBatchCreation: self.batches = get_available_batches(kwargs) def set_auto_serial_batch_entries_for_inward(self): - print(self.get("serial_nos")) if (self.get("batches") and self.has_batch_no) or ( self.get("serial_nos") and self.has_serial_no diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d2d495383fc..2b62baf42b3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -902,7 +902,7 @@ class update_entries_after(object): precision = doc.precision("total_qty") self.wh_data.qty_after_transaction += flt(doc.total_qty, precision) - if self.wh_data.qty_after_transaction: + if flt(self.wh_data.qty_after_transaction, precision): self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt( self.wh_data.qty_after_transaction, precision ) diff --git a/pyproject.toml b/pyproject.toml index a9adcecee8d..7d3614b2837 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,3 @@ force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true indent = "\t" - -[tool.bench.frappe-dependencies] -frappe = ">=16.0.0-dev,<17.0.0" -- GitLab From 3949c1a2504a70cb68e42caf7c332f6b66cc1a39 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 7 Mar 2024 10:03:47 +0100 Subject: [PATCH 23/24] fix: Additional merge conflicts --- README.md | 1 - .../bank_transaction/test_bank_transaction.py | 75 ++++++++++++------- .../pos_invoice_item/pos_invoice_item.json | 2 +- .../sales_invoice_item.json | 2 +- erpnext/stock/serial_batch_bundle.py | 1 - 5 files changed, 51 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 637e49e5619..da2c080700f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # Dokos - Dokos is a 100% open-source management software that is based on ERPNext. It is distributed under the GPLv3 license. diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index e7adb93920b..72d5cbcfdae 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -17,6 +17,34 @@ class TestBankTransaction(unittest.TestCase): pass +def create_bank_account( + bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account" +): + try: + frappe.get_doc( + { + "doctype": "Bank", + "bank_name": bank_name, + } + ).insert() + except frappe.DuplicateEntryError: + pass + + try: + bank_account = frappe.get_doc( + { + "doctype": "Bank Account", + "account_name": bank_account_name, + "bank": bank_name, + "account": gl_account, + } + ).insert(ignore_if_duplicate=True) + except frappe.DuplicateEntryError: + pass + + return bank_account.name + + def create_gl_account(gl_account_name="_Test Bank - _TC"): gl_account = frappe.get_doc( { @@ -31,7 +59,9 @@ def create_gl_account(gl_account_name="_Test Bank - _TC"): return gl_account.name -def add_transactions(bank_account="_Test Bank - _TC"): +def add_transactions(): + create_bank_account() + doc = frappe.get_doc( { "doctype": "Bank Transaction", @@ -39,7 +69,7 @@ def add_transactions(bank_account="_Test Bank - _TC"): "date": "2018-10-23", "debit": 1200, "currency": "INR", - "bank_account": bank_account, + "bank_account": "Checking Account - Citi Bank", } ).insert() doc.submit() @@ -51,7 +81,7 @@ def add_transactions(bank_account="_Test Bank - _TC"): "date": "2018-10-23", "debit": 1700, "currency": "INR", - "bank_account": bank_account, + "bank_account": "Checking Account - Citi Bank", } ).insert() doc.submit() @@ -63,7 +93,7 @@ def add_transactions(bank_account="_Test Bank - _TC"): "date": "2018-10-26", "debit": 690, "currency": "INR", - "bank_account": bank_account, + "bank_account": "Checking Account - Citi Bank", } ).insert() doc.submit() @@ -75,7 +105,7 @@ def add_transactions(bank_account="_Test Bank - _TC"): "date": "2018-10-27", "debit": 3900, "currency": "INR", - "bank_account": bank_account, + "bank_account": "Checking Account - Citi Bank", } ).insert() doc.submit() @@ -87,13 +117,13 @@ def add_transactions(bank_account="_Test Bank - _TC"): "date": "2018-10-27", "credit": 109080, "currency": "INR", - "bank_account": bank_account, + "bank_account": "Checking Account - Citi Bank", } ).insert() doc.submit() -def add_vouchers(gl_account="_Test Bank - _TC"): +def add_payments(): try: frappe.get_doc( { @@ -109,7 +139,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"): pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Conrad Oct 18" pe.reference_date = "2018-10-24" pe.insert() @@ -128,14 +158,14 @@ def add_vouchers(gl_account="_Test Bank - _TC"): pass pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Herr G Oct 18" pe.reference_date = "2018-10-24" pe.insert() pe.submit() pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Herr G Nov 18" pe.reference_date = "2018-11-01" pe.insert() @@ -165,20 +195,8 @@ def add_vouchers(gl_account="_Test Bank - _TC"): except frappe.DuplicateEntryError: pass - pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1) - pi.cash_bank_account = gl_account - pi.insert() - pi.submit() - pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) - pe.reference_no = "Poore Simon's Oct 18" - pe.reference_date = "2018-10-28" - pe.paid_amount = 690 - pe.received_amount = 690 - pe.insert() - pe.submit() - - si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) - pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account) + pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Poore Simon's Oct 18" pe.reference_date = "2018-10-28" pe.insert() @@ -208,11 +226,16 @@ def add_vouchers(gl_account="_Test Bank - _TC"): if not frappe.db.get_value( "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"} ): - mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account}) + mode_of_payment.append( + "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"} + ) + mode_of_payment.save() si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) si.is_pos = 1 - si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080}) + si.append( + "payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080} + ) si.insert() si.submit() diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index d832ada0f6d..4a289cf47fb 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -854,7 +854,7 @@ ], "istable": 1, "links": [], - "modified": "2024-02-04 16:36:25.665743", + "modified": "2024-02-25 15:50:17.140269", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 18c4bf8b2ba..4283b961c7c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -952,7 +952,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-02-04 11:52:16.106541", + "modified": "2024-02-25 15:56:44.828634", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 7758f1e5dd0..4510d76e205 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -925,7 +925,6 @@ class SerialBatchCreation: self.batches = get_available_batches(kwargs) def set_auto_serial_batch_entries_for_inward(self): - if (self.get("batches") and self.has_batch_no) or ( self.get("serial_nos") and self.has_serial_no ): -- GitLab From aacd74f80c4f1563c201d64ae385f3f4e28c2f7d Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 7 Mar 2024 11:56:27 +0100 Subject: [PATCH 24/24] fix: Test --- erpnext/accounts/doctype/journal_entry/test_journal_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 798d3bb6c82..bf1c8a9a239 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -456,6 +456,7 @@ class TestJournalEntry(unittest.TestCase): self.check_gl_entries() + jv.reload() # @dokos # Change cost center for bank account - _Test Cost Center for BS Account create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC" -- GitLab