From 3ead28906cb849d50b53e2402704121acdfb5b9d Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 22 Aug 2023 14:41:07 +0000 Subject: [PATCH 001/280] feat: item(row) wise tax amount rounding --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++----- .../accounts/doctype/payment_entry/payment_entry.py | 5 +++++ erpnext/controllers/taxes_and_totals.py | 5 +++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 6857ba343e..570be095ab 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -32,6 +32,7 @@ "column_break_19", "add_taxes_from_item_tax_template", "book_tax_discount_loss", + "round_row_wise_tax", "print_settings", "show_inclusive_tax_in_print", "show_taxes_as_table_in_print", @@ -58,7 +59,6 @@ "closing_settings_tab", "period_closing_settings_section", "acc_frozen_upto", - "ignore_account_closing_balance", "column_break_25", "frozen_accounts_modifier", "tab_break_dpet", @@ -410,10 +410,10 @@ }, { "default": "0", - "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", - "fieldname": "ignore_account_closing_balance", + "description": "Tax Amount will be rounded on a row(items) level", + "fieldname": "round_row_wise_tax", "fieldtype": "Check", - "label": "Ignore Account Closing Balance" + "label": "Round Tax Amount Row-wise" } ], "icon": "icon-cog", @@ -421,7 +421,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-07-27 15:05:34.000264", + "modified": "2023-08-22 20:11:00.042840", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ac31e8a1db..39ee497fda 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1387,6 +1387,9 @@ class PaymentEntry(AccountsController): def calculate_taxes(self): self.total_taxes_and_charges = 0.0 self.base_total_taxes_and_charges = 0.0 + frappe.flags.round_row_wise_tax = ( + frappe.db.get_single_value("Accounts Settings", "round_row_wise_tax") + ) actual_tax_dict = dict( [ @@ -1398,6 +1401,8 @@ class PaymentEntry(AccountsController): for i, tax in enumerate(self.get("taxes")): current_tax_amount = self.get_current_tax_amount(tax) + if frappe.flags.round_row_wise_tax: + current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount")) if tax.charge_type == "Actual": actual_tax_dict[tax.idx] -= current_tax_amount diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 62d4c53868..1cf1788f43 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -25,6 +25,9 @@ class calculate_taxes_and_totals(object): def __init__(self, doc: Document): self.doc = doc frappe.flags.round_off_applicable_accounts = [] + frappe.flags.round_row_wise_tax = ( + frappe.db.get_single_value("Accounts Settings", "round_row_wise_tax") + ) self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items") @@ -368,6 +371,8 @@ class calculate_taxes_and_totals(object): for i, tax in enumerate(self.doc.get("taxes")): # tax_amount represents the amount of tax for the current step current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map) + if frappe.flags.round_row_wise_tax: + current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount")) # Adjust divisional loss to the last item if tax.charge_type == "Actual": From c20258d2a355388b799b6e9eca710d9254cd0388 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 23 Aug 2023 03:59:08 +0000 Subject: [PATCH 002/280] fix: tax calc changes in js --- .../accounts/doctype/payment_entry/payment_entry.py | 5 ----- erpnext/public/js/controllers/taxes_and_totals.js | 10 +++++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 39ee497fda..ac31e8a1db 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1387,9 +1387,6 @@ class PaymentEntry(AccountsController): def calculate_taxes(self): self.total_taxes_and_charges = 0.0 self.base_total_taxes_and_charges = 0.0 - frappe.flags.round_row_wise_tax = ( - frappe.db.get_single_value("Accounts Settings", "round_row_wise_tax") - ) actual_tax_dict = dict( [ @@ -1401,8 +1398,6 @@ class PaymentEntry(AccountsController): for i, tax in enumerate(self.get("taxes")): current_tax_amount = self.get_current_tax_amount(tax) - if frappe.flags.round_row_wise_tax: - current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount")) if tax.charge_type == "Actual": actual_tax_dict[tax.idx] -= current_tax_amount diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index eeb09cb8b0..8062ce05cd 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -185,7 +185,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { frappe.flags.round_off_applicable_accounts = []; if (me.frm.doc.company) { - return frappe.call({ + frappe.call({ "method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts", "args": { "company": me.frm.doc.company, @@ -198,6 +198,11 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } }); } + + frappe.db.get_single_value("Accounts Settings", "round_row_wise_tax") + .then((round_row_wise_tax) => { + frappe.flags.round_row_wise_tax = round_row_wise_tax; + }) } determine_exclusive_rate() { @@ -338,6 +343,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { $.each(me.frm.doc["taxes"] || [], function(i, tax) { // tax_amount represents the amount of tax for the current step var current_tax_amount = me.get_current_tax_amount(item, tax, item_tax_map); + if (frappe.flags.round_row_wise_tax) { + current_tax_amount = flt(current_tax_amount, precision("tax_amount", tax)); + } // Adjust divisional loss to the last item if (tax.charge_type == "Actual") { From dfb5b88abbd3d00f625f18afeb3e0e57d1fcade6 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 23 Aug 2023 04:01:00 +0000 Subject: [PATCH 003/280] chore: linters --- erpnext/controllers/taxes_and_totals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 1cf1788f43..243b5eb96e 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -25,8 +25,8 @@ class calculate_taxes_and_totals(object): def __init__(self, doc: Document): self.doc = doc frappe.flags.round_off_applicable_accounts = [] - frappe.flags.round_row_wise_tax = ( - frappe.db.get_single_value("Accounts Settings", "round_row_wise_tax") + frappe.flags.round_row_wise_tax = frappe.db.get_single_value( + "Accounts Settings", "round_row_wise_tax" ) self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items") From 0ebcc2cf2c7c3a7c764bdf378822e542ade73253 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 23 Aug 2023 04:51:09 +0000 Subject: [PATCH 004/280] fix: round item_wise_tax_detail in taxes --- erpnext/controllers/taxes_and_totals.py | 9 +++++++-- erpnext/public/js/controllers/taxes_and_totals.js | 11 +++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 243b5eb96e..39d2cf632a 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -483,8 +483,13 @@ class calculate_taxes_and_totals(object): # store tax breakup for each item key = item.item_code or item.item_name item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate - if tax.item_wise_tax_detail.get(key): - item_wise_tax_amount += tax.item_wise_tax_detail[key][1] + if frappe.flags.round_row_wise_tax: + item_wise_tax_amount = flt(item_wise_tax_amount, tax.precision("tax_amount")) + if tax.item_wise_tax_detail.get(key): + item_wise_tax_amount += flt(tax.item_wise_tax_detail[key][1], tax.precision("tax_amount")) + else: + if tax.item_wise_tax_detail.get(key): + item_wise_tax_amount += tax.item_wise_tax_detail[key][1] tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)] diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 8062ce05cd..81dcc06471 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -480,8 +480,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } let item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate; - if (tax_detail && tax_detail[key]) - item_wise_tax_amount += tax_detail[key][1]; + if (frappe.flags.round_row_wise_tax) { + item_wise_tax_amount = flt(item_wise_tax_amount, precision("tax_amount", tax)); + if (tax_detail && tax_detail[key]) { + item_wise_tax_amount += flt(tax_detail[key][1], precision("tax_amount", tax)); + } + } else { + if (tax_detail && tax_detail[key]) + item_wise_tax_amount += tax_detail[key][1]; + } tax_detail[key] = [tax_rate, flt(item_wise_tax_amount, precision("base_tax_amount", tax))]; } From 9e1b2c9f5799ca5b84bf63f34b64a72d15b99097 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Thu, 24 Aug 2023 05:02:14 +0000 Subject: [PATCH 005/280] fix: item wise split up rounding --- erpnext/controllers/taxes_and_totals.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 39d2cf632a..c1dc316c54 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -487,11 +487,15 @@ class calculate_taxes_and_totals(object): item_wise_tax_amount = flt(item_wise_tax_amount, tax.precision("tax_amount")) if tax.item_wise_tax_detail.get(key): item_wise_tax_amount += flt(tax.item_wise_tax_detail[key][1], tax.precision("tax_amount")) + tax.item_wise_tax_detail[key] = [ + tax_rate, + flt(item_wise_tax_amount, tax.precision("tax_amount")), + ] else: if tax.item_wise_tax_detail.get(key): item_wise_tax_amount += tax.item_wise_tax_detail[key][1] - tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)] + tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)] def round_off_totals(self, tax): if tax.account_head in frappe.flags.round_off_applicable_accounts: From 89ddf3272e0ce20cb177688d738703e0f0386a1c Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Thu, 24 Aug 2023 05:56:56 +0000 Subject: [PATCH 006/280] fix(regional): item wise tax calc issue --- erpnext/accounts/utils.py | 6 +-- .../regional/united_arab_emirates/utils.py | 38 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9d6d0f91fb..1aefeaacf7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -908,9 +908,9 @@ def get_outstanding_invoices( min_outstanding=None, max_outstanding=None, accounting_dimensions=None, - vouchers=None, # list of dicts [{'voucher_type': '', 'voucher_no': ''}] for filtering - limit=None, # passed by reconciliation tool - voucher_no=None, # filter passed by reconciliation tool + vouchers=None, # list of dicts [{'voucher_type': '', 'voucher_no': ''}] for filtering + limit=None, # passed by reconciliation tool + voucher_no=None, # filter passed by reconciliation tool ): ple = qb.DocType("Payment Ledger Entry") diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index a910af6a1d..efeaeed324 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -7,32 +7,32 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax def update_itemised_tax_data(doc): + # maybe this should be a standard function rather than a regional one if not doc.taxes: return + if not doc.items: + return + + meta = frappe.get_meta(doc.items[0].doctype) + if not meta.has_field("tax_rate"): + return + itemised_tax = get_itemised_tax(doc.taxes) for row in doc.items: - tax_rate = 0.0 - item_tax_rate = 0.0 + tax_rate, tax_amount = 0.0, 0.0 + # dont even bother checking in item tax template as it contains both input and output accounts - double the tax rate + item_code = row.item_code or row.item_name + if itemised_tax.get(item_code): + for tax in itemised_tax.get(row.item_code).values(): + _tax_rate = flt(tax.get("tax_rate", 0), row.precision("tax_rate")) + tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) + tax_rate += _tax_rate - if row.item_tax_rate: - item_tax_rate = frappe.parse_json(row.item_tax_rate) - - # First check if tax rate is present - # If not then look up in item_wise_tax_detail - if item_tax_rate: - for account, rate in item_tax_rate.items(): - tax_rate += rate - elif row.item_code and itemised_tax.get(row.item_code): - tax_rate = sum([tax.get("tax_rate", 0) for d, tax in itemised_tax.get(row.item_code).items()]) - - meta = frappe.get_meta(row.doctype) - - if meta.has_field("tax_rate"): - row.tax_rate = flt(tax_rate, row.precision("tax_rate")) - row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount")) - row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) + row.tax_rate = flt(tax_rate, row.precision("tax_rate")) + row.tax_amount = flt(tax_amount, row.precision("tax_amount")) + row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) def get_account_currency(account): From 159be1d40fbc07b619c27f782d872bf2d1f35a3a Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sun, 27 Aug 2023 18:43:42 +0000 Subject: [PATCH 007/280] fix: revert `ignore_account_closing_balance` field --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 570be095ab..061bab320e 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -59,6 +59,7 @@ "closing_settings_tab", "period_closing_settings_section", "acc_frozen_upto", + "ignore_account_closing_balance", "column_break_25", "frozen_accounts_modifier", "tab_break_dpet", @@ -408,6 +409,13 @@ "fieldtype": "Check", "label": "Enable Fuzzy Matching" }, + { + "default": "0", + "description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ", + "fieldname": "ignore_account_closing_balance", + "fieldtype": "Check", + "label": "Ignore Account Closing Balance" + }, { "default": "0", "description": "Tax Amount will be rounded on a row(items) level", @@ -421,7 +429,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-22 20:11:00.042840", + "modified": "2023-08-28 00:12:02.740633", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 662e9547daa662fa3e669644479b2a43fb5c2cb7 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 6 Sep 2023 17:40:19 +0200 Subject: [PATCH 008/280] refactor: Remove Regionalisation From France as now there is an App ERPNext France to manage it --- erpnext/regional/__init__.py | 4 +- erpnext/regional/france/__init__.py | 0 erpnext/regional/france/setup.py | 30 -- erpnext/regional/france/utils.py | 8 - .../__init__.py | 0 .../fichier_des_ecritures_comptables_[fec].js | 97 ----- ...ichier_des_ecritures_comptables_[fec].json | 19 - .../fichier_des_ecritures_comptables_[fec].py | 339 ------------------ 8 files changed, 2 insertions(+), 495 deletions(-) delete mode 100644 erpnext/regional/france/__init__.py delete mode 100644 erpnext/regional/france/setup.py delete mode 100644 erpnext/regional/france/utils.py delete mode 100644 erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/__init__.py delete mode 100644 erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].js delete mode 100644 erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].json delete mode 100644 erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index ec2db81124..bd5d540103 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -10,7 +10,7 @@ from erpnext import get_region def check_deletion_permission(doc, method): region = get_region(doc.company) - if region in ["Nepal", "France"] and doc.docstatus != 0: + if region in ["Nepal"] and doc.docstatus != 0: frappe.throw(_("Deletion is not permitted for country {0}").format(region)) @@ -20,7 +20,7 @@ def create_transaction_log(doc, method): Called on submit of Sales Invoice and Payment Entry. """ region = get_region() - if region not in ["France", "Germany"]: + if region not in ["Germany"]: return data = str(doc.as_dict()) diff --git a/erpnext/regional/france/__init__.py b/erpnext/regional/france/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/france/setup.py b/erpnext/regional/france/setup.py deleted file mode 100644 index da772d6b77..0000000000 --- a/erpnext/regional/france/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields - - -def setup(company=None, patch=True): - make_custom_fields() - add_custom_roles_for_reports() - - -def make_custom_fields(): - custom_fields = { - "Company": [ - dict(fieldname="siren_number", label="SIREN Number", fieldtype="Data", insert_after="website") - ] - } - - create_custom_fields(custom_fields) - - -def add_custom_roles_for_reports(): - report_name = "Fichier des Ecritures Comptables [FEC]" - - if not frappe.db.get_value("Custom Role", dict(report=report_name)): - frappe.get_doc( - dict(doctype="Custom Role", report=report_name, roles=[dict(role="Accounts Manager")]) - ).insert() diff --git a/erpnext/regional/france/utils.py b/erpnext/regional/france/utils.py deleted file mode 100644 index 65dfd2db91..0000000000 --- a/erpnext/regional/france/utils.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt - - -# don't remove this function it is used in tests -def test_method(): - """test function""" - return "overridden" diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/__init__.py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].js b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].js deleted file mode 100644 index b85b58f636..0000000000 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].js +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.query_reports["Fichier des Ecritures Comptables [FEC]"] = { - "filters": [ - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), - "reqd": 1 - } - ], - onload: function(query_report) { - query_report.page.add_inner_button(__("Export"), function() { - fec_export(query_report); - }); - - query_report.add_make_chart_button = function() { - // - }; - - query_report.export_report = function() { - fec_export(query_report); - }; - } -}; - -let fec_export = function(query_report) { - const fiscal_year = query_report.get_values().fiscal_year; - const company = query_report.get_values().company; - - frappe.db.get_value("Company", company, "siren_number", (value) => { - const company_data = value.siren_number; - if (company_data === null || company_data === undefined) { - frappe.msgprint(__("Please register the SIREN number in the company information file")); - } else { - frappe.db.get_value("Fiscal Year", fiscal_year, "year_end_date", (r) => { - const fy = r.year_end_date; - const title = company_data + "FEC" + moment(fy).format('YYYYMMDD'); - const column_row = query_report.columns.map(col => col.label); - const column_data = query_report.get_data_for_csv(false); - const result = [column_row].concat(column_data); - downloadify(result, null, title); - }); - - } - }); -}; - -let downloadify = function(data, roles, title) { - if (roles && roles.length && !has_common(roles, roles)) { - frappe.msgprint(__("Export not allowed. You need {0} role to export.", [frappe.utils.comma_or(roles)])); - return; - } - - const filename = title + ".txt"; - let csv_data = to_tab_csv(data); - const a = document.createElement('a'); - - if ("download" in a) { - // Used Blob object, because it can handle large files - let blob_object = new Blob([csv_data], { - type: 'text/csv;charset=UTF-8' - }); - a.href = URL.createObjectURL(blob_object); - a.download = filename; - - } else { - // use old method - a.href = 'data:attachment/csv,' + encodeURIComponent(csv_data); - a.download = filename; - a.target = "_blank"; - } - - document.body.appendChild(a); - a.click(); - - document.body.removeChild(a); -}; - -let to_tab_csv = function(data) { - let res = []; - $.each(data, function(i, row) { - res.push(row.join("\t")); - }); - return res.join("\n"); -}; diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].json b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].json deleted file mode 100644 index 9b48e11428..0000000000 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 0, - "creation": "2018-01-10 15:10:16.650129", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2018-01-11 10:27:25.595485", - "modified_by": "Administrator", - "module": "Regional", - "name": "Fichier des Ecritures Comptables [FEC]", - "owner": "Administrator", - "ref_doctype": "GL Entry", - "report_name": "Fichier des Ecritures Comptables [FEC]", - "report_type": "Script Report", - "roles": [] -} \ No newline at end of file diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py deleted file mode 100644 index 6717989008..0000000000 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py +++ /dev/null @@ -1,339 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import re - -import frappe -from frappe import _ -from frappe.utils import format_datetime - -COLUMNS = [ - { - "label": "JournalCode", - "fieldname": "JournalCode", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "JournalLib", - "fieldname": "JournalLib", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "EcritureNum", - "fieldname": "EcritureNum", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "EcritureDate", - "fieldname": "EcritureDate", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "CompteNum", - "fieldname": "CompteNum", - "fieldtype": "Link", - "options": "Account", - "width": 100, - }, - { - "label": "CompteLib", - "fieldname": "CompteLib", - "fieldtype": "Link", - "options": "Account", - "width": 200, - }, - { - "label": "CompAuxNum", - "fieldname": "CompAuxNum", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "CompAuxLib", - "fieldname": "CompAuxLib", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "PieceRef", - "fieldname": "PieceRef", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "PieceDate", - "fieldname": "PieceDate", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "EcritureLib", - "fieldname": "EcritureLib", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "Debit", - "fieldname": "Debit", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "Credit", - "fieldname": "Credit", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "EcritureLet", - "fieldname": "EcritureLet", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "DateLet", - "fieldname": "DateLet", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "ValidDate", - "fieldname": "ValidDate", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "Montantdevise", - "fieldname": "Montantdevise", - "fieldtype": "Data", - "width": 90, - }, - { - "label": "Idevise", - "fieldname": "Idevise", - "fieldtype": "Data", - "width": 90, - }, -] - - -def execute(filters=None): - validate_filters(filters) - return COLUMNS, get_result( - company=filters["company"], - fiscal_year=filters["fiscal_year"], - ) - - -def validate_filters(filters): - if not filters.get("company"): - frappe.throw(_("{0} is mandatory").format(_("Company"))) - - if not filters.get("fiscal_year"): - frappe.throw(_("{0} is mandatory").format(_("Fiscal Year"))) - - -def get_gl_entries(company, fiscal_year): - gle = frappe.qb.DocType("GL Entry") - sales_invoice = frappe.qb.DocType("Sales Invoice") - purchase_invoice = frappe.qb.DocType("Purchase Invoice") - journal_entry = frappe.qb.DocType("Journal Entry") - payment_entry = frappe.qb.DocType("Payment Entry") - customer = frappe.qb.DocType("Customer") - supplier = frappe.qb.DocType("Supplier") - employee = frappe.qb.DocType("Employee") - - debit = frappe.query_builder.functions.Sum(gle.debit).as_("debit") - credit = frappe.query_builder.functions.Sum(gle.credit).as_("credit") - debit_currency = frappe.query_builder.functions.Sum(gle.debit_in_account_currency).as_( - "debitCurr" - ) - credit_currency = frappe.query_builder.functions.Sum(gle.credit_in_account_currency).as_( - "creditCurr" - ) - - query = ( - frappe.qb.from_(gle) - .left_join(sales_invoice) - .on(gle.voucher_no == sales_invoice.name) - .left_join(purchase_invoice) - .on(gle.voucher_no == purchase_invoice.name) - .left_join(journal_entry) - .on(gle.voucher_no == journal_entry.name) - .left_join(payment_entry) - .on(gle.voucher_no == payment_entry.name) - .left_join(customer) - .on(gle.party == customer.name) - .left_join(supplier) - .on(gle.party == supplier.name) - .left_join(employee) - .on(gle.party == employee.name) - .select( - gle.posting_date.as_("GlPostDate"), - gle.name.as_("GlName"), - gle.account, - gle.transaction_date, - debit, - credit, - debit_currency, - credit_currency, - gle.voucher_type, - gle.voucher_no, - gle.against_voucher_type, - gle.against_voucher, - gle.account_currency, - gle.against, - gle.party_type, - gle.party, - sales_invoice.name.as_("InvName"), - sales_invoice.title.as_("InvTitle"), - sales_invoice.posting_date.as_("InvPostDate"), - purchase_invoice.name.as_("PurName"), - purchase_invoice.title.as_("PurTitle"), - purchase_invoice.posting_date.as_("PurPostDate"), - journal_entry.cheque_no.as_("JnlRef"), - journal_entry.posting_date.as_("JnlPostDate"), - journal_entry.title.as_("JnlTitle"), - payment_entry.name.as_("PayName"), - payment_entry.posting_date.as_("PayPostDate"), - payment_entry.title.as_("PayTitle"), - customer.customer_name, - customer.name.as_("cusName"), - supplier.supplier_name, - supplier.name.as_("supName"), - employee.employee_name, - employee.name.as_("empName"), - ) - .where((gle.company == company) & (gle.fiscal_year == fiscal_year)) - .groupby(gle.voucher_type, gle.voucher_no, gle.account) - .orderby(gle.posting_date, gle.voucher_no) - ) - - return query.run(as_dict=True) - - -def get_result(company, fiscal_year): - data = get_gl_entries(company, fiscal_year) - - result = [] - - company_currency = frappe.get_cached_value("Company", company, "default_currency") - accounts = frappe.get_all( - "Account", filters={"Company": company}, fields=["name", "account_number"] - ) - - for d in data: - JournalCode = re.split("-|/|[0-9]", d.get("voucher_no"))[0] - - if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith( - "{0}/".format(JournalCode) - ): - EcritureNum = re.split("-|/", d.get("voucher_no"))[1] - else: - EcritureNum = re.search(r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE)[1] - - EcritureDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") - - account_number = [ - account.account_number for account in accounts if account.name == d.get("account") - ] - if account_number[0] is not None: - CompteNum = account_number[0] - else: - frappe.throw( - _( - "Account number for account {0} is not available.
Please setup your Chart of Accounts correctly." - ).format(d.get("account")) - ) - - if d.get("party_type") == "Customer": - CompAuxNum = d.get("cusName") - CompAuxLib = d.get("customer_name") - - elif d.get("party_type") == "Supplier": - CompAuxNum = d.get("supName") - CompAuxLib = d.get("supplier_name") - - elif d.get("party_type") == "Employee": - CompAuxNum = d.get("empName") - CompAuxLib = d.get("employee_name") - - elif d.get("party_type") == "Student": - CompAuxNum = d.get("stuName") - CompAuxLib = d.get("student_name") - - elif d.get("party_type") == "Member": - CompAuxNum = d.get("memName") - CompAuxLib = d.get("member_name") - - else: - CompAuxNum = "" - CompAuxLib = "" - - ValidDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") - - PieceRef = d.get("voucher_no") or "Sans Reference" - - # EcritureLib is the reference title unless it is an opening entry - if d.get("is_opening") == "Yes": - EcritureLib = _("Opening Entry Journal") - if d.get("voucher_type") == "Sales Invoice": - EcritureLib = d.get("InvTitle") - elif d.get("voucher_type") == "Purchase Invoice": - EcritureLib = d.get("PurTitle") - elif d.get("voucher_type") == "Journal Entry": - EcritureLib = d.get("JnlTitle") - elif d.get("voucher_type") == "Payment Entry": - EcritureLib = d.get("PayTitle") - else: - EcritureLib = d.get("voucher_type") - - PieceDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd") - - debit = "{:.2f}".format(d.get("debit")).replace(".", ",") - - credit = "{:.2f}".format(d.get("credit")).replace(".", ",") - - Idevise = d.get("account_currency") - - if Idevise != company_currency: - Montantdevise = ( - "{:.2f}".format(d.get("debitCurr")).replace(".", ",") - if d.get("debitCurr") != 0 - else "{:.2f}".format(d.get("creditCurr")).replace(".", ",") - ) - else: - Montantdevise = ( - "{:.2f}".format(d.get("debit")).replace(".", ",") - if d.get("debit") != 0 - else "{:.2f}".format(d.get("credit")).replace(".", ",") - ) - - row = [ - JournalCode, - d.get("voucher_type"), - EcritureNum, - EcritureDate, - CompteNum, - d.get("account"), - CompAuxNum, - CompAuxLib, - PieceRef, - PieceDate, - EcritureLib, - debit, - credit, - "", - "", - ValidDate, - Montantdevise, - Idevise, - ] - - result.append(row) - - return result From f447177f8da7dfae5696242dfcdfdb32ae73f29b Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 6 Sep 2023 17:41:27 +0200 Subject: [PATCH 009/280] refactor: Remove Regionalisation From France as now there is an App ERPNext France to manage it --- erpnext/regional/address_template/templates/france.html | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 erpnext/regional/address_template/templates/france.html diff --git a/erpnext/regional/address_template/templates/france.html b/erpnext/regional/address_template/templates/france.html deleted file mode 100644 index 752331eeec..0000000000 --- a/erpnext/regional/address_template/templates/france.html +++ /dev/null @@ -1,5 +0,0 @@ -{% if address_line1 %}{{ address_line1 }}{% endif -%} -{% if address_line2 %}
{{ address_line2 }}{% endif -%} -{% if pincode %}
{{ pincode }}{% endif -%} -{% if city %} {{ city }}{% endif -%} -{% if country %}
{{ country }}{% endif -%} From a3ebef7a29de51640c69c4e086fba0b4ba957cbc Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 13 Sep 2023 17:18:13 +0200 Subject: [PATCH 010/280] fix: fix CI --- erpnext/tests/test_regional.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/tests/test_regional.py b/erpnext/tests/test_regional.py index 2c16defc20..55c8bb7d47 100644 --- a/erpnext/tests/test_regional.py +++ b/erpnext/tests/test_regional.py @@ -14,6 +14,3 @@ class TestInit(unittest.TestCase): def test_regional_overrides(self): frappe.flags.country = "Maldives" self.assertEqual(test_method(), "original") - - frappe.flags.country = "France" - self.assertEqual(test_method(), "overridden") From 6e1ad4c5bded8193be96a3e5bbc1df1aa0e3f956 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 17 Sep 2023 20:17:40 +0200 Subject: [PATCH 011/280] fix: payment request rounding in multi-currency and on status update --- erpnext/accounts/doctype/payment_request/payment_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 11d6d5f433..df4f1b2c3f 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -249,7 +249,7 @@ class PaymentRequest(Document): if ( party_account_currency == ref_doc.company_currency and party_account_currency != self.currency ): - party_amount = ref_doc.base_grand_total + party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total") else: party_amount = self.grand_total From c5667a6cc101fcf0e1219575906d9b84cdd429c1 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 21 Sep 2023 12:53:30 +0200 Subject: [PATCH 012/280] fix: missing french translation on Lead and Prospect --- erpnext/translations/fr.csv | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 31806310b0..fbd2ece544 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -8840,3 +8840,20 @@ Global Defaults,Valeurs par Défaut Globales, Is Mandatory,Est obligatoire, WhatsApp,WhatsApp, Make a call,Passer un coup de téléphone, +No of Employees,Nb de salarié(e)s +No. of Employees,Nb de salarié(e)s +Annual Revenue,CA annuel +Qualified By,Qualifié par +Qualified on,Qualifié le +Open Tasks,Tâche à faire ouverte +No open task,Pas de Tâche à faire ouverte +Open Events,Evénements ouvert +No open event,Pas Evénements ouvert +New Task,Nv. Tâche à faire +No Notes,Pas de note +New Note,Nouvelle Note +Prospect Owner,Resp. du Prospect +Deal Owner,Resp. de l'opportunité +Stage,Etape +Probability,Probabilité +Closing,Clôture \ No newline at end of file From e922ec60eb44196255a1e4b9cce839d6f7f05493 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:26:20 +0530 Subject: [PATCH 013/280] feat: allow on submit fields --- .../purchase_invoice/purchase_invoice.json | 23 +++++++++++++------ .../purchase_invoice_item.json | 1 + .../purchase_taxes_and_charges.json | 2 ++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 0599e19d9b..f3c01816cc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -166,6 +166,7 @@ "against_expense_account", "column_break_63", "unrealized_profit_loss_account", + "repost_required", "subscription_section", "subscription", "auto_repeat", @@ -191,8 +192,7 @@ "inter_company_invoice_reference", "is_old_subcontracting_flow", "remarks", - "connections_tab", - "column_break_38" + "connections_tab" ], "fields": [ { @@ -990,6 +990,7 @@ "print_hide": 1 }, { + "allow_on_submit": 1, "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", @@ -1053,6 +1054,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "depends_on": "eval:flt(doc.write_off_amount)!=0", "fieldname": "write_off_account", "fieldtype": "Link", @@ -1217,6 +1219,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "default": "No", "fieldname": "is_opening", "fieldtype": "Select", @@ -1349,6 +1352,7 @@ "options": "Project" }, { + "allow_on_submit": 1, "depends_on": "eval:doc.is_internal_supplier", "description": "Unrealized Profit/Loss account for intra-company transfers", "fieldname": "unrealized_profit_loss_account", @@ -1504,10 +1508,6 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_38", - "fieldtype": "Column Break" - }, { "fieldname": "column_break_50", "fieldtype": "Column Break" @@ -1578,13 +1578,22 @@ "fieldname": "use_company_roundoff_cost_center", "fieldtype": "Check", "label": "Use Company Default Round Off Cost Center" + }, + { + "default": "0", + "fieldname": "repost_required", + "fieldtype": "Check", + "hidden": 1, + "label": "Repost Required", + "options": "Account", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-07-25 17:22:59.145031", + "modified": "2023-09-21 12:22:04.545106", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", 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 81c7577467..3690142aac 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -473,6 +473,7 @@ "label": "Accounting" }, { + "allow_on_submit": 1, "fieldname": "expense_account", "fieldtype": "Link", "label": "Expense Head", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index d86abade92..347cae05b7 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -86,6 +86,7 @@ "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "columns": 2, "fieldname": "account_head", "fieldtype": "Link", @@ -97,6 +98,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "default": ":Company", "fieldname": "cost_center", "fieldtype": "Link", From 68effd93bdb1a91a8625d983cd9b8afeb7b1eb3b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:28:07 +0530 Subject: [PATCH 014/280] refactor: move reposting logic to common controller --- .../doctype/sales_invoice/sales_invoice.py | 100 +++++------------- erpnext/controllers/accounts_controller.py | 55 ++++++++++ 2 files changed, 81 insertions(+), 74 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7bdb2b49ce..87f4bd0023 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -11,9 +11,6 @@ from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form import erpnext from erpnext.accounts.deferred_revenue import validate_service_stop_date -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( - get_accounting_dimensions, -) from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, validate_loyalty_points, @@ -517,79 +514,34 @@ class SalesInvoice(SellingController): def on_update_after_submit(self): if hasattr(self, "repost_required"): - needs_repost = 0 - - # Check if any field affecting accounting entry is altered - doc_before_update = self.get_doc_before_save() - accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"] - - # Check if opening entry check updated - if doc_before_update.get("is_opening") != self.is_opening: - needs_repost = 1 - - if not needs_repost: - # Parent Level Accounts excluding party account - for field in ( - "additional_discount_account", - "cash_bank_account", - "account_for_change_amount", - "write_off_account", - "loyalty_redemption_account", - "unrealized_profit_loss_account", - ): - if doc_before_update.get(field) != self.get(field): - needs_repost = 1 - break - - # Check for parent accounting dimensions - for dimension in accounting_dimensions: - if doc_before_update.get(dimension) != self.get(dimension): - needs_repost = 1 - break - - # Check for child tables - if self.check_if_child_table_updated( - "items", - doc_before_update, - ("income_account", "expense_account", "discount_account"), - accounting_dimensions, - ): - needs_repost = 1 - - if self.check_if_child_table_updated( - "taxes", doc_before_update, ("account_head",), accounting_dimensions - ): - needs_repost = 1 - + fields_to_check = [ + "additional_discount_account", + "cash_bank_account", + "account_for_change_amount", + "write_off_account", + "loyalty_redemption_account", + "unrealized_profit_loss_account", + ] + child_tables = { + "items": ("income_account", "expense_account", "discount_account"), + "taxes": ("account_head",), + } + self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) + self.validate_deferred_accounting_before_repost() self.validate_accounts() + self.db_set("repost_required", self.needs_repost) - # validate if deferred revenue is enabled for any item - # Don't allow to update the invoice if deferred revenue is enabled - if needs_repost: - for item in self.get("items"): - if item.enable_deferred_revenue: - frappe.throw( - _( - "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." - ).format(item.item_code) - ) - - self.db_set("repost_required", needs_repost) - - def check_if_child_table_updated( - self, child_table, doc_before_update, fields_to_check, accounting_dimensions - ): - # Check if any field affecting accounting entry is altered - for index, item in enumerate(self.get(child_table)): - for field in fields_to_check: - if doc_before_update.get(child_table)[index].get(field) != item.get(field): - return True - - for dimension in accounting_dimensions: - if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension): - return True - - return False + def validate_deferred_accounting_before_repost(self): + # validate if deferred revenue is enabled for any item + # Don't allow to update the invoice if deferred revenue is enabled + if self.needs_repost: + for item in self.get("items"): + if item.enable_deferred_revenue: + frappe.throw( + _( + "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." + ).format(item.item_code) + ) @frappe.whitelist() def repost_accounting_entries(self): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e635aa7924..08978640bc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2186,6 +2186,44 @@ class AccountsController(TransactionBase): _("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx) ) + def check_if_fields_updated(self, fields_to_check, child_tables): + # Check if any field affecting accounting entry is altered + 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 + + 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 + + return needs_repost + + @frappe.whitelist() + def repost_accounting_entries(self): + if self.repost_required: + self.docstatus = 2 + self.make_gl_entries_on_cancel() + self.docstatus = 1 + self.make_gl_entries() + self.db_set("repost_required", 0) + else: + frappe.throw(_("No updates pending for reposting")) + @frappe.whitelist() def get_tax_rate(account_head): @@ -3191,6 +3229,23 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.create_stock_reservation_entries() +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"] + # Check if any field affecting accounting entry is altered + for index, item in enumerate(child_table_after_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 + + @erpnext.allow_regional def validate_regional(doc): pass From e77814fbc0b15a47c8ecde579fc0d2a9e200a476 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:29:14 +0530 Subject: [PATCH 015/280] feat: add repost btn in invoice --- .../purchase_invoice/purchase_invoice.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c8c9ad1b3a..d895a947e2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -65,6 +65,25 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm); } + if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) { + this.frm.set_intro(__("Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update.")); + this.frm.add_custom_button(__('Repost Accounting Entries'), + () => { + this.frm.call({ + doc: this.frm.doc, + method: 'repost_accounting_entries', + freeze: true, + freeze_message: __('Reposting...'), + callback: (r) => { + if (!r.exc) { + frappe.msgprint(__('Accounting Entries are reposted')); + me.frm.refresh(); + } + } + }); + }).removeClass('btn-default').addClass('btn-warning'); + } + if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){ if(doc.on_hold) { this.frm.add_custom_button( From 23470bf52dd8f7b5fee127e5bc506938ae9632f3 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:30:53 +0530 Subject: [PATCH 016/280] feat: allow repost for pi --- .../purchase_invoice/purchase_invoice.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 55972719f8..fbe4e8b41a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -522,6 +522,31 @@ class PurchaseInvoice(BuyingController): self.process_common_party_accounting() + def on_update_after_submit(self): + if hasattr(self, "repost_required"): + fields_to_check = [ + "cash_bank_account", + "write_off_account" "unrealized_profit_loss_account", + ] + child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} + self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) + self.validate_deferred_accounting_before_repost() + self.validate_write_off_account() + self.validate_expense_account() + self.db_set("repost_required", self.needs_repost) + + def validate_deferred_accounting_before_repost(self): + # validate if deferred expense is enabled for any item + # Don't allow to update the invoice if deferred expense is enabled + if self.needs_repost: + for item in self.get("items"): + if item.enable_deferred_expense: + frappe.throw( + _( + "Deferred Expense is enabled for item {0}. You cannot update the invoice after submission." + ).format(item.item_code) + ) + def make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: gl_entries = self.get_gl_entries() From c88f6d1fa7f28453d81cabf6726bf8eb1b5d8969 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 21 Sep 2023 17:41:59 +0530 Subject: [PATCH 017/280] fix: linting issues --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fbe4e8b41a..97661e8824 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -526,7 +526,8 @@ class PurchaseInvoice(BuyingController): if hasattr(self, "repost_required"): fields_to_check = [ "cash_bank_account", - "write_off_account" "unrealized_profit_loss_account", + "write_off_account", + "unrealized_profit_loss_account", ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) From c66c4385759b25243f946b708055bccc12561809 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 22 Sep 2023 11:22:25 +0530 Subject: [PATCH 018/280] test: reposted acc entries for pi --- .../purchase_invoice/purchase_invoice.js | 4 +-- .../purchase_invoice/test_purchase_invoice.py | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d895a947e2..095617dbcf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -66,7 +66,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. } if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) { - this.frm.set_intro(__("Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update.")); + this.frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update.")); this.frm.add_custom_button(__('Repost Accounting Entries'), () => { this.frm.call({ @@ -76,7 +76,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. freeze_message: __('Reposting...'), callback: (r) => { if (!r.exc) { - frappe.msgprint(__('Accounting Entries are reposted')); + frappe.msgprint(__('Accounting Entries are reposted.')); me.frm.refresh(); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b4dd75a714..0aaea060b5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1744,7 +1744,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi = make_purchase_invoice( company="_Test Company", - customer="_Test Supplier", do_not_save=True, do_not_submit=True, rate=1000, @@ -1862,7 +1861,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi = make_purchase_invoice( company="_Test Company", - customer="_Test Supplier", do_not_save=True, do_not_submit=True, rate=1000, @@ -1892,6 +1890,32 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): clear_dimension_defaults("Branch") disable_dimension() + def test_repost_accounting_entries(self): + pi = make_purchase_invoice( + rate=1000, + price_list_rate=1000, + qty=1, + ) + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate()], + ["Creditors - _TC", 0.0, 1000, nowdate()], + ] + check_gl_entries(self, pi.name, expected_gle, nowdate()) + + pi.items[0].expense_account = "Service - _TC" + pi.save() + pi.load_from_db() + self.assertTrue(pi.repost_required) + pi.repost_accounting_entries() + + expected_gle = [ + ["Creditors - _TC", 0.0, 1000, nowdate()], + ["Service - _TC", 1000, 0.0, nowdate()], + ] + check_gl_entries(self, pi.name, expected_gle, nowdate()) + pi.load_from_db() + self.assertFalse(pi.repost_required) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( From ac28a5b372d96badd7ba808308a3f1270943f892 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 22 Sep 2023 12:41:17 +0530 Subject: [PATCH 019/280] fix: allocate amt for payment term invoices --- .../doctype/payment_entry/payment_entry.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 0203c45058..8ae4aa748a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -874,20 +874,25 @@ frappe.ui.form.on('Payment Entry', { } } + let outstanding_amount; $.each(frm.doc.references || [], function(i, row) { if (frappe.flags.allocate_payment_amount == 0) { //If allocate payment amount checkbox is unchecked, set zero to allocate amount row.allocated_amount = 0; - } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) { - if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) { - row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ? - allocated_positive_outstanding : row.outstanding_amount; + } else if (frappe.flags.allocate_payment_amount != 0 && (row.payment_term || !row.allocated_amount || paid_amount_change)) { + if(row.payment_term) + outstanding_amount = row.allocated_amount; + else + outstanding_amount = row.outstanding_amount; + if (outstanding_amount > 0 && allocated_positive_outstanding >= 0) { + row.allocated_amount = (outstanding_amount >= allocated_positive_outstanding) ? + allocated_positive_outstanding : outstanding_amount; allocated_positive_outstanding -= flt(row.allocated_amount); - } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { - row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ? - -1*allocated_negative_outstanding : row.outstanding_amount; + } else if (outstanding_amount < 0 && allocated_negative_outstanding) { + row.allocated_amount = (Math.abs(outstanding_amount) >= allocated_negative_outstanding) ? + -1*allocated_negative_outstanding : outstanding_amount; allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount)); } } From eded7871f347f0f1e5149087d5e7a3ccfcf9dbf1 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 21 Sep 2023 07:32:08 +0530 Subject: [PATCH 020/280] refactor!: remove `GoCardless Settings` --- .../doctype/gocardless_settings/__init__.py | 89 ------- .../gocardless_settings.js | 8 - .../gocardless_settings.json | 211 ----------------- .../gocardless_settings.py | 220 ------------------ .../test_gocardless_settings.py | 8 - pyproject.toml | 2 - 6 files changed, 538 deletions(-) delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py deleted file mode 100644 index 65be5993ff..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt - - -import hashlib -import hmac -import json - -import frappe - - -@frappe.whitelist(allow_guest=True) -def webhooks(): - r = frappe.request - if not r: - return - - if not authenticate_signature(r): - raise frappe.AuthenticationError - - gocardless_events = json.loads(r.get_data()) or [] - for event in gocardless_events["events"]: - set_status(event) - - return 200 - - -def set_status(event): - resource_type = event.get("resource_type", {}) - - if resource_type == "mandates": - set_mandate_status(event) - - -def set_mandate_status(event): - mandates = [] - if isinstance(event["links"], (list,)): - for link in event["links"]: - mandates.append(link["mandate"]) - else: - mandates.append(event["links"]["mandate"]) - - if ( - event["action"] == "pending_customer_approval" - or event["action"] == "pending_submission" - or event["action"] == "submitted" - or event["action"] == "active" - ): - disabled = 0 - else: - disabled = 1 - - for mandate in mandates: - frappe.db.set_value("GoCardless Mandate", mandate, "disabled", disabled) - - -def authenticate_signature(r): - """Returns True if the received signature matches the generated signature""" - received_signature = frappe.get_request_header("Webhook-Signature") - - if not received_signature: - return False - - for key in get_webhook_keys(): - computed_signature = hmac.new(key.encode("utf-8"), r.get_data(), hashlib.sha256).hexdigest() - if hmac.compare_digest(str(received_signature), computed_signature): - return True - - return False - - -def get_webhook_keys(): - def _get_webhook_keys(): - webhook_keys = [ - d.webhooks_secret - for d in frappe.get_all( - "GoCardless Settings", - fields=["webhooks_secret"], - ) - if d.webhooks_secret - ] - - return webhook_keys - - return frappe.cache().get_value("gocardless_webhooks_secret", _get_webhook_keys) - - -def clear_cache(): - frappe.cache().delete_value("gocardless_webhooks_secret") diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js deleted file mode 100644 index 241129719b..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('GoCardless Settings', { - refresh: function(frm) { - erpnext.utils.check_payments_app(); - } -}); diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json deleted file mode 100644 index cca36536ac..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:gateway_name", - "beta": 0, - "creation": "2018-02-06 16:11:10.028249", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gateway_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Payment Gateway Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "access_token", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Access Token", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "webhooks_secret", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Webhooks Secret", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "use_sandbox", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use Sandbox", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2022-02-12 14:18:47.209114", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "GoCardless Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py deleted file mode 100644 index 4a29a6a21d..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt - - -from urllib.parse import urlencode - -import frappe -import gocardless_pro -from frappe import _ -from frappe.integrations.utils import create_request_log -from frappe.model.document import Document -from frappe.utils import call_hook_method, cint, flt, get_url - -from erpnext.utilities import payment_app_import_guard - - -class GoCardlessSettings(Document): - supported_currencies = ["EUR", "DKK", "GBP", "SEK", "AUD", "NZD", "CAD", "USD"] - - def validate(self): - self.initialize_client() - - def initialize_client(self): - self.environment = self.get_environment() - try: - self.client = gocardless_pro.Client( - access_token=self.access_token, environment=self.environment - ) - return self.client - except Exception as e: - frappe.throw(e) - - def on_update(self): - with payment_app_import_guard(): - from payments.utils import create_payment_gateway - - create_payment_gateway( - "GoCardless-" + self.gateway_name, settings="GoCardLess Settings", controller=self.gateway_name - ) - call_hook_method("payment_gateway_enabled", gateway="GoCardless-" + self.gateway_name) - - def on_payment_request_submission(self, data): - if data.reference_doctype != "Fees": - customer_data = frappe.db.get_value( - data.reference_doctype, data.reference_name, ["company", "customer_name"], as_dict=1 - ) - - data = { - "amount": flt(data.grand_total, data.precision("grand_total")), - "title": customer_data.company.encode("utf-8"), - "description": data.subject.encode("utf-8"), - "reference_doctype": data.doctype, - "reference_docname": data.name, - "payer_email": data.email_to or frappe.session.user, - "payer_name": customer_data.customer_name, - "order_id": data.name, - "currency": data.currency, - } - - valid_mandate = self.check_mandate_validity(data) - if valid_mandate is not None: - data.update(valid_mandate) - - self.create_payment_request(data) - return False - else: - return True - - def check_mandate_validity(self, data): - - if frappe.db.exists("GoCardless Mandate", dict(customer=data.get("payer_name"), disabled=0)): - registered_mandate = frappe.db.get_value( - "GoCardless Mandate", dict(customer=data.get("payer_name"), disabled=0), "mandate" - ) - self.initialize_client() - mandate = self.client.mandates.get(registered_mandate) - - if ( - mandate.status == "pending_customer_approval" - or mandate.status == "pending_submission" - or mandate.status == "submitted" - or mandate.status == "active" - ): - return {"mandate": registered_mandate} - else: - return None - else: - return None - - def get_environment(self): - if self.use_sandbox: - return "sandbox" - else: - return "live" - - def validate_transaction_currency(self, currency): - if currency not in self.supported_currencies: - frappe.throw( - _( - "Please select another payment method. Go Cardless does not support transactions in currency '{0}'" - ).format(currency) - ) - - def get_payment_url(self, **kwargs): - return get_url("./integrations/gocardless_checkout?{0}".format(urlencode(kwargs))) - - def create_payment_request(self, data): - self.data = frappe._dict(data) - - try: - self.integration_request = create_request_log(self.data, "Host", "GoCardless") - return self.create_charge_on_gocardless() - - except Exception: - frappe.log_error("Gocardless payment reqeust failed") - return { - "redirect_to": frappe.redirect_to_message( - _("Server Error"), - _( - "There seems to be an issue with the server's GoCardless configuration. Don't worry, in case of failure, the amount will get refunded to your account." - ), - ), - "status": 401, - } - - def create_charge_on_gocardless(self): - redirect_to = self.data.get("redirect_to") or None - redirect_message = self.data.get("redirect_message") or None - - reference_doc = frappe.get_doc( - self.data.get("reference_doctype"), self.data.get("reference_docname") - ) - self.initialize_client() - - try: - payment = self.client.payments.create( - params={ - "amount": cint(reference_doc.grand_total * 100), - "currency": reference_doc.currency, - "links": {"mandate": self.data.get("mandate")}, - "metadata": { - "reference_doctype": reference_doc.doctype, - "reference_document": reference_doc.name, - }, - }, - headers={ - "Idempotency-Key": self.data.get("reference_docname"), - }, - ) - - if ( - payment.status == "pending_submission" - or payment.status == "pending_customer_approval" - or payment.status == "submitted" - ): - self.integration_request.db_set("status", "Authorized", update_modified=False) - self.flags.status_changed_to = "Completed" - self.integration_request.db_set("output", payment.status, update_modified=False) - - elif payment.status == "confirmed" or payment.status == "paid_out": - self.integration_request.db_set("status", "Completed", update_modified=False) - self.flags.status_changed_to = "Completed" - self.integration_request.db_set("output", payment.status, update_modified=False) - - elif ( - payment.status == "cancelled" - or payment.status == "customer_approval_denied" - or payment.status == "charged_back" - ): - self.integration_request.db_set("status", "Cancelled", update_modified=False) - frappe.log_error("Gocardless payment cancelled") - self.integration_request.db_set("error", payment.status, update_modified=False) - else: - self.integration_request.db_set("status", "Failed", update_modified=False) - frappe.log_error("Gocardless payment failed") - self.integration_request.db_set("error", payment.status, update_modified=False) - - except Exception as e: - frappe.log_error("GoCardless Payment Error") - - if self.flags.status_changed_to == "Completed": - status = "Completed" - if "reference_doctype" in self.data and "reference_docname" in self.data: - custom_redirect_to = None - try: - custom_redirect_to = frappe.get_doc( - self.data.get("reference_doctype"), self.data.get("reference_docname") - ).run_method("on_payment_authorized", self.flags.status_changed_to) - except Exception: - frappe.log_error("Gocardless redirect failed") - - if custom_redirect_to: - redirect_to = custom_redirect_to - - redirect_url = redirect_to - else: - status = "Error" - redirect_url = "payment-failed" - - if redirect_message: - redirect_url += "&" + urlencode({"redirect_message": redirect_message}) - - redirect_url = get_url(redirect_url) - - return {"redirect_to": redirect_url, "status": status} - - -def get_gateway_controller(doc): - payment_request = frappe.get_doc("Payment Request", doc) - gateway_controller = frappe.db.get_value( - "Payment Gateway", payment_request.payment_gateway, "gateway_controller" - ) - return gateway_controller - - -def gocardless_initialization(doc): - gateway_controller = get_gateway_controller(doc) - settings = frappe.get_doc("GoCardless Settings", gateway_controller) - client = settings.initialize_client() - return client diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py deleted file mode 100644 index 379afe51dd..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt - -import unittest - - -class TestGoCardlessSettings(unittest.TestCase): - pass diff --git a/pyproject.toml b/pyproject.toml index 7841c92054..604aa44585 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,11 +16,9 @@ dependencies = [ "holidays~=0.28", # integration dependencies - "gocardless-pro~=1.22.0", "googlemaps", "plaid-python~=7.2.1", "python-youtube~=0.8.0", - "tweepy~=4.14.0", # Not used directly - required by PyQRCode for PNG generation "pypng~=0.20220715.0", From eb419e8e591e9101954523a9fb2d122d0e778ae8 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 21 Sep 2023 07:32:39 +0530 Subject: [PATCH 021/280] refactor!: remove `Mpesa Settings` --- .../doctype/mpesa_settings/__init__.py | 0 .../mpesa_settings/account_balance.html | 28 -- .../doctype/mpesa_settings/mpesa_connector.py | 149 -------- .../mpesa_settings/mpesa_custom_fields.py | 56 --- .../doctype/mpesa_settings/mpesa_settings.js | 39 -- .../mpesa_settings/mpesa_settings.json | 152 -------- .../doctype/mpesa_settings/mpesa_settings.py | 354 ----------------- .../mpesa_settings/test_mpesa_settings.py | 361 ------------------ erpnext/erpnext_integrations/utils.py | 31 -- 9 files changed, 1170 deletions(-) delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py delete mode 100644 erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/__init__.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html b/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html deleted file mode 100644 index b74a7187f0..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/account_balance.html +++ /dev/null @@ -1,28 +0,0 @@ - -{% if not jQuery.isEmptyObject(data) %} -
{{ __("Balance Details") }}
- - - - - - - - - - - - {% for(const [key, value] of Object.entries(data)) { %} - - - - - - - - {% } %} - -
{{ __("Account Type") }}{{ __("Current Balance") }}{{ __("Available Balance") }}{{ __("Reserved Balance") }}{{ __("Uncleared Balance") }}
{%= key %} {%= value["current_balance"] %} {%= value["available_balance"] %} {%= value["reserved_balance"] %} {%= value["uncleared_balance"] %}
-{% else %} -

Account Balance Information Not Available.

-{% endif %} diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py deleted file mode 100644 index a577e7fa69..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_connector.py +++ /dev/null @@ -1,149 +0,0 @@ -import base64 -import datetime - -import requests -from requests.auth import HTTPBasicAuth - - -class MpesaConnector: - def __init__( - self, - env="sandbox", - app_key=None, - app_secret=None, - sandbox_url="https://sandbox.safaricom.co.ke", - live_url="https://api.safaricom.co.ke", - ): - """Setup configuration for Mpesa connector and generate new access token.""" - self.env = env - self.app_key = app_key - self.app_secret = app_secret - if env == "sandbox": - self.base_url = sandbox_url - else: - self.base_url = live_url - self.authenticate() - - def authenticate(self): - """ - This method is used to fetch the access token required by Mpesa. - - Returns: - access_token (str): This token is to be used with the Bearer header for further API calls to Mpesa. - """ - authenticate_uri = "/oauth/v1/generate?grant_type=client_credentials" - authenticate_url = "{0}{1}".format(self.base_url, authenticate_uri) - r = requests.get(authenticate_url, auth=HTTPBasicAuth(self.app_key, self.app_secret)) - self.authentication_token = r.json()["access_token"] - return r.json()["access_token"] - - def get_balance( - self, - initiator=None, - security_credential=None, - party_a=None, - identifier_type=None, - remarks=None, - queue_timeout_url=None, - result_url=None, - ): - """ - This method uses Mpesa's Account Balance API to to enquire the balance on a M-Pesa BuyGoods (Till Number). - - Args: - initiator (str): Username used to authenticate the transaction. - security_credential (str): Generate from developer portal. - command_id (str): AccountBalance. - party_a (int): Till number being queried. - identifier_type (int): Type of organization receiving the transaction. (MSISDN/Till Number/Organization short code) - remarks (str): Comments that are sent along with the transaction(maximum 100 characters). - queue_timeout_url (str): The url that handles information of timed out transactions. - result_url (str): The url that receives results from M-Pesa api call. - - Returns: - OriginatorConverstionID (str): The unique request ID for tracking a transaction. - ConversationID (str): The unique request ID returned by mpesa for each request made - ResponseDescription (str): Response Description message - """ - - payload = { - "Initiator": initiator, - "SecurityCredential": security_credential, - "CommandID": "AccountBalance", - "PartyA": party_a, - "IdentifierType": identifier_type, - "Remarks": remarks, - "QueueTimeOutURL": queue_timeout_url, - "ResultURL": result_url, - } - headers = { - "Authorization": "Bearer {0}".format(self.authentication_token), - "Content-Type": "application/json", - } - saf_url = "{0}{1}".format(self.base_url, "/mpesa/accountbalance/v1/query") - r = requests.post(saf_url, headers=headers, json=payload) - return r.json() - - def stk_push( - self, - business_shortcode=None, - passcode=None, - amount=None, - callback_url=None, - reference_code=None, - phone_number=None, - description=None, - ): - """ - This method uses Mpesa's Express API to initiate online payment on behalf of a customer. - - Args: - business_shortcode (int): The short code of the organization. - passcode (str): Get from developer portal - amount (int): The amount being transacted - callback_url (str): A CallBack URL is a valid secure URL that is used to receive notifications from M-Pesa API. - reference_code(str): Account Reference: This is an Alpha-Numeric parameter that is defined by your system as an Identifier of the transaction for CustomerPayBillOnline transaction type. - phone_number(int): The Mobile Number to receive the STK Pin Prompt. - description(str): This is any additional information/comment that can be sent along with the request from your system. MAX 13 characters - - Success Response: - CustomerMessage(str): Messages that customers can understand. - CheckoutRequestID(str): This is a global unique identifier of the processed checkout transaction request. - ResponseDescription(str): Describes Success or failure - MerchantRequestID(str): This is a global unique Identifier for any submitted payment request. - ResponseCode(int): 0 means success all others are error codes. e.g.404.001.03 - - Error Reponse: - requestId(str): This is a unique requestID for the payment request - errorCode(str): This is a predefined code that indicates the reason for request failure. - errorMessage(str): This is a predefined code that indicates the reason for request failure. - """ - - time = ( - str(datetime.datetime.now()).split(".")[0].replace("-", "").replace(" ", "").replace(":", "") - ) - password = "{0}{1}{2}".format(str(business_shortcode), str(passcode), time) - encoded = base64.b64encode(bytes(password, encoding="utf8")) - payload = { - "BusinessShortCode": business_shortcode, - "Password": encoded.decode("utf-8"), - "Timestamp": time, - "Amount": amount, - "PartyA": int(phone_number), - "PartyB": reference_code, - "PhoneNumber": int(phone_number), - "CallBackURL": callback_url, - "AccountReference": reference_code, - "TransactionDesc": description, - "TransactionType": "CustomerPayBillOnline" - if self.env == "sandbox" - else "CustomerBuyGoodsOnline", - } - headers = { - "Authorization": "Bearer {0}".format(self.authentication_token), - "Content-Type": "application/json", - } - - saf_url = "{0}{1}".format(self.base_url, "/mpesa/stkpush/v1/processrequest") - r = requests.post(saf_url, headers=headers, json=payload) - return r.json() diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py deleted file mode 100644 index c92edc5efa..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_custom_fields.py +++ /dev/null @@ -1,56 +0,0 @@ -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields - - -def create_custom_pos_fields(): - """Create custom fields corresponding to POS Settings and POS Invoice.""" - pos_field = { - "POS Invoice": [ - { - "fieldname": "request_for_payment", - "label": "Request for Payment", - "fieldtype": "Button", - "hidden": 1, - "insert_after": "contact_email", - }, - { - "fieldname": "mpesa_receipt_number", - "label": "Mpesa Receipt Number", - "fieldtype": "Data", - "read_only": 1, - "insert_after": "company", - }, - ] - } - if not frappe.get_meta("POS Invoice").has_field("request_for_payment"): - create_custom_fields(pos_field) - - record_dict = [ - { - "doctype": "POS Field", - "fieldname": "contact_mobile", - "label": "Mobile No", - "fieldtype": "Data", - "options": "Phone", - "parenttype": "POS Settings", - "parent": "POS Settings", - "parentfield": "invoice_fields", - }, - { - "doctype": "POS Field", - "fieldname": "request_for_payment", - "label": "Request for Payment", - "fieldtype": "Button", - "parenttype": "POS Settings", - "parent": "POS Settings", - "parentfield": "invoice_fields", - }, - ] - create_pos_settings(record_dict) - - -def create_pos_settings(record_dict): - for record in record_dict: - if frappe.db.exists("POS Field", {"fieldname": record.get("fieldname")}): - continue - frappe.get_doc(record).insert() diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js deleted file mode 100644 index 447d720ca2..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.js +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Mpesa Settings', { - onload_post_render: function(frm) { - frm.events.setup_account_balance_html(frm); - }, - - refresh: function(frm) { - erpnext.utils.check_payments_app(); - - frappe.realtime.on("refresh_mpesa_dashboard", function(){ - frm.reload_doc(); - frm.events.setup_account_balance_html(frm); - }); - }, - - get_account_balance: function(frm) { - if (!frm.doc.initiator_name && !frm.doc.security_credential) { - frappe.throw(__("Please set the initiator name and the security credential")); - } - frappe.call({ - method: "get_account_balance_info", - doc: frm.doc - }); - }, - - setup_account_balance_html: function(frm) { - if (!frm.doc.account_balance) return; - $("div").remove(".form-dashboard-section.custom"); - frm.dashboard.add_section( - frappe.render_template('account_balance', { - data: JSON.parse(frm.doc.account_balance) - }) - ); - frm.dashboard.show(); - } - -}); diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json deleted file mode 100644 index 8f3b4271c1..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "actions": [], - "autoname": "field:payment_gateway_name", - "creation": "2020-09-10 13:21:27.398088", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "payment_gateway_name", - "consumer_key", - "consumer_secret", - "initiator_name", - "till_number", - "transaction_limit", - "sandbox", - "column_break_4", - "business_shortcode", - "online_passkey", - "security_credential", - "get_account_balance", - "account_balance" - ], - "fields": [ - { - "fieldname": "payment_gateway_name", - "fieldtype": "Data", - "label": "Payment Gateway Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "consumer_key", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Consumer Key", - "reqd": 1 - }, - { - "fieldname": "consumer_secret", - "fieldtype": "Password", - "in_list_view": 1, - "label": "Consumer Secret", - "reqd": 1 - }, - { - "fieldname": "till_number", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Till Number", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "sandbox", - "fieldtype": "Check", - "label": "Sandbox" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "online_passkey", - "fieldtype": "Password", - "label": " Online PassKey", - "reqd": 1 - }, - { - "fieldname": "initiator_name", - "fieldtype": "Data", - "label": "Initiator Name" - }, - { - "fieldname": "security_credential", - "fieldtype": "Small Text", - "label": "Security Credential" - }, - { - "fieldname": "account_balance", - "fieldtype": "Long Text", - "hidden": 1, - "label": "Account Balance", - "read_only": 1 - }, - { - "fieldname": "get_account_balance", - "fieldtype": "Button", - "label": "Get Account Balance" - }, - { - "depends_on": "eval:(doc.sandbox==0)", - "fieldname": "business_shortcode", - "fieldtype": "Data", - "label": "Business Shortcode", - "mandatory_depends_on": "eval:(doc.sandbox==0)" - }, - { - "default": "150000", - "fieldname": "transaction_limit", - "fieldtype": "Float", - "label": "Transaction Limit", - "non_negative": 1 - } - ], - "links": [], - "modified": "2021-03-02 17:35:14.084342", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Mpesa Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py deleted file mode 100644 index a298e11eaf..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py +++ /dev/null @@ -1,354 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - - -from json import dumps, loads - -import frappe -from frappe import _ -from frappe.integrations.utils import create_request_log -from frappe.model.document import Document -from frappe.utils import call_hook_method, fmt_money, get_request_site_address - -from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector -from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import ( - create_custom_pos_fields, -) -from erpnext.erpnext_integrations.utils import create_mode_of_payment -from erpnext.utilities import payment_app_import_guard - - -class MpesaSettings(Document): - supported_currencies = ["KES"] - - def validate_transaction_currency(self, currency): - if currency not in self.supported_currencies: - frappe.throw( - _( - "Please select another payment method. Mpesa does not support transactions in currency '{0}'" - ).format(currency) - ) - - def on_update(self): - with payment_app_import_guard(): - from payments.utils import create_payment_gateway - - create_custom_pos_fields() - create_payment_gateway( - "Mpesa-" + self.payment_gateway_name, - settings="Mpesa Settings", - controller=self.payment_gateway_name, - ) - call_hook_method( - "payment_gateway_enabled", gateway="Mpesa-" + self.payment_gateway_name, payment_channel="Phone" - ) - - # required to fetch the bank account details from the payment gateway account - frappe.db.commit() - create_mode_of_payment("Mpesa-" + self.payment_gateway_name, payment_type="Phone") - - def request_for_payment(self, **kwargs): - args = frappe._dict(kwargs) - request_amounts = self.split_request_amount_according_to_transaction_limit(args) - - for i, amount in enumerate(request_amounts): - args.request_amount = amount - if frappe.flags.in_test: - from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import ( - get_payment_request_response_payload, - ) - - response = frappe._dict(get_payment_request_response_payload(amount)) - else: - response = frappe._dict(generate_stk_push(**args)) - - self.handle_api_response("CheckoutRequestID", args, response) - - def split_request_amount_according_to_transaction_limit(self, args): - request_amount = args.request_amount - if request_amount > self.transaction_limit: - # make multiple requests - request_amounts = [] - requests_to_be_made = frappe.utils.ceil( - request_amount / self.transaction_limit - ) # 480/150 = ceil(3.2) = 4 - for i in range(requests_to_be_made): - amount = self.transaction_limit - if i == requests_to_be_made - 1: - amount = request_amount - ( - self.transaction_limit * i - ) # for 4th request, 480 - (150 * 3) = 30 - request_amounts.append(amount) - else: - request_amounts = [request_amount] - - return request_amounts - - @frappe.whitelist() - def get_account_balance_info(self): - payload = dict( - reference_doctype="Mpesa Settings", reference_docname=self.name, doc_details=vars(self) - ) - - if frappe.flags.in_test: - from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import ( - get_test_account_balance_response, - ) - - response = frappe._dict(get_test_account_balance_response()) - else: - response = frappe._dict(get_account_balance(payload)) - - self.handle_api_response("ConversationID", payload, response) - - def handle_api_response(self, global_id, request_dict, response): - """Response received from API calls returns a global identifier for each transaction, this code is returned during the callback.""" - # check error response - if getattr(response, "requestId"): - req_name = getattr(response, "requestId") - error = response - else: - # global checkout id used as request name - req_name = getattr(response, global_id) - error = None - - if not frappe.db.exists("Integration Request", req_name): - create_request_log(request_dict, "Host", "Mpesa", req_name, error) - - if error: - frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error")) - - -def generate_stk_push(**kwargs): - """Generate stk push by making a API call to the stk push API.""" - args = frappe._dict(kwargs) - try: - callback_url = ( - get_request_site_address(True) - + "/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.verify_transaction" - ) - - mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:]) - env = "production" if not mpesa_settings.sandbox else "sandbox" - # for sandbox, business shortcode is same as till number - business_shortcode = ( - mpesa_settings.business_shortcode if env == "production" else mpesa_settings.till_number - ) - - connector = MpesaConnector( - env=env, - app_key=mpesa_settings.consumer_key, - app_secret=mpesa_settings.get_password("consumer_secret"), - ) - - mobile_number = sanitize_mobile_number(args.sender) - - response = connector.stk_push( - business_shortcode=business_shortcode, - amount=args.request_amount, - passcode=mpesa_settings.get_password("online_passkey"), - callback_url=callback_url, - reference_code=mpesa_settings.till_number, - phone_number=mobile_number, - description="POS Payment", - ) - - return response - - except Exception: - frappe.log_error("Mpesa Express Transaction Error") - frappe.throw( - _("Issue detected with Mpesa configuration, check the error logs for more details"), - title=_("Mpesa Express Error"), - ) - - -def sanitize_mobile_number(number): - """Add country code and strip leading zeroes from the phone number.""" - return "254" + str(number).lstrip("0") - - -@frappe.whitelist(allow_guest=True) -def verify_transaction(**kwargs): - """Verify the transaction result received via callback from stk.""" - transaction_response = frappe._dict(kwargs["Body"]["stkCallback"]) - - checkout_id = getattr(transaction_response, "CheckoutRequestID", "") - if not isinstance(checkout_id, str): - frappe.throw(_("Invalid Checkout Request ID")) - - integration_request = frappe.get_doc("Integration Request", checkout_id) - transaction_data = frappe._dict(loads(integration_request.data)) - total_paid = 0 # for multiple integration request made against a pos invoice - success = False # for reporting successfull callback to point of sale ui - - if transaction_response["ResultCode"] == 0: - if integration_request.reference_doctype and integration_request.reference_docname: - try: - item_response = transaction_response["CallbackMetadata"]["Item"] - amount = fetch_param_value(item_response, "Amount", "Name") - mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name") - pr = frappe.get_doc( - integration_request.reference_doctype, integration_request.reference_docname - ) - - mpesa_receipts, completed_payments = get_completed_integration_requests_info( - integration_request.reference_doctype, integration_request.reference_docname, checkout_id - ) - - total_paid = amount + sum(completed_payments) - mpesa_receipts = ", ".join(mpesa_receipts + [mpesa_receipt]) - - if total_paid >= pr.grand_total: - pr.run_method("on_payment_authorized", "Completed") - success = True - - frappe.db.set_value("POS Invoice", pr.reference_name, "mpesa_receipt_number", mpesa_receipts) - integration_request.handle_success(transaction_response) - except Exception: - integration_request.handle_failure(transaction_response) - frappe.log_error("Mpesa: Failed to verify transaction") - - else: - integration_request.handle_failure(transaction_response) - - frappe.publish_realtime( - event="process_phone_payment", - doctype="POS Invoice", - docname=transaction_data.payment_reference, - user=integration_request.owner, - message={ - "amount": total_paid, - "success": success, - "failure_message": transaction_response["ResultDesc"] - if transaction_response["ResultCode"] != 0 - else "", - }, - ) - - -def get_completed_integration_requests_info(reference_doctype, reference_docname, checkout_id): - output_of_other_completed_requests = frappe.get_all( - "Integration Request", - filters={ - "name": ["!=", checkout_id], - "reference_doctype": reference_doctype, - "reference_docname": reference_docname, - "status": "Completed", - }, - pluck="output", - ) - - mpesa_receipts, completed_payments = [], [] - - for out in output_of_other_completed_requests: - out = frappe._dict(loads(out)) - item_response = out["CallbackMetadata"]["Item"] - completed_amount = fetch_param_value(item_response, "Amount", "Name") - completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name") - completed_payments.append(completed_amount) - mpesa_receipts.append(completed_mpesa_receipt) - - return mpesa_receipts, completed_payments - - -def get_account_balance(request_payload): - """Call account balance API to send the request to the Mpesa Servers.""" - try: - mpesa_settings = frappe.get_doc("Mpesa Settings", request_payload.get("reference_docname")) - env = "production" if not mpesa_settings.sandbox else "sandbox" - connector = MpesaConnector( - env=env, - app_key=mpesa_settings.consumer_key, - app_secret=mpesa_settings.get_password("consumer_secret"), - ) - - callback_url = ( - get_request_site_address(True) - + "/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.process_balance_info" - ) - - response = connector.get_balance( - mpesa_settings.initiator_name, - mpesa_settings.security_credential, - mpesa_settings.till_number, - 4, - mpesa_settings.name, - callback_url, - callback_url, - ) - return response - except Exception: - frappe.log_error("Mpesa: Failed to get account balance") - frappe.throw(_("Please check your configuration and try again"), title=_("Error")) - - -@frappe.whitelist(allow_guest=True) -def process_balance_info(**kwargs): - """Process and store account balance information received via callback from the account balance API call.""" - account_balance_response = frappe._dict(kwargs["Result"]) - - conversation_id = getattr(account_balance_response, "ConversationID", "") - if not isinstance(conversation_id, str): - frappe.throw(_("Invalid Conversation ID")) - - request = frappe.get_doc("Integration Request", conversation_id) - - if request.status == "Completed": - return - - transaction_data = frappe._dict(loads(request.data)) - - if account_balance_response["ResultCode"] == 0: - try: - result_params = account_balance_response["ResultParameters"]["ResultParameter"] - - balance_info = fetch_param_value(result_params, "AccountBalance", "Key") - balance_info = format_string_to_json(balance_info) - - ref_doc = frappe.get_doc(transaction_data.reference_doctype, transaction_data.reference_docname) - ref_doc.db_set("account_balance", balance_info) - - request.handle_success(account_balance_response) - frappe.publish_realtime( - "refresh_mpesa_dashboard", - doctype="Mpesa Settings", - docname=transaction_data.reference_docname, - user=transaction_data.owner, - ) - except Exception: - request.handle_failure(account_balance_response) - frappe.log_error( - title="Mpesa Account Balance Processing Error", message=account_balance_response - ) - else: - request.handle_failure(account_balance_response) - - -def format_string_to_json(balance_info): - """ - Format string to json. - - e.g: '''Working Account|KES|481000.00|481000.00|0.00|0.00''' - => {'Working Account': {'current_balance': '481000.00', - 'available_balance': '481000.00', - 'reserved_balance': '0.00', - 'uncleared_balance': '0.00'}} - """ - balance_dict = frappe._dict() - for account_info in balance_info.split("&"): - account_info = account_info.split("|") - balance_dict[account_info[0]] = dict( - current_balance=fmt_money(account_info[2], currency="KES"), - available_balance=fmt_money(account_info[3], currency="KES"), - reserved_balance=fmt_money(account_info[4], currency="KES"), - uncleared_balance=fmt_money(account_info[5], currency="KES"), - ) - return dumps(balance_dict) - - -def fetch_param_value(response, key, key_field): - """Fetch the specified key from list of dictionary. Key is identified via the key field.""" - for param in response: - if param[key_field] == key: - return param["Value"] diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py deleted file mode 100644 index b52662421d..0000000000 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ /dev/null @@ -1,361 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest -from json import dumps - -import frappe - -from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice -from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import ( - process_balance_info, - verify_transaction, -) -from erpnext.erpnext_integrations.utils import create_mode_of_payment - - -class TestMpesaSettings(unittest.TestCase): - def setUp(self): - # create payment gateway in setup - create_mpesa_settings(payment_gateway_name="_Test") - create_mpesa_settings(payment_gateway_name="_Account Balance") - create_mpesa_settings(payment_gateway_name="Payment") - - def tearDown(self): - frappe.db.sql("delete from `tabMpesa Settings`") - frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") - - def test_creation_of_payment_gateway(self): - mode_of_payment = create_mode_of_payment("Mpesa-_Test", payment_type="Phone") - self.assertTrue(frappe.db.exists("Payment Gateway Account", {"payment_gateway": "Mpesa-_Test"})) - self.assertTrue(mode_of_payment.name) - self.assertEqual(mode_of_payment.type, "Phone") - - def test_processing_of_account_balance(self): - mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance") - mpesa_doc.get_account_balance_info() - - callback_response = get_account_balance_callback_payload() - process_balance_info(**callback_response) - integration_request = frappe.get_doc("Integration Request", "AG_20200927_00007cdb1f9fb6494315") - - # test integration request creation and successful update of the status on receiving callback response - self.assertTrue(integration_request) - self.assertEqual(integration_request.status, "Completed") - - # test formatting of account balance received as string to json with appropriate currency symbol - mpesa_doc.reload() - self.assertEqual( - mpesa_doc.account_balance, - dumps( - { - "Working Account": { - "current_balance": "Sh 481,000.00", - "available_balance": "Sh 481,000.00", - "reserved_balance": "Sh 0.00", - "uncleared_balance": "Sh 0.00", - } - } - ), - ) - - integration_request.delete() - - def test_processing_of_callback_payload(self): - mpesa_account = frappe.db.get_value( - "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" - ) - frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") - - pos_invoice = create_pos_invoice(do_not_submit=1) - pos_invoice.append( - "payments", {"mode_of_payment": "Mpesa-Payment", "account": mpesa_account, "amount": 500} - ) - pos_invoice.contact_mobile = "093456543894" - pos_invoice.currency = "KES" - pos_invoice.save() - - pr = pos_invoice.create_payment_request() - # test payment request creation - self.assertEqual(pr.payment_gateway, "Mpesa-Payment") - - # submitting payment request creates integration requests with random id - integration_req_ids = frappe.get_all( - "Integration Request", - filters={ - "reference_doctype": pr.doctype, - "reference_docname": pr.name, - }, - pluck="name", - ) - - callback_response = get_payment_callback_payload( - Amount=500, CheckoutRequestID=integration_req_ids[0] - ) - verify_transaction(**callback_response) - # test creation of integration request - integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) - - # test integration request creation and successful update of the status on receiving callback response - self.assertTrue(integration_request) - self.assertEqual(integration_request.status, "Completed") - - pos_invoice.reload() - integration_request.reload() - self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") - self.assertEqual(integration_request.status, "Completed") - - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") - integration_request.delete() - pr.reload() - pr.cancel() - pr.delete() - pos_invoice.delete() - - def test_processing_of_multiple_callback_payload(self): - mpesa_account = frappe.db.get_value( - "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" - ) - frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") - frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") - - pos_invoice = create_pos_invoice(do_not_submit=1) - pos_invoice.append( - "payments", {"mode_of_payment": "Mpesa-Payment", "account": mpesa_account, "amount": 1000} - ) - pos_invoice.contact_mobile = "093456543894" - pos_invoice.currency = "KES" - pos_invoice.save() - - pr = pos_invoice.create_payment_request() - # test payment request creation - self.assertEqual(pr.payment_gateway, "Mpesa-Payment") - - # submitting payment request creates integration requests with random id - integration_req_ids = frappe.get_all( - "Integration Request", - filters={ - "reference_doctype": pr.doctype, - "reference_docname": pr.name, - }, - pluck="name", - ) - - # create random receipt nos and send it as response to callback handler - mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids] - - integration_requests = [] - for i in range(len(integration_req_ids)): - callback_response = get_payment_callback_payload( - Amount=500, - CheckoutRequestID=integration_req_ids[i], - MpesaReceiptNumber=mpesa_receipt_numbers[i], - ) - # handle response manually - verify_transaction(**callback_response) - # test completion of integration request - integration_request = frappe.get_doc("Integration Request", integration_req_ids[i]) - self.assertEqual(integration_request.status, "Completed") - integration_requests.append(integration_request) - - # check receipt number once all the integration requests are completed - pos_invoice.reload() - self.assertEqual(pos_invoice.mpesa_receipt_number, ", ".join(mpesa_receipt_numbers)) - - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") - [d.delete() for d in integration_requests] - pr.reload() - pr.cancel() - pr.delete() - pos_invoice.delete() - - def test_processing_of_only_one_succes_callback_payload(self): - mpesa_account = frappe.db.get_value( - "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" - ) - frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") - frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") - - pos_invoice = create_pos_invoice(do_not_submit=1) - pos_invoice.append( - "payments", {"mode_of_payment": "Mpesa-Payment", "account": mpesa_account, "amount": 1000} - ) - pos_invoice.contact_mobile = "093456543894" - pos_invoice.currency = "KES" - pos_invoice.save() - - pr = pos_invoice.create_payment_request() - # test payment request creation - self.assertEqual(pr.payment_gateway, "Mpesa-Payment") - - # submitting payment request creates integration requests with random id - integration_req_ids = frappe.get_all( - "Integration Request", - filters={ - "reference_doctype": pr.doctype, - "reference_docname": pr.name, - }, - pluck="name", - ) - - # create random receipt nos and send it as response to callback handler - mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids] - - callback_response = get_payment_callback_payload( - Amount=500, - CheckoutRequestID=integration_req_ids[0], - MpesaReceiptNumber=mpesa_receipt_numbers[0], - ) - # handle response manually - verify_transaction(**callback_response) - # test completion of integration request - integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) - self.assertEqual(integration_request.status, "Completed") - - # now one request is completed - # second integration request fails - # now retrying payment request should make only one integration request again - pr = pos_invoice.create_payment_request() - new_integration_req_ids = frappe.get_all( - "Integration Request", - filters={ - "reference_doctype": pr.doctype, - "reference_docname": pr.name, - "name": ["not in", integration_req_ids], - }, - pluck="name", - ) - - self.assertEqual(len(new_integration_req_ids), 1) - - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") - frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") - pr.reload() - pr.cancel() - pr.delete() - pos_invoice.delete() - - -def create_mpesa_settings(payment_gateway_name="Express"): - if frappe.db.exists("Mpesa Settings", payment_gateway_name): - return frappe.get_doc("Mpesa Settings", payment_gateway_name) - - doc = frappe.get_doc( - dict( # nosec - doctype="Mpesa Settings", - sandbox=1, - payment_gateway_name=payment_gateway_name, - consumer_key="5sMu9LVI1oS3oBGPJfh3JyvLHwZOdTKn", - consumer_secret="VI1oS3oBGPJfh3JyvLHw", - online_passkey="LVI1oS3oBGPJfh3JyvLHwZOd", - till_number="174379", - ) - ) - - doc.insert(ignore_permissions=True) - return doc - - -def get_test_account_balance_response(): - """Response received after calling the account balance API.""" - return { - "ResultType": 0, - "ResultCode": 0, - "ResultDesc": "The service request has been accepted successfully.", - "OriginatorConversationID": "10816-694520-2", - "ConversationID": "AG_20200927_00007cdb1f9fb6494315", - "TransactionID": "LGR0000000", - "ResultParameters": { - "ResultParameter": [ - {"Key": "ReceiptNo", "Value": "LGR919G2AV"}, - {"Key": "Conversation ID", "Value": "AG_20170727_00004492b1b6d0078fbe"}, - {"Key": "FinalisedTime", "Value": 20170727101415}, - {"Key": "Amount", "Value": 10}, - {"Key": "TransactionStatus", "Value": "Completed"}, - {"Key": "ReasonType", "Value": "Salary Payment via API"}, - {"Key": "TransactionReason"}, - {"Key": "DebitPartyCharges", "Value": "Fee For B2C Payment|KES|33.00"}, - {"Key": "DebitAccountType", "Value": "Utility Account"}, - {"Key": "InitiatedTime", "Value": 20170727101415}, - {"Key": "Originator Conversation ID", "Value": "19455-773836-1"}, - {"Key": "CreditPartyName", "Value": "254708374149 - John Doe"}, - {"Key": "DebitPartyName", "Value": "600134 - Safaricom157"}, - ] - }, - "ReferenceData": {"ReferenceItem": {"Key": "Occasion", "Value": "aaaa"}}, - } - - -def get_payment_request_response_payload(Amount=500): - """Response received after successfully calling the stk push process request API.""" - - CheckoutRequestID = frappe.utils.random_string(10) - - return { - "MerchantRequestID": "8071-27184008-1", - "CheckoutRequestID": CheckoutRequestID, - "ResultCode": 0, - "ResultDesc": "The service request is processed successfully.", - "CallbackMetadata": { - "Item": [ - {"Name": "Amount", "Value": Amount}, - {"Name": "MpesaReceiptNumber", "Value": "LGR7OWQX0R"}, - {"Name": "TransactionDate", "Value": 20201006113336}, - {"Name": "PhoneNumber", "Value": 254723575670}, - ] - }, - } - - -def get_payment_callback_payload( - Amount=500, CheckoutRequestID="ws_CO_061020201133231972", MpesaReceiptNumber="LGR7OWQX0R" -): - """Response received from the server as callback after calling the stkpush process request API.""" - return { - "Body": { - "stkCallback": { - "MerchantRequestID": "19465-780693-1", - "CheckoutRequestID": CheckoutRequestID, - "ResultCode": 0, - "ResultDesc": "The service request is processed successfully.", - "CallbackMetadata": { - "Item": [ - {"Name": "Amount", "Value": Amount}, - {"Name": "MpesaReceiptNumber", "Value": MpesaReceiptNumber}, - {"Name": "Balance"}, - {"Name": "TransactionDate", "Value": 20170727154800}, - {"Name": "PhoneNumber", "Value": 254721566839}, - ] - }, - } - } - } - - -def get_account_balance_callback_payload(): - """Response received from the server as callback after calling the account balance API.""" - return { - "Result": { - "ResultType": 0, - "ResultCode": 0, - "ResultDesc": "The service request is processed successfully.", - "OriginatorConversationID": "16470-170099139-1", - "ConversationID": "AG_20200927_00007cdb1f9fb6494315", - "TransactionID": "OIR0000000", - "ResultParameters": { - "ResultParameter": [ - {"Key": "AccountBalance", "Value": "Working Account|KES|481000.00|481000.00|0.00|0.00"}, - {"Key": "BOCompletedTime", "Value": 20200927234123}, - ] - }, - "ReferenceData": { - "ReferenceItem": { - "Key": "QueueTimeoutURL", - "Value": "https://internalsandbox.safaricom.co.ke/mpesa/abresults/v1/submit", - } - }, - } - } diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 981486eb30..8984f1bee7 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -6,8 +6,6 @@ from urllib.parse import urlparse import frappe from frappe import _ -from erpnext import get_default_company - def validate_webhooks_request(doctype, hmac_key, secret_key="secret"): def innerfn(fn): @@ -47,35 +45,6 @@ def get_webhook_address(connector_name, method, exclude_uri=False, force_https=F return server_url -def create_mode_of_payment(gateway, payment_type="General"): - payment_gateway_account = frappe.db.get_value( - "Payment Gateway Account", {"payment_gateway": gateway}, ["payment_account"] - ) - - mode_of_payment = frappe.db.exists("Mode of Payment", gateway) - if not mode_of_payment and payment_gateway_account: - mode_of_payment = frappe.get_doc( - { - "doctype": "Mode of Payment", - "mode_of_payment": gateway, - "enabled": 1, - "type": payment_type, - "accounts": [ - { - "doctype": "Mode of Payment Account", - "company": get_default_company(), - "default_account": payment_gateway_account, - } - ], - } - ) - mode_of_payment.insert(ignore_permissions=True) - - return mode_of_payment - elif mode_of_payment: - return frappe.get_doc("Mode of Payment", mode_of_payment) - - def get_tracking_url(carrier, tracking_number): # Return the formatted Tracking URL. tracking_url = "" From 543a76863f2fc32fd10b64ce02637d11dc357a0d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 22 Sep 2023 18:49:13 +0530 Subject: [PATCH 022/280] refactor!: remove `GoCardless Mandate` --- .../doctype/gocardless_mandate/__init__.py | 0 .../gocardless_mandate/gocardless_mandate.js | 5 - .../gocardless_mandate.json | 184 ------------------ .../gocardless_mandate/gocardless_mandate.py | 9 - .../test_gocardless_mandate.py | 8 - 5 files changed, 206 deletions(-) delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_mandate/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/__init__.py b/erpnext/erpnext_integrations/doctype/gocardless_mandate/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js deleted file mode 100644 index 37f9f7b9df..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('GoCardless Mandate', { -}); diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json deleted file mode 100644 index edf652c8f3..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:mandate", - "beta": 0, - "creation": "2018-02-08 11:33:15.721919", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mandate", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mandate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gocardless_customer", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "GoCardless Customer", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-02-11 12:28:03.183095", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "GoCardless Mandate", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py deleted file mode 100644 index bceb3caebd..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class GoCardlessMandate(Document): - pass diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py deleted file mode 100644 index 0c1952a16a..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt - -import unittest - - -class TestGoCardlessMandate(unittest.TestCase): - pass From 9554f6ea3c35488cd33e27aa5efcaf6ea4b7b9b8 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 22 Sep 2023 18:52:26 +0530 Subject: [PATCH 023/280] refactor!: remove `GoCardless Templates` --- .../integrations/gocardless_checkout.js | 24 ---- .../integrations/gocardless_confirmation.js | 24 ---- .../templates/pages/integrations/__init__.py | 0 .../integrations/gocardless_checkout.html | 16 --- .../pages/integrations/gocardless_checkout.py | 100 ----------------- .../integrations/gocardless_confirmation.html | 16 --- .../integrations/gocardless_confirmation.py | 106 ------------------ 7 files changed, 286 deletions(-) delete mode 100644 erpnext/templates/includes/integrations/gocardless_checkout.js delete mode 100644 erpnext/templates/includes/integrations/gocardless_confirmation.js delete mode 100644 erpnext/templates/pages/integrations/__init__.py delete mode 100644 erpnext/templates/pages/integrations/gocardless_checkout.html delete mode 100644 erpnext/templates/pages/integrations/gocardless_checkout.py delete mode 100644 erpnext/templates/pages/integrations/gocardless_confirmation.html delete mode 100644 erpnext/templates/pages/integrations/gocardless_confirmation.py diff --git a/erpnext/templates/includes/integrations/gocardless_checkout.js b/erpnext/templates/includes/integrations/gocardless_checkout.js deleted file mode 100644 index b18d55090c..0000000000 --- a/erpnext/templates/includes/integrations/gocardless_checkout.js +++ /dev/null @@ -1,24 +0,0 @@ -$(document).ready(function() { - var data = {{ frappe.form_dict | json }}; - var doctype = "{{ reference_doctype }}" - var docname = "{{ reference_docname }}" - - frappe.call({ - method: "erpnext.templates.pages.integrations.gocardless_checkout.check_mandate", - freeze: true, - headers: { - "X-Requested-With": "XMLHttpRequest" - }, - args: { - "data": JSON.stringify(data), - "reference_doctype": doctype, - "reference_docname": docname - }, - callback: function(r) { - if (r.message) { - window.location.href = r.message.redirect_to - } - } - }) - -}) diff --git a/erpnext/templates/includes/integrations/gocardless_confirmation.js b/erpnext/templates/includes/integrations/gocardless_confirmation.js deleted file mode 100644 index fee1d2b632..0000000000 --- a/erpnext/templates/includes/integrations/gocardless_confirmation.js +++ /dev/null @@ -1,24 +0,0 @@ -$(document).ready(function() { - var redirect_flow_id = "{{ redirect_flow_id }}"; - var doctype = "{{ reference_doctype }}"; - var docname = "{{ reference_docname }}"; - - frappe.call({ - method: "erpnext.templates.pages.integrations.gocardless_confirmation.confirm_payment", - freeze: true, - headers: { - "X-Requested-With": "XMLHttpRequest" - }, - args: { - "redirect_flow_id": redirect_flow_id, - "reference_doctype": doctype, - "reference_docname": docname - }, - callback: function(r) { - if (r.message) { - window.location.href = r.message.redirect_to; - } - } - }); - -}); diff --git a/erpnext/templates/pages/integrations/__init__.py b/erpnext/templates/pages/integrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.html b/erpnext/templates/pages/integrations/gocardless_checkout.html deleted file mode 100644 index 6072db49ea..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_checkout.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} Payment {% endblock %} - -{%- block header -%}{% endblock %} - -{% block script %} - -{% endblock %} - -{%- block page_content -%} -

- {{ _("Loading Payment System") }} -

- -{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py deleted file mode 100644 index 655be52c55..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_checkout.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import json - -import frappe -from frappe import _ -from frappe.utils import flt, get_url - -from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import ( - get_gateway_controller, - gocardless_initialization, -) - -no_cache = 1 - -expected_keys = ( - "amount", - "title", - "description", - "reference_doctype", - "reference_docname", - "payer_name", - "payer_email", - "order_id", - "currency", -) - - -def get_context(context): - context.no_cache = 1 - - # all these keys exist in form_dict - if not (set(expected_keys) - set(frappe.form_dict.keys())): - for key in expected_keys: - context[key] = frappe.form_dict[key] - - context["amount"] = flt(context["amount"]) - - gateway_controller = get_gateway_controller(context.reference_docname) - context["header_img"] = frappe.db.get_value( - "GoCardless Settings", gateway_controller, "header_img" - ) - - else: - frappe.redirect_to_message( - _("Some information is missing"), - _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."), - ) - frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - - -@frappe.whitelist(allow_guest=True) -def check_mandate(data, reference_doctype, reference_docname): - data = json.loads(data) - - client = gocardless_initialization(reference_docname) - - payer = frappe.get_doc("Customer", data["payer_name"]) - - if payer.customer_type == "Individual" and payer.customer_primary_contact is not None: - primary_contact = frappe.get_doc("Contact", payer.customer_primary_contact) - prefilled_customer = { - "company_name": payer.name, - "given_name": primary_contact.first_name, - } - if primary_contact.last_name is not None: - prefilled_customer.update({"family_name": primary_contact.last_name}) - - if primary_contact.email_id is not None: - prefilled_customer.update({"email": primary_contact.email_id}) - else: - prefilled_customer.update({"email": frappe.session.user}) - - else: - prefilled_customer = {"company_name": payer.name, "email": frappe.session.user} - - success_url = get_url( - "./integrations/gocardless_confirmation?reference_doctype=" - + reference_doctype - + "&reference_docname=" - + reference_docname - ) - - try: - redirect_flow = client.redirect_flows.create( - params={ - "description": _("Pay {0} {1}").format(data["amount"], data["currency"]), - "session_token": frappe.session.user, - "success_redirect_url": success_url, - "prefilled_customer": prefilled_customer, - } - ) - - return {"redirect_to": redirect_flow.redirect_url} - - except Exception as e: - frappe.log_error("GoCardless Payment Error") - return {"redirect_to": "/integrations/payment-failed"} diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.html b/erpnext/templates/pages/integrations/gocardless_confirmation.html deleted file mode 100644 index d961c6344a..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} Payment {% endblock %} - -{%- block header -%}{% endblock %} - -{% block script %} - -{% endblock %} - -{%- block page_content -%} -

- {{ _("Payment Confirmation") }} -

- -{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py deleted file mode 100644 index 559aa4806d..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe import _ - -from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import ( - get_gateway_controller, - gocardless_initialization, -) - -no_cache = 1 - -expected_keys = ("redirect_flow_id", "reference_doctype", "reference_docname") - - -def get_context(context): - context.no_cache = 1 - - # all these keys exist in form_dict - if not (set(expected_keys) - set(frappe.form_dict.keys())): - for key in expected_keys: - context[key] = frappe.form_dict[key] - - else: - frappe.redirect_to_message( - _("Some information is missing"), - _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."), - ) - frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - - -@frappe.whitelist(allow_guest=True) -def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): - - client = gocardless_initialization(reference_docname) - - try: - redirect_flow = client.redirect_flows.complete( - redirect_flow_id, params={"session_token": frappe.session.user} - ) - - confirmation_url = redirect_flow.confirmation_url - gocardless_success_page = frappe.get_hooks("gocardless_success_page") - if gocardless_success_page: - confirmation_url = frappe.get_attr(gocardless_success_page[-1])( - reference_doctype, reference_docname - ) - - data = { - "mandate": redirect_flow.links.mandate, - "customer": redirect_flow.links.customer, - "redirect_to": confirmation_url, - "redirect_message": "Mandate successfully created", - "reference_doctype": reference_doctype, - "reference_docname": reference_docname, - } - - try: - create_mandate(data) - except Exception as e: - frappe.log_error("GoCardless Mandate Registration Error") - - gateway_controller = get_gateway_controller(reference_docname) - frappe.get_doc("GoCardless Settings", gateway_controller).create_payment_request(data) - - return {"redirect_to": confirmation_url} - - except Exception as e: - frappe.log_error("GoCardless Payment Error") - return {"redirect_to": "/integrations/payment-failed"} - - -def create_mandate(data): - data = frappe._dict(data) - frappe.logger().debug(data) - - mandate = data.get("mandate") - - if frappe.db.exists("GoCardless Mandate", mandate): - return - - else: - reference_doc = frappe.db.get_value( - data.get("reference_doctype"), - data.get("reference_docname"), - ["reference_doctype", "reference_name"], - as_dict=1, - ) - erpnext_customer = frappe.db.get_value( - reference_doc.reference_doctype, reference_doc.reference_name, ["customer_name"], as_dict=1 - ) - - try: - frappe.get_doc( - { - "doctype": "GoCardless Mandate", - "mandate": mandate, - "customer": erpnext_customer.customer_name, - "gocardless_customer": data.get("customer"), - } - ).insert(ignore_permissions=True) - - except Exception: - frappe.log_error("Gocardless: Unable to create mandate") From dc1294ec548d251b1523639f6149f8fa61ae3d42 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 22 Sep 2023 16:53:06 +0200 Subject: [PATCH 024/280] refactor: missing translation --- erpnext/translations/fr.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index fbd2ece544..c3a7f351ca 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -8856,4 +8856,5 @@ Prospect Owner,Resp. du Prospect Deal Owner,Resp. de l'opportunité Stage,Etape Probability,Probabilité -Closing,Clôture \ No newline at end of file +Closing,Clôture +Allow Sales,Autoriser à la vente From b2d520647f4f295e3809e80b707249cbdf346d41 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 22 Sep 2023 16:55:58 +0200 Subject: [PATCH 025/280] refactor: missing translation --- erpnext/translations/fr.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index c3a7f351ca..04ea9de6fc 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -7097,8 +7097,8 @@ Max Discount (%),Réduction Max (%), No of Months,Nombre de mois, Customer Items,Articles du clients, Inspection Criteria,Critères d'Inspection, -Inspection Required before Purchase,Inspection Requise avant Achat, -Inspection Required before Delivery,Inspection Requise avant Livraison, +Inspection Required before Purchase,Inspection Requise à la reception, +Inspection Required before Delivery,Inspection Requise à l'expedition, Default BOM,Nomenclature par Défaut, Supply Raw Materials for Purchase,Fournir les Matières Premières pour l'Achat, If subcontracted to a vendor,Si sous-traité à un fournisseur, From f15603017c7c938a08a1a629977ac48391b1b041 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 22 Sep 2023 16:56:12 +0200 Subject: [PATCH 026/280] refactor: missing translation --- erpnext/translations/fr.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 04ea9de6fc..4f4a61ffca 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -7097,7 +7097,7 @@ Max Discount (%),Réduction Max (%), No of Months,Nombre de mois, Customer Items,Articles du clients, Inspection Criteria,Critères d'Inspection, -Inspection Required before Purchase,Inspection Requise à la reception, +Inspection Required before Purchase,Inspection Requise à la réception, Inspection Required before Delivery,Inspection Requise à l'expedition, Default BOM,Nomenclature par Défaut, Supply Raw Materials for Purchase,Fournir les Matières Premières pour l'Achat, From b3aa201eb570167c44225438031438065eef9728 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 14:57:53 +0530 Subject: [PATCH 027/280] fix: split inv allocated amt on server side --- .../doctype/payment_entry/payment_entry.js | 18 +++++++----------- .../doctype/payment_entry/payment_entry.py | 15 ++++++++------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 8ae4aa748a..2526f7b265 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -880,19 +880,15 @@ frappe.ui.form.on('Payment Entry', { //If allocate payment amount checkbox is unchecked, set zero to allocate amount row.allocated_amount = 0; - } else if (frappe.flags.allocate_payment_amount != 0 && (row.payment_term || !row.allocated_amount || paid_amount_change)) { - if(row.payment_term) - outstanding_amount = row.allocated_amount; - else - outstanding_amount = row.outstanding_amount; - if (outstanding_amount > 0 && allocated_positive_outstanding >= 0) { - row.allocated_amount = (outstanding_amount >= allocated_positive_outstanding) ? - allocated_positive_outstanding : outstanding_amount; + } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) { + if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) { + row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ? + allocated_positive_outstanding : row.outstanding_amount; allocated_positive_outstanding -= flt(row.allocated_amount); - } else if (outstanding_amount < 0 && allocated_negative_outstanding) { - row.allocated_amount = (Math.abs(outstanding_amount) >= allocated_negative_outstanding) ? - -1*allocated_negative_outstanding : outstanding_amount; + } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { + row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ? + -1*allocated_negative_outstanding : row.outstanding_amount; allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount)); } } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 38a520996c..e6403fddef 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -271,16 +271,18 @@ class PaymentEntry(AccountsController): # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key latest = latest.get(d.payment_term) or latest.get(None) - # The reference has already been fully paid if not latest: frappe.throw( _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name) ) # The reference has already been partly paid - elif latest.outstanding_amount < latest.invoice_amount and flt( - d.outstanding_amount, d.precision("outstanding_amount") - ) != flt(latest.outstanding_amount, d.precision("outstanding_amount")): + elif ( + latest.outstanding_amount < latest.invoice_amount + and flt(d.outstanding_amount, d.precision("outstanding_amount")) + != flt(latest.outstanding_amount, d.precision("outstanding_amount")) + and d.payment_term == "" + ): frappe.throw( _( "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." @@ -1751,11 +1753,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company): "voucher_type": d.voucher_type, "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), - "outstanding_amount": flt(d.outstanding_amount), - "payment_term_outstanding": payment_term_outstanding, - "allocated_amount": payment_term_outstanding + "outstanding_amount": payment_term_outstanding if payment_term_outstanding else d.outstanding_amount, + "payment_term_outstanding": payment_term_outstanding, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, "account": d.account, From 545f2ccdf1a66ccdcea04de532e637fa502b7e67 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 14:59:12 +0530 Subject: [PATCH 028/280] chore: remove unused variable --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2526f7b265..0203c45058 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -874,7 +874,6 @@ frappe.ui.form.on('Payment Entry', { } } - let outstanding_amount; $.each(frm.doc.references || [], function(i, row) { if (frappe.flags.allocate_payment_amount == 0) { //If allocate payment amount checkbox is unchecked, set zero to allocate amount From ba7212c98b81a43e5f63d010eadf99635c95e349 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:11:46 +0530 Subject: [PATCH 029/280] refactor: remove unused method --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 87f4bd0023..bc6bc1fd62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -543,17 +543,6 @@ class SalesInvoice(SellingController): ).format(item.item_code) ) - @frappe.whitelist() - def repost_accounting_entries(self): - if self.repost_required: - self.docstatus = 2 - self.make_gl_entries_on_cancel() - self.docstatus = 1 - self.make_gl_entries() - self.db_set("repost_required", 0) - else: - frappe.throw(_("No updates pending for reposting")) - def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0 From 7ebf0836833d9226ca9e0d2d231b35be4f438842 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:13:33 +0530 Subject: [PATCH 030/280] refactor: use repost accounting legder --- erpnext/controllers/accounts_controller.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 08978640bc..f29eee7737 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2216,10 +2216,11 @@ class AccountsController(TransactionBase): @frappe.whitelist() def repost_accounting_entries(self): if self.repost_required: - self.docstatus = 2 - self.make_gl_entries_on_cancel() - self.docstatus = 1 - self.make_gl_entries() + repost_ledger = frappe.new_doc("Repost Accounting Ledger") + repost_ledger.company = self.company + repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name}) + repost_ledger.insert() + repost_ledger.submit() self.db_set("repost_required", 0) else: frappe.throw(_("No updates pending for reposting")) From a856091ff4756462b6cf0f02493f6fb6ddf59921 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:18:06 +0530 Subject: [PATCH 031/280] refactor: remove repeated validation for voucher --- .../doctype/purchase_invoice/purchase_invoice.py | 13 ------------- .../accounts/doctype/sales_invoice/sales_invoice.py | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 97661e8824..1b1485fbe9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -531,23 +531,10 @@ class PurchaseInvoice(BuyingController): ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_deferred_accounting_before_repost() self.validate_write_off_account() self.validate_expense_account() self.db_set("repost_required", self.needs_repost) - def validate_deferred_accounting_before_repost(self): - # validate if deferred expense is enabled for any item - # Don't allow to update the invoice if deferred expense is enabled - if self.needs_repost: - for item in self.get("items"): - if item.enable_deferred_expense: - frappe.throw( - _( - "Deferred Expense is enabled for item {0}. You cannot update the invoice after submission." - ).format(item.item_code) - ) - def make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: gl_entries = self.get_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bc6bc1fd62..cded062352 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -527,22 +527,9 @@ class SalesInvoice(SellingController): "taxes": ("account_head",), } self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_deferred_accounting_before_repost() self.validate_accounts() self.db_set("repost_required", self.needs_repost) - def validate_deferred_accounting_before_repost(self): - # validate if deferred revenue is enabled for any item - # Don't allow to update the invoice if deferred revenue is enabled - if self.needs_repost: - for item in self.get("items"): - if item.enable_deferred_revenue: - frappe.throw( - _( - "Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission." - ).format(item.item_code) - ) - def set_paid_amount(self): paid_amount = 0.0 base_paid_amount = 0.0 From 1856050ef9022d5688bcd98ea4edb146946b8b7a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 19:30:42 +0530 Subject: [PATCH 032/280] fix: do not run bg job for single doc --- .../repost_accounting_ledger.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 4cf2ed2f46..e533fed35b 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -139,14 +139,17 @@ class RepostAccountingLedger(Document): return rendered_page def on_submit(self): - job_name = "repost_accounting_ledger_" + self.name - frappe.enqueue( - method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", - account_repost_doc=self.name, - is_async=True, - job_name=job_name, - ) - frappe.msgprint(_("Repost has started in the background")) + if len(self.vouchers) > 1: + job_name = "repost_accounting_ledger_" + self.name + frappe.enqueue( + method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", + account_repost_doc=self.name, + is_async=True, + job_name=job_name, + ) + frappe.msgprint(_("Repost has started in the background")) + else: + start_repost(self.name) @frappe.whitelist() From 8ef0d8870830b1a4f848a91d201fb740e7f47aa0 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 20:29:38 +0530 Subject: [PATCH 033/280] fix: call validate before setting repost flag --- .../purchase_invoice/purchase_invoice.py | 11 ++++- .../repost_accounting_ledger.py | 46 ++++++++++--------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1b1485fbe9..85ed1260d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -11,6 +11,9 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, import erpnext from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, +) from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, get_total_in_party_account_currency, @@ -484,6 +487,11 @@ class PurchaseInvoice(BuyingController): _("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt) ) + def validate_for_repost(self): + self.validate_write_off_account() + self.validate_expense_account() + validate_docs_for_deferred_accounting([], [self.name]) + def on_submit(self): super(PurchaseInvoice, self).on_submit() @@ -531,8 +539,7 @@ class PurchaseInvoice(BuyingController): ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_write_off_account() - self.validate_expense_account() + self.validate_for_repost() self.db_set("repost_required", self.needs_repost) def make_gl_entries(self, gl_entries=None, from_repost=False): diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index e533fed35b..dbb0971fde 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -21,29 +21,8 @@ class RepostAccountingLedger(Document): def validate_for_deferred_accounting(self): sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"] - docs_with_deferred_revenue = frappe.db.get_all( - "Sales Invoice Item", - filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True}, - fields=["parent"], - as_list=1, - ) - purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"] - docs_with_deferred_expense = frappe.db.get_all( - "Purchase Invoice Item", - filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1}, - fields=["parent"], - as_list=1, - ) - - if docs_with_deferred_revenue or docs_with_deferred_expense: - frappe.throw( - _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format( - frappe.bold( - comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]) - ) - ) - ) + validate_docs_for_deferred_accounting(sales_docs, purchase_docs) def validate_for_closed_fiscal_year(self): if self.vouchers: @@ -184,3 +163,26 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries() frappe.db.commit() + + +def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): + docs_with_deferred_revenue = frappe.db.get_all( + "Sales Invoice Item", + filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True}, + fields=["parent"], + as_list=1, + ) + + docs_with_deferred_expense = frappe.db.get_all( + "Purchase Invoice Item", + filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1}, + fields=["parent"], + as_list=1, + ) + + if docs_with_deferred_revenue or docs_with_deferred_expense: + frappe.throw( + _("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format( + frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])) + ) + ) From 61c6ebbb95b0e7ecbb86312646fe862b9a471d95 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Sat, 23 Sep 2023 20:30:50 +0530 Subject: [PATCH 034/280] fix: validation for si --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index cded062352..f380825db7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -15,6 +15,9 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, validate_loyalty_points, ) +from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import ( + validate_docs_for_deferred_accounting, +) from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) @@ -165,6 +168,12 @@ class SalesInvoice(SellingController): self.validate_account_for_change_amount() self.validate_income_account() + def validate_for_repost(self): + self.validate_write_off_account() + self.validate_account_for_change_amount() + self.validate_income_account() + validate_docs_for_deferred_accounting([self.name], []) + def validate_fixed_asset(self): for d in self.get("items"): if d.is_fixed_asset and d.meta.get_field("asset") and d.asset: @@ -527,7 +536,7 @@ class SalesInvoice(SellingController): "taxes": ("account_head",), } self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_accounts() + self.validate_for_repost() self.db_set("repost_required", self.needs_repost) def set_paid_amount(self): From 38aebf65e2c9a93e3fc0d999668040f471c55b51 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 25 Sep 2023 11:08:24 +0530 Subject: [PATCH 035/280] refactor!: remove `stripe_integration.py` --- .../payment_request/payment_request.py | 4 +- .../stripe_integration.py | 70 ------------------- 2 files changed, 3 insertions(+), 71 deletions(-) delete mode 100644 erpnext/erpnext_integrations/stripe_integration.py diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 11d6d5f433..028efc4d6d 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -20,7 +20,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.party import get_party_account, get_party_bank_account from erpnext.accounts.utils import get_account_currency -from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription from erpnext.utilities import payment_app_import_guard @@ -393,6 +392,9 @@ class PaymentRequest(Document): def create_subscription(self, payment_provider, gateway_controller, data): if payment_provider == "stripe": + with payment_app_import_guard(): + from payments.payment_gateways.stripe_integration import create_stripe_subscription + return create_stripe_subscription(gateway_controller, data) diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py deleted file mode 100644 index 634e5c2e89..0000000000 --- a/erpnext/erpnext_integrations/stripe_integration.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe import _ -from frappe.integrations.utils import create_request_log - -from erpnext.utilities import payment_app_import_guard - - -def create_stripe_subscription(gateway_controller, data): - with payment_app_import_guard(): - import stripe - - stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) - stripe_settings.data = frappe._dict(data) - - stripe.api_key = stripe_settings.get_password(fieldname="secret_key", raise_exception=False) - stripe.default_http_client = stripe.http_client.RequestsClient() - - try: - stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") - stripe_settings.payment_plans = frappe.get_doc( - "Payment Request", stripe_settings.data.reference_docname - ).subscription_plans - return create_subscription_on_stripe(stripe_settings) - - except Exception: - stripe_settings.log_error("Unable to create Stripe subscription") - return { - "redirect_to": frappe.redirect_to_message( - _("Server Error"), - _( - "It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account." - ), - ), - "status": 401, - } - - -def create_subscription_on_stripe(stripe_settings): - with payment_app_import_guard(): - import stripe - - items = [] - for payment_plan in stripe_settings.payment_plans: - plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id") - items.append({"price": plan, "quantity": payment_plan.qty}) - - try: - customer = stripe.Customer.create( - source=stripe_settings.data.stripe_token_id, - description=stripe_settings.data.payer_name, - email=stripe_settings.data.payer_email, - ) - - subscription = stripe.Subscription.create(customer=customer, items=items) - - if subscription.status == "active": - stripe_settings.integration_request.db_set("status", "Completed", update_modified=False) - stripe_settings.flags.status_changed_to = "Completed" - - else: - stripe_settings.integration_request.db_set("status", "Failed", update_modified=False) - frappe.log_error(f"Stripe Subscription ID {subscription.id}: Payment failed") - except Exception: - stripe_settings.integration_request.db_set("status", "Failed", update_modified=False) - stripe_settings.log_error("Unable to create Stripe subscription") - - return stripe_settings.finalize_request() From ee178ff2ce4fa2021dc846dfaa363aff42fa17bf Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 25 Sep 2023 12:12:11 +0530 Subject: [PATCH 036/280] chore: add disabled field for bank account (#37226) --- erpnext/accounts/doctype/bank_account/bank_account.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 41d79479ca..32f1c675d3 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -13,6 +13,7 @@ "account_type", "account_subtype", "column_break_7", + "disabled", "is_default", "is_company_account", "company", @@ -199,10 +200,16 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Branch Code" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" } ], "links": [], - "modified": "2022-05-04 15:49:42.620630", + "modified": "2023-09-22 21:31:34.763977", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", From 5e4b73918db809afe0c3880a63537d8e4743f3bf Mon Sep 17 00:00:00 2001 From: NIYAZ RAZAK <76736615+niyazrazak@users.noreply.github.com> Date: Mon, 25 Sep 2023 22:15:08 +0300 Subject: [PATCH 037/280] fix: set route to cost center cost center mapping is not correct --- .../report/profitability_analysis/profitability_analysis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index c9accef7a6..ebd0ec1a73 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -112,7 +112,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "to_fiscal_year": data.fiscal_year }; - if(data.based_on == 'cost_center'){ + if(data.based_on == 'Cost Center'){ frappe.route_options["cost_center"] = data.account } else { frappe.route_options["project"] = data.account From 0f6b5300327d65edd764c83e4437ed1cab1cb6de Mon Sep 17 00:00:00 2001 From: Abraham Kalungi <85731451+kalungia@users.noreply.github.com> Date: Tue, 26 Sep 2023 05:27:50 +0200 Subject: [PATCH 038/280] feat: Added ledger for Botswana VAT (#37212) feat: Added ledger for Botswana VAT (#37212) --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 3532d6b456..b59e2194f5 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -157,9 +157,14 @@ }, "Botswana": { - "Botswana Tax": { + "Botswana Tax 14%": { "account_name": "VAT", - "tax_rate": 12.00 + "tax_rate": 14.00 + } + "Botswana Tax 12%": { + "account_name": "VAT", + "tax_rate": 12.00, + "default": 1 } }, From 73fc97495040fe574cef94930e06aaa7b4e87b7b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 26 Sep 2023 10:54:23 +0530 Subject: [PATCH 039/280] fix: incorrect `Parent Task` getting set for 2nd to nth child Task (#37230) --- erpnext/projects/doctype/project/project.py | 29 ++++++++------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index a66b5497ce..e9aed1afb4 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -70,6 +70,7 @@ class Project(Document): tmp_task_details.append(template_task_details) task = self.create_task_from_template(template_task_details) project_tasks.append(task) + self.dependency_mapping(tmp_task_details, project_tasks) def create_task_from_template(self, task_details): @@ -108,36 +109,28 @@ class Project(Document): def dependency_mapping(self, template_tasks, project_tasks): for project_task in project_tasks: - if project_task.get("template_task"): - template_task = frappe.get_doc("Task", project_task.template_task) - else: - template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0] - template_task = frappe.get_doc("Task", template_task.name) + template_task = frappe.get_doc("Task", project_task.template_task) self.check_depends_on_value(template_task, project_task, project_tasks) self.check_for_parent_tasks(template_task, project_task, project_tasks) def check_depends_on_value(self, template_task, project_task, project_tasks): if template_task.get("depends_on") and not project_task.get("depends_on"): + project_template_map = {pt.template_task: pt for pt in project_tasks} + for child_task in template_task.get("depends_on"): - child_task_subject = frappe.db.get_value("Task", child_task.task, "subject") - corresponding_project_task = list( - filter(lambda x: x.subject == child_task_subject, project_tasks) - ) - if len(corresponding_project_task): + if project_template_map and project_template_map.get(child_task.task): project_task.reload() # reload, as it might have been updated in the previous iteration - project_task.append("depends_on", {"task": corresponding_project_task[0].name}) + project_task.append("depends_on", {"task": project_template_map.get(child_task.task).name}) project_task.save() def check_for_parent_tasks(self, template_task, project_task, project_tasks): if template_task.get("parent_task") and not project_task.get("parent_task"): - parent_task_subject = frappe.db.get_value("Task", template_task.get("parent_task"), "subject") - corresponding_project_task = list( - filter(lambda x: x.subject == parent_task_subject, project_tasks) - ) - if len(corresponding_project_task): - project_task.parent_task = corresponding_project_task[0].name - project_task.save() + for pt in project_tasks: + if pt.template_task == template_task.parent_task: + project_task.parent_task = pt.name + project_task.save() + break def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: From dd911aa521f6b4c692198345c5f1ec02875c8fe9 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Tue, 26 Sep 2023 13:45:39 +0530 Subject: [PATCH 040/280] chore(stock_ledger): drop redundant check Commit c2d7461d3cac639778e7740c4b08e459332a6b14 dropped a usage of `last_valuation_rate` around this code block. After that, it was always checked although the value would be None as it was being explicitly set above. Signed-off-by: Akhil Narang --- erpnext/stock/stock_ledger.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index db71fe280a..d3807b0f97 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1464,8 +1464,6 @@ def get_valuation_rate( if not company: company = frappe.get_cached_value("Warehouse", warehouse, "company") - last_valuation_rate = None - # Get moving average rate of a specific batch number if warehouse and serial_and_batch_bundle: batch_obj = BatchNoValuation( @@ -1482,21 +1480,18 @@ def get_valuation_rate( return batch_obj.get_incoming_rate() # Get valuation rate from last sle for the same item and warehouse - if not last_valuation_rate or last_valuation_rate[0][0] is None: - last_valuation_rate = frappe.db.sql( - """select valuation_rate - from `tabStock Ledger Entry` force index (item_warehouse) - where - item_code = %s - AND warehouse = %s - AND valuation_rate >= 0 - AND is_cancelled = 0 - AND NOT (voucher_no = %s AND voucher_type = %s) - order by posting_date desc, posting_time desc, name desc limit 1""", - (item_code, warehouse, voucher_no, voucher_type), - ) - - if last_valuation_rate: + if last_valuation_rate := frappe.db.sql( + """select valuation_rate + from `tabStock Ledger Entry` force index (item_warehouse) + where + item_code = %s + AND warehouse = %s + AND valuation_rate >= 0 + AND is_cancelled = 0 + AND NOT (voucher_no = %s AND voucher_type = %s) + order by posting_date desc, posting_time desc, name desc limit 1""", + (item_code, warehouse, voucher_no, voucher_type), + ): return flt(last_valuation_rate[0][0]) # If negative stock allowed, and item delivered without any incoming entry, From 656c758263fa91f847377a503d76b70b91ffb741 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 26 Sep 2023 14:20:41 +0530 Subject: [PATCH 041/280] chore: Add missing comma --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index b59e2194f5..a746ebee7e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -160,7 +160,7 @@ "Botswana Tax 14%": { "account_name": "VAT", "tax_rate": 14.00 - } + }, "Botswana Tax 12%": { "account_name": "VAT", "tax_rate": 12.00, From ed7f67b1a85a1c2ef47bba77267e20cf6bbced71 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Sep 2023 14:23:21 +0530 Subject: [PATCH 042/280] refactor: remove references in repost doctypes upon parent doc delet --- .../repost_accounting_ledger.json | 5 ++- .../repost_payment_ledger.json | 5 ++- erpnext/controllers/accounts_controller.py | 37 ++++++++++++++++--- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json index 8d56c9bb11..5b7cd2b0b2 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json @@ -55,7 +55,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-27 15:47:58.975034", + "modified": "2023-09-26 14:21:27.362567", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger", @@ -77,5 +77,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json index 5175fd169f..ed8d395a0e 100644 --- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json +++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json @@ -99,7 +99,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-08 07:38:40.079038", + "modified": "2023-09-26 14:21:35.719727", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Payment Ledger", @@ -155,5 +155,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f29eee7737..6efa09bb99 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -243,13 +243,38 @@ class AccountsController(TransactionBase): _doc.cancel() _doc.delete() - def on_trash(self): - # delete references in 'Repost Payment Ledger' - rpi = frappe.qb.DocType("Repost Payment Ledger Items") - frappe.qb.from_(rpi).delete().where( - (rpi.voucher_type == self.doctype) & (rpi.voucher_no == self.name) - ).run() + def _remove_references_in_repost_doctypes(self): + repost_doctypes = ["Repost Payment Ledger Items", "Repost Accounting Ledger Items"] + for _doctype in repost_doctypes: + dt = frappe.qb.DocType(_doctype) + rows = ( + frappe.qb.from_(dt) + .select(dt.name, dt.parent, dt.parenttype) + .where((dt.voucher_type == self.doctype) & (dt.voucher_no == self.name)) + .run(as_dict=True) + ) + + if rows: + references_map = frappe._dict() + for x in rows: + references_map.setdefault((x.parenttype, x.parent), []).append(x.name) + + for doc, rows in references_map.items(): + repost_doc = frappe.get_doc(doc[0], doc[1]) + + for row in rows: + if _doctype == "Repost Payment Ledger Items": + repost_doc.remove(repost_doc.get("repost_vouchers", {"name": row})[0]) + else: + repost_doc.remove(repost_doc.get("vouchers", {"name": row})[0]) + + repost_doc.flags.ignore_validate_update_after_submit = True + repost_doc.flags.ignore_links = True + repost_doc.save(ignore_permissions=True) + + def on_trash(self): + self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() # delete sl and gl entries on deletion of transaction From 832d7e7d7bdf8569c4ae0d35d929f7a0a440954f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 26 Sep 2023 15:08:40 +0530 Subject: [PATCH 043/280] fix: set AR filters after rename --- .../process_statement_of_accounts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 9a5ad35bcf..ee1c9cd208 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -145,7 +145,8 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency): def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, - "customer": entry.customer, + "party_type": "Customer", + "party": entry.customer, "customer_name": entry.customer_name if entry.customer_name else None, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, From 7d96044d8eb74ad1cb78548183350b7512cc091b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 26 Sep 2023 15:10:16 +0530 Subject: [PATCH 044/280] fix: change filters for AR summary --- .../accounts_receivable.js | 2 -- .../accounts_receivable_summary.js | 24 ++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index bb00d616db..67a14e7880 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -52,9 +52,7 @@ frappe.query_reports["Accounts Receivable"] = { }, on_change: () => { frappe.query_report.set_filter_value('party', ""); - let party_type = frappe.query_report.get_filter_value('party_type'); frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); - } }, { diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 715cd6476e..a78fbeb030 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -72,10 +72,28 @@ frappe.query_reports["Accounts Receivable Summary"] = { } }, { - "fieldname":"customer", - "label": __("Customer"), + "fieldname": "party_type", + "label": __("Party Type"), "fieldtype": "Link", - "options": "Customer" + "options": "Party Type", + "Default": "Customer", + get_query: () => { + return { + filters: { + 'account_type': 'Receivable' + } + }; + }, + on_change: () => { + frappe.query_report.set_filter_value('party', ""); + frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); + } + }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", }, { "fieldname":"customer_group", From b1770b3f8679a2edf8375d0185547a7fcf2bb2bd Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 26 Sep 2023 15:10:20 +0530 Subject: [PATCH 045/280] refactor: remove test `test_default_bank_account` --- .../plaid_settings/test_plaid_settings.py | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 86e1b31eba..67168536e7 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -43,40 +43,6 @@ class TestPlaidSettings(unittest.TestCase): add_account_subtype("loan") self.assertEqual(frappe.get_doc("Bank Account Subtype", "loan").name, "loan") - def test_default_bank_account(self): - if not frappe.db.exists("Bank", "Citi"): - frappe.get_doc({"doctype": "Bank", "bank_name": "Citi"}).insert() - - bank_accounts = { - "account": { - "subtype": "checking", - "mask": "0000", - "type": "depository", - "id": "6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK", - "name": "Plaid Checking", - }, - "account_id": "6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK", - "link_session_id": "db673d75-61aa-442a-864f-9b3f174f3725", - "accounts": [ - { - "type": "depository", - "subtype": "checking", - "mask": "0000", - "id": "6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK", - "name": "Plaid Checking", - } - ], - "institution": {"institution_id": "ins_6", "name": "Citi"}, - } - - bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler) - company = frappe.db.get_single_value("Global Defaults", "default_company") - frappe.db.set_value("Company", company, "default_bank_account", None) - - self.assertRaises( - frappe.ValidationError, add_bank_accounts, response=bank_accounts, bank=bank, company=company - ) - def test_new_transaction(self): if not frappe.db.exists("Bank", "Citi"): frappe.get_doc({"doctype": "Bank", "bank_name": "Citi"}).insert() From 76a5d94f37054a8c24f84fecf7d55fd9b0d01e81 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 26 Sep 2023 15:11:54 +0530 Subject: [PATCH 046/280] fix: set new AP summary filters --- .../accounts_payable/accounts_payable.js | 3 --- .../accounts_payable_summary.js | 23 ++++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 27a85701ed..484ff7fa2b 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -108,11 +108,8 @@ frappe.query_reports["Accounts Payable"] = { }, on_change: () => { frappe.query_report.set_filter_value('party', ""); - let party_type = frappe.query_report.get_filter_value('party_type'); frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); - } - }, { "fieldname":"party", diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index ea200720df..8a1725c048 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -72,10 +72,27 @@ frappe.query_reports["Accounts Payable Summary"] = { } }, { - "fieldname":"supplier", - "label": __("Supplier"), + "fieldname": "party_type", + "label": __("Party Type"), "fieldtype": "Link", - "options": "Supplier" + "options": "Party Type", + get_query: () => { + return { + filters: { + 'account_type': 'Payable' + } + }; + }, + on_change: () => { + frappe.query_report.set_filter_value('party', ""); + frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); + } + }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", }, { "fieldname":"payment_terms_template", From d558ba29facedc3776fb6a4495c3e63c04e63a19 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:55:16 +0530 Subject: [PATCH 047/280] fix: serial number decimal issue (backport #37242) (#37250) fix: serial number decimal issue (#37242) (cherry picked from commit 78ab2013e59bb6a95d10164328ad3f7c815497b5) Co-authored-by: rohitwaghchaure --- erpnext/controllers/subcontracting_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index d4270a76d4..5fa66b1a87 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -804,7 +804,7 @@ class SubcontractingController(StockController): { "item_code": item.rm_item_code, "warehouse": self.supplier_warehouse, - "actual_qty": -1 * flt(item.consumed_qty), + "actual_qty": -1 * flt(item.consumed_qty, item.precision("consumed_qty")), "dependant_sle_voucher_detail_no": item.reference_name, }, ) From 2cca37ad7d1f650142c21d7e1f3a127198acbb43 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 18:29:54 +0530 Subject: [PATCH 048/280] fix: reserved qty for production plan (backport #37251) (#37253) fix: reserved qty for production plan (#37251) (cherry picked from commit 0a0d5b3e6612be1b6e1c9febd4a86a772a02511d) Co-authored-by: rohitwaghchaure --- .../production_plan/test_production_plan.py | 53 +++++++++++++++++++ .../doctype/work_order/work_order.py | 15 +++--- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 5292571058..2348d2b688 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1050,6 +1050,59 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_resered_qty_for_production_plan_for_work_order(self): + from erpnext.stock.utils import get_or_make_bin + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + pln = create_production_plan(item_code="Test Production Item 1") + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty - before_qty, 1) + + pln.make_work_order() + + work_orders = [] + for row in frappe.get_all("Work Order", filters={"production_plan": pln.name}, fields=["name"]): + wo_doc = frappe.get_doc("Work Order", row.name) + wo_doc.source_warehouse = "_Test Warehouse - _TC" + wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC" + wo_doc.fg_warehouse = "_Test Warehouse - _TC" + for d in wo_doc.required_items: + d.source_warehouse = "_Test Warehouse - _TC" + make_stock_entry( + item_code=d.item_code, + qty=d.required_qty, + rate=100, + target="_Test Warehouse - _TC", + ) + + wo_doc.submit() + work_orders.append(wo_doc) + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty, before_qty) + + rm_work_order = None + for wo_doc in work_orders: + for d in wo_doc.required_items: + if d.item_code == "Raw Material Item 1": + rm_work_order = wo_doc + break + + if rm_work_order: + s = frappe.get_doc(make_se_from_wo(rm_work_order.name, "Material Transfer for Manufacture", 1)) + s.submit() + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty, before_qty) + def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self): from erpnext.stock.utils import get_or_make_bin diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 5ad79f94b7..d8fc220386 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1519,16 +1519,17 @@ def get_reserved_qty_for_production( wo = frappe.qb.DocType("Work Order") wo_item = frappe.qb.DocType("Work Order Item") + if check_production_plan: + qty_field = wo_item.required_qty + else: + qty_field = Case() + qty_field = qty_field.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) + qty_field = qty_field.else_(wo_item.required_qty - wo_item.consumed_qty) + query = ( frappe.qb.from_(wo) .from_(wo_item) - .select( - Sum( - Case() - .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) - .else_(wo_item.required_qty - wo_item.consumed_qty) - ) - ) + .select(Sum(qty_field)) .where( (wo_item.item_code == item_code) & (wo_item.parent == wo.name) From 8051c2d3cb78cbe40beca311224beedca0357542 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Tue, 26 Sep 2023 15:29:27 +0200 Subject: [PATCH 049/280] refactor: In Quotation Item, discount_and_margin section should have same collapsible_depends_on as other similar DocType (Sales Order Item,Sales Invoice Item,...) (#37252) --- erpnext/selling/doctype/quotation_item/quotation_item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index f2aabc5240..dde2f9b76b 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -235,6 +235,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -666,7 +667,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-02-06 11:00:07.042364", + "modified": "2023-09-26 13:42:11.410294", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", From 8c5fcb825716a63a074b1d83b830a06412b4c035 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 27 Sep 2023 10:22:09 +0530 Subject: [PATCH 050/280] fix: PCV posting issues (#37029) * fix: PCV posting issues * fix: process closing entries separately in a background job * test: Update tests * chore: fix broken ci --- .../payment_request/payment_request.json | 7 ++- .../period_closing_voucher.json | 10 +++- .../period_closing_voucher.py | 53 ++++++++++++++----- .../test_period_closing_voucher.py | 2 +- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 5ffd7180f6..66b5c4b983 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -268,8 +268,7 @@ "fieldname": "email_to", "fieldtype": "Data", "in_global_search": 1, - "label": "To", - "options": "Email" + "label": "To" }, { "depends_on": "eval: doc.payment_channel != \"Phone\"", @@ -340,8 +339,8 @@ }, { "fieldname": "payment_url", - "hidden": 1, "fieldtype": "Data", + "hidden": 1, "length": 500, "options": "URL", "read_only": 1 @@ -396,7 +395,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-09-16 14:15:02.510890", + "modified": "2023-09-27 09:51:42.277638", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 54a76b3419..624b5f82f6 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -8,6 +8,7 @@ "transaction_date", "posting_date", "fiscal_year", + "year_start_date", "amended_from", "company", "column_break1", @@ -100,16 +101,22 @@ "fieldtype": "Text", "label": "Error Message", "read_only": 1 + }, + { + "fieldname": "year_start_date", + "fieldtype": "Date", + "label": "Year Start Date" } ], "icon": "fa fa-file-text", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2022-07-20 14:51:04.714154", + "modified": "2023-09-11 20:19:11.810533", "modified_by": "Administrator", "module": "Accounts", "name": "Period Closing Voucher", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -144,5 +151,6 @@ "search_fields": "posting_date, fiscal_year", "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "closing_account_head" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index d984d86af2..674db6c2e4 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -95,15 +95,23 @@ class PeriodClosingVoucher(AccountsController): self.check_if_previous_year_closed() - pce = frappe.db.sql( - """select name from `tabPeriod Closing Voucher` - where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""", - (self.posting_date, self.fiscal_year, self.company), + pcv = frappe.qb.DocType("Period Closing Voucher") + existing_entry = ( + frappe.qb.from_(pcv) + .select(pcv.name) + .where( + (pcv.posting_date >= self.posting_date) + & (pcv.fiscal_year == self.fiscal_year) + & (pcv.docstatus == 1) + & (pcv.company == self.company) + ) + .run() ) - if pce and pce[0][0]: + + if existing_entry and existing_entry[0][0]: frappe.throw( _("Another Period Closing Entry {0} has been made after {1}").format( - pce[0][0], self.posting_date + existing_entry[0][0], self.posting_date ) ) @@ -130,18 +138,27 @@ class PeriodClosingVoucher(AccountsController): frappe.enqueue( process_gl_entries, gl_entries=gl_entries, + voucher_name=self.name, + timeout=3000, + ) + + frappe.enqueue( + process_closing_entries, + gl_entries=gl_entries, closing_entries=closing_entries, voucher_name=self.name, company=self.company, closing_date=self.posting_date, - queue="long", + timeout=3000, ) + frappe.msgprint( _("The GL Entries will be processed in the background, it can take a few minutes."), alert=True, ) else: - process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) + process_gl_entries(gl_entries, self.name) + process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -322,17 +339,12 @@ class PeriodClosingVoucher(AccountsController): return query.run(as_dict=1) -def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date): - from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( - make_closing_entries, - ) +def process_gl_entries(gl_entries, voucher_name): from erpnext.accounts.general_ledger import make_gl_entries try: if gl_entries: make_gl_entries(gl_entries, merge_entries=False) - - make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() @@ -340,6 +352,19 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") +def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): + from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( + make_closing_entries, + ) + + try: + if gl_entries + closing_entries: + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) + except Exception as e: + frappe.db.rollback() + frappe.log_error(e) + + def make_reverse_gl_entries(voucher_type, voucher_no): from erpnext.accounts.general_ledger import make_reverse_gl_entries diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 5d08e8d1c2..1bd565e1b3 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -10,7 +10,7 @@ from frappe.utils import today from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.utils import get_fiscal_year, now +from erpnext.accounts.utils import get_fiscal_year class TestPeriodClosingVoucher(unittest.TestCase): From 4ada5a488ebee75c3286af5c9492ea53274730c4 Mon Sep 17 00:00:00 2001 From: vr-greycube <66350441+vr-greycube@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:38:32 +0530 Subject: [PATCH 051/280] fix: Use default Cost Center of the Company for additional discount (#37234) fix: Set cost center as default company cost center When Discount Accounting in enabled in Selling Settings, use Company default Cost Center while making GL entries for additional_discount_account --- 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 6efa09bb99..6812940ee2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1491,7 +1491,7 @@ class AccountsController(TransactionBase): "account": self.additional_discount_account, "against": supplier_or_customer, dr_or_cr: self.base_discount_amount, - "cost_center": self.cost_center, + "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), }, item=self, ) From 2dc95e5d59526a531b064815d195df8413d1b837 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Sep 2023 12:34:40 +0530 Subject: [PATCH 052/280] fix: trial balance report freezes when adding filters (#37264) fix: Only add onclick if correct data is returned workaround for https://github.com/frappe/datatable/issues/177 --- erpnext/public/js/financial_statements.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 959cf507d5..907a775bfa 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -6,8 +6,10 @@ erpnext.financial_statements = { if (data && column.fieldname=="account") { value = data.account_name || value; - column.link_onclick = - "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + if (data.account) { + column.link_onclick = + "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + } column.is_tree = true; } From 888ed36eed9eae73d23de71feeb67425a085e950 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:01:18 +0530 Subject: [PATCH 053/280] fix: set route filter values for AP --- .../accounts_payable/accounts_payable.js | 38 ++++++++++++------- erpnext/buying/doctype/supplier/supplier.js | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 484ff7fa2b..9c73cbb344 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -95,18 +95,11 @@ frappe.query_reports["Accounts Payable"] = { "options": "Payment Terms Template" }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - get_query: () => { - return { - filters: { - 'account_type': 'Payable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); } @@ -114,8 +107,15 @@ frappe.query_reports["Accounts Payable"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname": "supplier_group", @@ -164,3 +164,15 @@ frappe.query_reports["Accounts Payable"] = { } erpnext.utils.add_dimensions('Accounts Payable', 9); + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Payable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 08dc44c71b..70d27828b4 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -88,7 +88,7 @@ frappe.ui.form.on("Supplier", { }, __("View")); frm.add_custom_button(__('Accounts Payable'), function () { - frappe.set_route('query-report', 'Accounts Payable', { supplier: frm.doc.name }); + frappe.set_route('query-report', 'Accounts Payable', { party_type: "Supplier", party: frm.doc.name }); }, __("View")); frm.add_custom_button(__('Bank Account'), function () { From 9d15124a6a3ad1bacc944f2f62b3999548bd40e8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:01:48 +0530 Subject: [PATCH 054/280] fix: set route filter values for AR --- .../accounts_receivable.js | 42 ++++++++++++------- erpnext/selling/doctype/customer/customer.js | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 67a14e7880..1073be0bdc 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -1,6 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +frappe.provide("erpnext.utils"); + frappe.query_reports["Accounts Receivable"] = { "filters": [ { @@ -38,19 +40,11 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - "Default": "Customer", - get_query: () => { - return { - filters: { - 'account_type': 'Receivable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); } @@ -58,8 +52,15 @@ frappe.query_reports["Accounts Receivable"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname": "party_account", @@ -192,3 +193,16 @@ frappe.query_reports["Accounts Receivable"] = { } erpnext.utils.add_dimensions('Accounts Receivable', 9); + + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Receivable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index e274a52690..42932ad8bd 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -138,7 +138,7 @@ frappe.ui.form.on("Customer", { // custom buttons frm.add_custom_button(__('Accounts Receivable'), function () { - frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name}); + frappe.set_route('query-report', 'Accounts Receivable', { party_type: "Customer", party: frm.doc.name }); }, __('View')); frm.add_custom_button(__('Accounting Ledger'), function () { From e7239e02d4ff7ec0338f45aa4264dc65d12d3f36 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:02:52 +0530 Subject: [PATCH 055/280] fix: query for multiselect filter --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 7942402365..e3b671f397 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -801,7 +801,7 @@ class ReceivablePayableReport(object): self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type) if self.filters.get("party"): - self.qb_selection_filter.append(self.filters.party == self.ple.party) + self.qb_selection_filter.append(self.ple.party.isin(self.filters.party)) if self.filters.party_account: self.qb_selection_filter.append(self.ple.account == self.filters.party_account) From f7cb68a45fade2499d51d92a5a096f0640b039f2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:03:37 +0530 Subject: [PATCH 056/280] fix: summary report filters --- .../accounts_payable_summary.js | 38 +++++++++++------- .../accounts_receivable_summary.js | 39 ++++++++++++------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 8a1725c048..9e575e669d 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -72,18 +72,11 @@ frappe.query_reports["Accounts Payable Summary"] = { } }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - get_query: () => { - return { - filters: { - 'account_type': 'Payable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); } @@ -91,8 +84,15 @@ frappe.query_reports["Accounts Payable Summary"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname":"payment_terms_template", @@ -122,3 +122,15 @@ frappe.query_reports["Accounts Payable Summary"] = { } erpnext.utils.add_dimensions('Accounts Payable Summary', 9); + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Payable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index a78fbeb030..5ad10c7890 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -72,19 +72,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { } }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - "Default": "Customer", - get_query: () => { - return { - filters: { - 'account_type': 'Receivable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); } @@ -92,8 +84,15 @@ frappe.query_reports["Accounts Receivable Summary"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname":"customer_group", @@ -151,3 +150,15 @@ frappe.query_reports["Accounts Receivable Summary"] = { } erpnext.utils.add_dimensions('Accounts Receivable Summary', 9); + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Receivable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file From 4b28154f5ee724d1a2c109f5b9e5b27bc707436b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:04:13 +0530 Subject: [PATCH 057/280] fix: process soa filter for multiselect --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index ee1c9cd208..52ae9513dd 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -146,7 +146,7 @@ def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, "party_type": "Customer", - "party": entry.customer, + "party": [entry.customer], "customer_name": entry.customer_name if entry.customer_name else None, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, From 59e8abfd57d85719147129d22e4cc2293a379d71 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:57:49 +0530 Subject: [PATCH 058/280] fix: party format in test --- .../accounts/report/accounts_payable/test_accounts_payable.py | 2 +- .../report/accounts_receivable/test_accounts_receivable.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 3cf93cc865..9f03d92cd5 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "party_type": "Supplier", - "party": self.supplier, + "party": [self.supplier], "report_date": today(), "range1": 30, "range2": 60, diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index b98916ee44..8c13f85a98 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -573,7 +573,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "party_type": "Customer", - "party": self.customer, + "party": [self.customer], "report_date": today(), "range1": 30, "range2": 60, From 296b233659f2596b6b7de479ca3a00df99885eb4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 27 Sep 2023 15:27:29 +0530 Subject: [PATCH 059/280] chore: patch to delete Payment Gateways --- erpnext/patches.txt | 1 + erpnext/patches/v15_0/delete_payment_gateway_doctypes.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 erpnext/patches/v15_0/delete_payment_gateway_doctypes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e9c056e3a9..8f2d076b53 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -344,5 +344,6 @@ erpnext.patches.v15_0.delete_woocommerce_settings_doctype erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults erpnext.patches.v14_0.update_invoicing_period_in_subscription execute:frappe.delete_doc("Page", "welcome-to-erpnext") +erpnext.patches.v15_0.delete_payment_gateway_doctypes # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v15_0/delete_payment_gateway_doctypes.py b/erpnext/patches/v15_0/delete_payment_gateway_doctypes.py new file mode 100644 index 0000000000..959b065780 --- /dev/null +++ b/erpnext/patches/v15_0/delete_payment_gateway_doctypes.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + for dt in ("GoCardless Settings", "GoCardless Mandate", "Mpesa Settings"): + frappe.delete_doc("DocType", dt, ignore_missing=True) From b3486b43c45df03697b13a4f0fc655d70bc52407 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 27 Sep 2023 18:51:09 +0200 Subject: [PATCH 060/280] fix: german translations of Accounts Settings --- erpnext/translations/de.csv | 39 +++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 03e9de4c55..b9e010add9 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -3345,7 +3345,7 @@ Cannot Calculate Arrival Time as Driver Address is Missing.,"Die Ankunftszeit ka Cannot Optimize Route as Driver Address is Missing.,"Route kann nicht optimiert werden, da die Fahreradresse fehlt.", Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.,"Aufgabe {0} kann nicht abgeschlossen werden, da die abhängige Aufgabe {1} nicht abgeschlossen / abgebrochen wurde.", Cannot find a matching Item. Please select some other value for {0}.,Ein passender Artikel kann nicht gefunden werden. Bitte einen anderen Wert für {0} auswählen., -"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings","Artikel {0} in Zeile {1} kann nicht mehr als {2} in Rechnung gestellt werden. Um eine Überberechnung zuzulassen, legen Sie die Überberechnung in den Kontoeinstellungen fest", +"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings","Artikel {0} in Zeile {1} kann nicht mehr als {2} in Rechnung gestellt werden. Um eine Überberechnung zuzulassen, legen Sie die Überberechnung in den Buchhaltungseinstellungen fest", "Capacity Planning Error, planned start time can not be same as end time","Kapazitätsplanungsfehler, die geplante Startzeit darf nicht mit der Endzeit übereinstimmen", Categories,Kategorien, Changes in {0},Änderungen in {0}, @@ -3751,7 +3751,7 @@ This page keeps track of items you want to buy from sellers.,"Auf dieser Seite w This page keeps track of your items in which buyers have showed some interest.,"Diese Seite verfolgt Ihre Artikel, an denen Käufer Interesse gezeigt haben.", Thursday,Donnerstag, Title,Bezeichnung, -"To allow over billing, update ""Over Billing Allowance"" in Accounts Settings or the Item.","Aktualisieren Sie "Over Billing Allowance" in den Kontoeinstellungen oder im Artikel, um eine Überberechnung zuzulassen.", +"To allow over billing, update ""Over Billing Allowance"" in Accounts Settings or the Item.","Aktualisieren Sie "Over Billing Allowance" in den Buchhaltungseinstellungen oder im Artikel, um eine Überberechnung zuzulassen.", "To allow over receipt / delivery, update ""Over Receipt/Delivery Allowance"" in Stock Settings or the Item.","Um eine Überbestätigung / Überlieferung zu ermöglichen, aktualisieren Sie "Überbestätigung / Überlieferung" in den Lagereinstellungen oder im Artikel.", Total,Summe, Total Payment Request amount cannot be greater than {0} amount,Der Gesamtbetrag der Zahlungsanforderung darf nicht größer als {0} sein, @@ -4118,8 +4118,8 @@ Mandatory For Profit and Loss Account,Obligatorisch für Gewinn- und Verlustrech Accounting Period,Abrechnungszeitraum, Period Name,Zeitraumname, Closed Documents,Geschlossene Dokumente, -Accounts Settings,Konteneinstellungen, -Settings for Accounts,Konteneinstellungen, +Accounts Settings,Buchhaltungseinstellungen, +Settings for Accounts,Einstellungen für das Buchhaltungsmodul, Make Accounting Entry For Every Stock Movement,Eine Buchung für jede Lagerbewegung erstellen, Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,Benutzer mit dieser Rolle sind berechtigt Konten zu sperren und Buchungen zu gesperrten Konten zu erstellen/verändern, Determine Address Tax Category From,Adresssteuerkategorie bestimmen von, @@ -7622,7 +7622,7 @@ Template Title,Vorlagentitel, Journal Entry Type,Buchungssatz-Typ, Journal Entry Template Account,Buchungssatzvorlagenkonto, Process Deferred Accounting,Aufgeschobene Buchhaltung verarbeiten, -Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again,Manuelle Eingabe kann nicht erstellt werden! Deaktivieren Sie die automatische Eingabe für die verzögerte Buchhaltung in den Konteneinstellungen und versuchen Sie es erneut, +Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again,Manuelle Eingabe kann nicht erstellt werden! Deaktivieren Sie die automatische Eingabe für die verzögerte Buchhaltung in den Buchhaltungseinstellungen und versuchen Sie es erneut, End date cannot be before start date,Das Enddatum darf nicht vor dem Startdatum liegen, Total Counts Targeted,Gesamtzahl der anvisierten Zählungen, Total Counts Completed,Gesamtzahl der abgeschlossenen Zählungen, @@ -7884,7 +7884,7 @@ Checking this will automatically create a Sales Invoice whenever an appointment Healthcare Service Items,Artikel im Gesundheitswesen, "You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ",Sie können ein Serviceelement für die Gebühr für stationäre Besuche erstellen und hier festlegen. Ebenso können Sie in diesem Abschnitt andere Gesundheitsposten für die Abrechnung einrichten. Klicken, Set up default Accounts for the Healthcare Facility,Richten Sie Standardkonten für die Gesundheitseinrichtung ein, -"If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.","Wenn Sie die Standardkonteneinstellungen überschreiben und die Einkommens- und Debitorenkonten für das Gesundheitswesen konfigurieren möchten, können Sie dies hier tun.", +"If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.","Wenn Sie die Buchhaltungseinstellungen überschreiben und spezielle Einkommens- und Debitorenkonten für das Gesundheitswesen konfigurieren möchten, können Sie dies hier tun.", Out Patient SMS alerts,Out Patient SMS-Benachrichtigungen, "If you want to send SMS alert on Patient Registration, you can enable this option. Similary, you can set up Out Patient SMS alerts for other functionalities in this section. Click ","Wenn Sie bei der Patientenregistrierung eine SMS-Benachrichtigung senden möchten, können Sie diese Option aktivieren. Ebenso können Sie in diesem Abschnitt SMS-Benachrichtigungen für andere Patienten einrichten. Klicken", Admission Order Details,Details zur Zulassungsbestellung, @@ -8833,3 +8833,30 @@ Global Defaults,Allgemeine Voreinstellungen, Is Mandatory,Ist obligatorisch, WhatsApp,WhatsApp, Make a call,Einen Anruf tätigen, +Enable Automatic Party Matching,Automatisches Zuordnen von Parteien aktivieren, +Auto match and set the Party in Bank Transactions,"Partei automatisch anhand der Kontonummer bzw. IBAN zuordnen", +Enable Fuzzy Matching,Unscharfe Zuordnung aktivieren, +Approximately match the description/party name against parties,"Partei automatisch anhand grober Übereinstimmung des Names zuordnen" +Accounts Closing,Kontoabschluss, +Period Closing Settings,Periodenabschlusseinstellungen, +Ignore Account Closing Balance,Saldo des Kontos zum Periodenabschluss ignorieren, +Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing),"Finanzberichte werden anhand des Hauptbuchs erstellt (sollte aktiviert sein, wenn Periodenabschlüsse fehlen oder nicht sequentiell für alle Jahre gebucht werden)", +Asset Settings,Vermögenswerteinstellungen, +POS Setting,POS-Einstellungen, +Create Ledger Entries for Change Amount,Buchungssätze für Wechselgeld erstellen, +"If enabled, ledger entries will be posted for change amount in POS transactions","Wenn aktiviert, werden Buchungssätze für Wechselgeld in POS-Transaktionen erstellt", +Credit Limit Settings,Kreditlimit-Einstellungen, +Role Allowed to Over Bill, +Users with this role are allowed to over bill above the allowance percentage,"Roll, die mehr als den erlaubten Prozentsatz zusätzlich abrechnen darf", +Role allowed to bypass Credit Limit,"Rolle, die das Kreditlimit umgehen darf", +Invoice Cancellation,Rechnungsstornierung, +Delete Accounting and Stock Ledger Entries on deletion of Transaction,Beim Löschen einer Transaktion auch die entsprechenden Buchungs- und Lagerbuchungssätze löschen, +Enable Common Party Accounting,Verknüpfung von Kunden und Liefeanten erlauben, +Allow multi-currency invoices against single party account,Rechnungsbeträge in Fremdwährungen dürfen umgerechnet und in der Hauptwährung gebucht werden, +Enabling this will allow creation of multi-currency invoices against single party account in company currency,Bei Aktivierung können Rechnungen in Fremdwährungen gegen ein Konto in der Hauptwährung gebucht werden, +Payment Terms from orders will be fetched into the invoices as is,Die Zahlungsbedingungen aus einem Auftragen werden eins zu eins in die Rechnungen übernommen, +Automatically Fetch Payment Terms from Order,Zahlungsbedingungen aus Auftrag in die Rechnung übernehmen, +Enable Custom Cash Flow Format,Individuelles Cashflow-Format aktivieren, +Tax Settings,Umsatzsteuer-Einstellungen, +Book Tax Loss on Early Payment Discount,Umsatzsteueranteil bei Skonto berücksichtigen, +Split Early Payment Discount Loss into Income and Tax Loss,"Skontobetrag in Aufwand und Umsatzsteuerkorrektur aufteilen", From d6e4f80187023f404d6dd9f0e6f29d5e117a0970 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 27 Sep 2023 23:34:57 +0530 Subject: [PATCH 061/280] fix: Set `serial_and_batch_bundle` in right field for Asset Repair Stock Entry (#37273) fix: Set `serial_and_batch_bundle` in same field for Asset Repair SE - `serial_and_batch_bundle` was earlier set in `serial_no` field --- erpnext/assets/doctype/asset_repair/asset_repair.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 7e95cb2a1b..9c2b8bc963 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -177,7 +177,7 @@ class AssetRepair(AccountsController): "item_code": stock_item.item_code, "qty": stock_item.consumed_quantity, "basic_rate": stock_item.valuation_rate, - "serial_no": stock_item.serial_and_batch_bundle, + "serial_and_batch_bundle": stock_item.serial_and_batch_bundle, "cost_center": self.cost_center, "project": self.project, }, From f375c8cc7f3295b5a04ec8e389cec763758801b0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 23:35:53 +0530 Subject: [PATCH 062/280] fix: incorrect qty for material request in Production Plan (backport #37270) (#37275) fix: incorrect qty for material request in Production Plan (#37270) (cherry picked from commit 8fe4a4d3aa7857e5e241bcb7145065753ab8b4cb) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 27 ++++----- .../production_plan/test_production_plan.py | 58 +++++++++++++++++++ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index e88b791401..fabdafcec2 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1509,6 +1509,10 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d def get_materials_from_other_locations(item, warehouses, new_mr_items, company): from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations + stock_uom, purchase_uom = frappe.db.get_value( + "Item", item.get("item_code"), ["stock_uom", "purchase_uom"] + ) + locations = get_available_item_locations( item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True ) @@ -1519,6 +1523,10 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): if required_qty <= 0: return + conversion_factor = 1.0 + if purchase_uom != stock_uom and purchase_uom == item["uom"]: + conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) + new_dict = copy.deepcopy(item) quantity = required_qty if d.get("qty") > required_qty else d.get("qty") @@ -1531,25 +1539,14 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): } ) - required_qty -= quantity + required_qty -= quantity / conversion_factor new_mr_items.append(new_dict) # raise purchase request for remaining qty - if required_qty: - stock_uom, purchase_uom = frappe.db.get_value( - "Item", item["item_code"], ["stock_uom", "purchase_uom"] - ) - if purchase_uom != stock_uom and purchase_uom == item["uom"]: - conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) - if not (conversion_factor or frappe.flags.show_qty_in_stock_uom): - frappe.throw( - _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format( - purchase_uom, stock_uom, item["item_code"] - ) - ) - - required_qty = required_qty / conversion_factor + precision = frappe.get_precision("Material Request Plan Item", "quantity") + if flt(required_qty, precision) > 0: + required_qty = required_qty if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): required_qty = ceil(required_qty) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2348d2b688..5d54c41538 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1230,6 +1230,64 @@ class TestProductionPlan(FrappeTestCase): if row.item_code == "SubAssembly2 For SUB Test": self.assertEqual(row.quantity, 10) + def test_transfer_and_purchase_mrp_for_purchase_uom(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + bom_tree = { + "Test FG Item INK PEN": { + "Test RM Item INK": {}, + } + } + + parent_bom = create_nested_bom(bom_tree, prefix="") + if not frappe.db.exists("UOM Conversion Detail", {"parent": "Test RM Item INK", "uom": "Kg"}): + doc = frappe.get_doc("Item", "Test RM Item INK") + doc.purchase_uom = "Kg" + doc.append("uoms", {"uom": "Kg", "conversion_factor": 0.5}) + doc.save() + + wh1 = create_warehouse("PNE Warehouse", company="_Test Company") + wh2 = create_warehouse("MBE Warehouse", company="_Test Company") + mrp_warhouse = create_warehouse("MRPBE Warehouse", company="_Test Company") + + make_stock_entry( + item_code="Test RM Item INK", + qty=2, + rate=100, + target=wh1, + ) + + make_stock_entry( + item_code="Test RM Item INK", + qty=2, + rate=100, + target=wh2, + ) + + plan = create_production_plan( + item_code=parent_bom.item, + planned_qty=10, + do_not_submit=1, + warehouse="_Test Warehouse - _TC", + ) + + plan.for_warehouse = mrp_warhouse + + items = get_items_for_material_requests( + plan.as_dict(), warehouses=[{"warehouse": wh1}, {"warehouse": wh2}] + ) + + for row in items: + row = frappe._dict(row) + if row.material_request_type == "Material Transfer": + self.assertTrue(row.from_warehouse in [wh1, wh2]) + self.assertEqual(row.quantity, 2) + + if row.material_request_type == "Purchase": + self.assertTrue(row.warehouse == mrp_warhouse) + self.assertEqual(row.quantity, 12) + def create_production_plan(**args): """ From 2c7d6aec89327330be415b8c624c9272fb3d613b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Sep 2023 11:16:57 +0530 Subject: [PATCH 063/280] test: multi select party filter in AR report --- .../test_accounts_receivable.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 8c13f85a98..4307689158 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -605,3 +605,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): for field in expected: with self.subTest(field=field): self.assertEqual(report_output.get(field), expected.get(field)) + + def test_multi_select_party_filter(self): + self.customer1 = self.customer + self.create_customer("_Test Customer 2") + self.customer2 = self.customer + self.create_customer("_Test Customer 3") + self.customer3 = self.customer + + filters = { + "company": self.company, + "party_type": "Customer", + "party": [self.customer1, self.customer3], + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si1.customer = self.customer1 + si1.save().submit() + + si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si2.customer = self.customer2 + si2.save().submit() + + si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si3.customer = self.customer3 + si3.save().submit() + + # check invoice grand total and invoiced column's value for 3 payment terms + report = execute(filters) + + expected_output = {self.customer1, self.customer3} + self.assertEqual(len(report[1]), 2) + output_for = set([x.party for x in report[1]]) + self.assertEqual(output_for, expected_output) From d9eb44e62d9307f4100ca8e8b500309ca6da6922 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 11:12:19 +0530 Subject: [PATCH 064/280] fix: ageing summary in AR --- .../process_statement_of_accounts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 52ae9513dd..e758ee1729 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -102,7 +102,8 @@ def set_ageing(doc, entry): "range2": 60, "range3": 90, "range4": 120, - "customer": entry.customer, + "party_type": "Customer", + "party": entry.customer, } ) col1, ageing = get_ageing(ageing_filters) From d391e8150557004164a12666a5b5b251e7756042 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 29 Sep 2023 13:40:59 +0530 Subject: [PATCH 065/280] refactor: block Payment Entry as ref in JE from UI --- .../accounts/doctype/journal_entry/journal_entry.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index cdd1203d49..22b6880ad5 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -53,7 +53,15 @@ frappe.ui.form.on("Journal Entry", { erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm); }, - + before_save: function(frm) { + if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) { + let payment_entry_references = frm.doc.accounts.filter(elem => (elem.reference_type == "Payment Entry")); + if (payment_entry_references.length > 0) { + let rows = payment_entry_references.map(x => "#"+x.idx); + frappe.throw(__("Rows: {0} have 'Payment Entry' as reference_type. This should not be set manually.", [frappe.utils.comma_and(rows)])); + } + } + }, make_inter_company_journal_entry: function(frm) { var d = new frappe.ui.Dialog({ title: __("Select Company"), From 67f878ff8c49f0f2e3704aa7264b35fb3418a36e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 14:34:43 +0530 Subject: [PATCH 066/280] refactor: separate function for statement dict --- .../process_statement_of_accounts.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index e758ee1729..6c959ba2f0 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -48,6 +48,20 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): + statement_dict = get_statement_dict(doc) + if not bool(statement_dict): + return False + elif consolidated: + delimiter = '
' if doc.include_break else "" + result = delimiter.join(list(statement_dict.values())) + return get_pdf(result, {"orientation": doc.orientation}) + else: + for customer, statement_html in statement_dict.items(): + statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation}) + return statement_dict + + +def get_statement_dict(doc, get_statement_dict=False): statement_dict = {} ageing = "" @@ -78,18 +92,11 @@ def get_report_pdf(doc, consolidated=True): if not res: continue - statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing) + statement_dict[entry.customer] = ( + [res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing) + ) - if not bool(statement_dict): - return False - elif consolidated: - delimiter = '
' if doc.include_break else "" - result = delimiter.join(list(statement_dict.values())) - return get_pdf(result, {"orientation": doc.orientation}) - else: - for customer, statement_html in statement_dict.items(): - statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation}) - return statement_dict + return statement_dict def set_ageing(doc, entry): @@ -103,7 +110,7 @@ def set_ageing(doc, entry): "range3": 90, "range4": 120, "party_type": "Customer", - "party": entry.customer, + "party": [entry.customer], } ) col1, ageing = get_ageing(ageing_filters) From 644e25e587affcf2a5fb821f148e468b154c261a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 14:35:43 +0530 Subject: [PATCH 067/280] test: process soa for gl and ar --- .../test_process_statement_of_accounts.py | 100 +++++++++++++++--- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index fb0d8d152f..a3a74df402 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -4,39 +4,107 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, today from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( + get_statement_dict, send_emails, ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -class TestProcessStatementOfAccounts(unittest.TestCase): +class TestProcessStatementOfAccounts(AccountsTestMixin, FrappeTestCase): def setUp(self): + self.create_company() + self.create_customer() + self.create_customer(customer_name="Other Customer") + self.clear_old_entries() self.si = create_sales_invoice() - self.process_soa = create_process_soa() + create_sales_invoice(customer="Other Customer") + + def test_process_soa_for_gl(self): + """Tests the utils for Statement of Accounts(General Ledger)""" + process_soa = create_process_soa( + name="_Test Process SOA for GL", + customers=[{"customer": "_Test Customer"}, {"customer": "Other Customer"}], + ) + statement_dict = get_statement_dict(process_soa, get_statement_dict=True) + + # Checks if the statements are filtered based on the Customer + self.assertIn("Other Customer", statement_dict) + self.assertIn("_Test Customer", statement_dict) + + # Checks if the correct number of receivable entries exist + # 3 rows for opening and closing and 1 row for SI + receivable_entries = statement_dict["_Test Customer"][0] + self.assertEqual(len(receivable_entries), 4) + + # Checks the amount for the receivable entry + self.assertEqual(receivable_entries[1].voucher_no, self.si.name) + self.assertEqual(receivable_entries[1].balance, 100) + + def test_process_soa_for_ar(self): + """Tests the utils for Statement of Accounts(Accounts Receivable)""" + process_soa = create_process_soa(name="_Test Process SOA for AR", report="Accounts Receivable") + statement_dict = get_statement_dict(process_soa, get_statement_dict=True) + + # Checks if the statements are filtered based on the Customer + self.assertNotIn("Other Customer", statement_dict) + self.assertIn("_Test Customer", statement_dict) + + # Checks if the correct number of receivable entries exist + receivable_entries = statement_dict["_Test Customer"][0] + self.assertEqual(len(receivable_entries), 1) + + # Checks the amount for the receivable entry + self.assertEqual(receivable_entries[0].voucher_no, self.si.name) + self.assertEqual(receivable_entries[0].total_due, 100) + + # Checks the ageing summary for AR + ageing_summary = statement_dict["_Test Customer"][1][0] + expected_summary = frappe._dict( + range1=100, + range2=0, + range3=0, + range4=0, + range5=0, + ) + self.check_ageing_summary(ageing_summary, expected_summary) def test_auto_email_for_process_soa_ar(self): - send_emails(self.process_soa.name, from_scheduler=True) - self.process_soa.load_from_db() - self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7))) + process_soa = create_process_soa( + name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable" + ) + send_emails(process_soa.name, from_scheduler=True) + process_soa.load_from_db() + self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7))) + + def check_ageing_summary(self, ageing, expected_ageing): + for age_range in expected_ageing: + self.assertEqual(expected_ageing[age_range], ageing.get(age_range)) def tearDown(self): - frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + frappe.db.rollback() -def create_process_soa(): - frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") +def create_process_soa(**args): + args = frappe._dict(args) + frappe.delete_doc_if_exists("Process Statement Of Accounts", args.name) process_soa = frappe.new_doc("Process Statement Of Accounts") - soa_dict = { - "name": "Test Process SOA", - "company": "_Test Company", - } + soa_dict = frappe._dict( + name=args.name, + company=args.company or "_Test Company", + customers=args.customers or [{"customer": "_Test Customer"}], + enable_auto_email=1 if args.enable_auto_email else 0, + frequency=args.frequency or "Weekly", + report=args.report or "General Ledger", + from_date=args.from_date or getdate(today()), + to_date=args.to_date or getdate(today()), + posting_date=args.posting_date or getdate(today()), + include_ageing=1, + ) process_soa.update(soa_dict) - process_soa.set("customers", [{"customer": "_Test Customer"}]) - process_soa.enable_auto_email = 1 - process_soa.frequency = "Weekly" - process_soa.report = "Accounts Receivable" process_soa.save() return process_soa From 36364c235e69c681fd1afa779a0fd1fb92e56b08 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 29 Sep 2023 14:35:45 +0530 Subject: [PATCH 068/280] fix: Description field for the 'Ignore Available Stock' (#37293) (cherry picked from commit 7f1483ad707feac6413994400217413e5c167c7b) --- .../doctype/production_plan/production_plan.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 0d0fd5e270..4a0041662b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -228,7 +228,7 @@ }, { "default": "0", - "description": "If enabled, the system won't create material requests for the available items.", + "description": "If enabled, the system will create material requests even if the stock exists in the 'Raw Materials Warehouse'.", "fieldname": "ignore_existing_ordered_qty", "fieldtype": "Check", "label": "Ignore Available Stock" @@ -422,7 +422,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-28 13:37:43.926686", + "modified": "2023-09-29 11:41:03.246059", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", From 361e55511886d97a8b3b03a2d56a7220200c76a6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 29 Sep 2023 15:05:02 +0530 Subject: [PATCH 069/280] fix: Not unique table/alias: 'tabTask' (#37285) --- erpnext/projects/doctype/task/task.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 05a70c390d..25a5455ac1 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -60,7 +60,6 @@ "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Subject", "reqd": 1, @@ -140,7 +139,6 @@ "fieldname": "parent_task", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "label": "Parent Task", "options": "Task", "search_index": 1 @@ -398,7 +396,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2023-09-06 13:52:05.861175", + "modified": "2023-09-28 13:52:05.861175", "modified_by": "Administrator", "module": "Projects", "name": "Task", From ff1dc72d740f32d75deb7fa6294e70b488f146b2 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 29 Sep 2023 15:19:09 +0530 Subject: [PATCH 070/280] fix: Set right party name in bank transaction - If party name and docname are different, set the docname in Bank Transaction --- .../doctype/bank_transaction/auto_match_party.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py index 5d94a08f2f..671cde58a9 100644 --- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -1,6 +1,7 @@ from typing import Tuple, Union import frappe +from frappe.core.utils import find from frappe.utils import flt from rapidfuzz import fuzz, process @@ -112,7 +113,8 @@ class AutoMatchbyPartyNameDescription: for party in parties: filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} - names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name") + field = party.lower() + "_name" + names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"]) for field in ["bank_party_name", "description"]: if not self.get(field): @@ -131,12 +133,18 @@ class AutoMatchbyPartyNameDescription: def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]: skip = False - result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio) + result = process.extract( + query=self.get(field), + choices=[name.get("party_name") for name in names], + scorer=fuzz.token_set_ratio, + ) party_name, skip = self.process_fuzzy_result(result) if not party_name: return None, skip + # Get Party Docname from the list of dicts + party_name = find(names, lambda x: x["party_name"] == party_name).get("name") return ( party, party_name, From 1dab195560021347c2edfae4ffcedc93be647868 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 18:20:02 +0530 Subject: [PATCH 071/280] fix: add only float row values for total --- .../accounts_receivable_summary.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index cffc87895e..f89841523a 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -99,13 +99,11 @@ class AccountsReceivableSummary(ReceivablePayableReport): # Add all amount columns for k in list(self.party_total[d.party]): - if k not in ["currency", "sales_person"]: - - self.party_total[d.party][k] += d.get(k, 0.0) + if type(self.party_total[d.party][k]) == float: + self.party_total[d.party][k] += d.get(k) or 0.0 # set territory, customer_group, sales person etc self.set_party_details(d) - self.party_total[d.party].update({"party_type": d.party_type}) def init_party_total(self, row): self.party_total.setdefault( @@ -124,6 +122,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): "total_due": 0.0, "future_amount": 0.0, "sales_person": [], + "party_type": row.party_type, } ), ) @@ -133,13 +132,12 @@ class AccountsReceivableSummary(ReceivablePayableReport): for key in ("territory", "customer_group", "supplier_group"): if row.get(key): - self.party_total[row.party][key] = row.get(key) - + self.party_total[row.party][key] = row.get(key, "") if row.sales_person: - self.party_total[row.party].sales_person.append(row.sales_person) + self.party_total[row.party].sales_person.append(row.get("sales_person", "")) if self.filters.sales_partner: - self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner") + self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "") def get_columns(self): self.columns = [] From 25718f5cc758a30b6641e7c9be12f4386dc81a28 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 1 Oct 2023 13:27:44 +0530 Subject: [PATCH 072/280] chore: rewrite query using query builder --- .../crm/report/lead_details/lead_details.py | 77 ++++++++----------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py index 7b8c43b2d6..98dfbec18b 100644 --- a/erpnext/crm/report/lead_details/lead_details.py +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Concat_ws, Date def execute(filters=None): @@ -69,53 +70,41 @@ def get_columns(): def get_data(filters): - return frappe.db.sql( - """ - SELECT - `tabLead`.name, - `tabLead`.lead_name, - `tabLead`.status, - `tabLead`.lead_owner, - `tabLead`.territory, - `tabLead`.source, - `tabLead`.email_id, - `tabLead`.mobile_no, - `tabLead`.phone, - `tabLead`.owner, - `tabLead`.company, - concat_ws(', ', - trim(',' from `tabAddress`.address_line1), - trim(',' from tabAddress.address_line2) - ) AS address, - `tabAddress`.state, - `tabAddress`.pincode, - `tabAddress`.country - FROM - `tabLead` left join `tabDynamic Link` on ( - `tabLead`.name = `tabDynamic Link`.link_name and - `tabDynamic Link`.parenttype = 'Address') - left join `tabAddress` on ( - `tabAddress`.name=`tabDynamic Link`.parent) - WHERE - company = %(company)s - AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s - {conditions} - ORDER BY - `tabLead`.creation asc """.format( - conditions=get_conditions(filters) - ), - filters, - as_dict=1, + lead = frappe.qb.DocType("Lead") + address = frappe.qb.DocType("Address") + dynamic_link = frappe.qb.DocType("Dynamic Link") + + query = ( + frappe.qb.from_(lead) + .left_join(dynamic_link) + .on((lead.name == dynamic_link.link_name) & (dynamic_link.parenttype == "Address")) + .left_join(address) + .on(address.name == dynamic_link.parent) + .select( + lead.name, + lead.lead_name, + lead.status, + lead.lead_owner, + lead.territory, + lead.source, + lead.email_id, + lead.mobile_no, + lead.phone, + lead.owner, + lead.company, + (Concat_ws(", ", address.address_line1, address.address_line2)).as_("address"), + address.state, + address.pincode, + address.country, + ) + .where(lead.company == filters.company) + .where(Date(lead.creation).between(filters.from_date, filters.to_date)) ) - -def get_conditions(filters): - conditions = [] - if filters.get("territory"): - conditions.append(" and `tabLead`.territory=%(territory)s") + query = query.where(lead.territory == filters.get("territory")) if filters.get("status"): - conditions.append(" and `tabLead`.status=%(status)s") + query = query.where(lead.status == filters.get("status")) - return " ".join(conditions) if conditions else "" + return query.run(as_dict=1) From d3f94a03fc011b8a4ef380497a1e2bc4d54cea57 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 1 Oct 2023 14:04:34 +0200 Subject: [PATCH 073/280] fix(stock): add delivery user and manager role --- erpnext/setup/doctype/driver/driver.json | 18 +++++++++- erpnext/setup/doctype/vehicle/vehicle.json | 18 +++++++++- .../doctype/delivery_note/delivery_note.json | 30 ++++++++++++++++ .../delivery_settings/delivery_settings.json | 4 +-- .../doctype/delivery_trip/delivery_trip.json | 34 +++++++++++++++++-- 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/driver/driver.json b/erpnext/setup/doctype/driver/driver.json index 8d426cc29a..2e994b5ff9 100644 --- a/erpnext/setup/doctype/driver/driver.json +++ b/erpnext/setup/doctype/driver/driver.json @@ -157,6 +157,22 @@ "role": "HR Manager", "share": 1, "write": 1 + }, + { + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "write": 1 } ], "quick_entry": 1, @@ -166,4 +182,4 @@ "sort_order": "DESC", "title_field": "full_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/vehicle/vehicle.json b/erpnext/setup/doctype/vehicle/vehicle.json index ed803a763a..b19d45924f 100644 --- a/erpnext/setup/doctype/vehicle/vehicle.json +++ b/erpnext/setup/doctype/vehicle/vehicle.json @@ -860,6 +860,22 @@ "share": 1, "submit": 0, "write": 1 + }, + { + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "write": 1 } ], "quick_entry": 1, @@ -872,4 +888,4 @@ "title_field": "", "track_changes": 1, "track_seen": 0 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index e0d49192eb..b85f296d0b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1460,6 +1460,36 @@ "read": 1, "role": "Stock Manager", "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "submit": 1, + "write": 1 } ], "search_fields": "status,customer,customer_name, territory,base_grand_total", diff --git a/erpnext/stock/doctype/delivery_settings/delivery_settings.json b/erpnext/stock/doctype/delivery_settings/delivery_settings.json index 963403b8f2..ad0ac45851 100644 --- a/erpnext/stock/doctype/delivery_settings/delivery_settings.json +++ b/erpnext/stock/doctype/delivery_settings/delivery_settings.json @@ -239,7 +239,7 @@ "print": 1, "read": 1, "report": 0, - "role": "System Manager", + "role": "Delivery Manager", "set_user_permissions": 0, "share": 1, "submit": 0, @@ -255,4 +255,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json index 9d8fe46e8c..ec72af8404 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json @@ -188,7 +188,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2023-06-27 11:22:27.927637", + "modified": "2023-10-01 07:06:06.314503", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Trip", @@ -224,10 +224,40 @@ "share": 1, "submit": 1, "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Delivery Manager", + "share": 1, + "submit": 1, + "write": 1 } ], "sort_field": "modified", "sort_order": "DESC", "states": [], "title_field": "driver_name" -} \ No newline at end of file +} From e7f4b7b190ab2da7fcd7579abdb0ba7bbed17b65 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 1 Oct 2023 21:25:01 +0530 Subject: [PATCH 074/280] fix: ignore user permissions for `Source Warehouse` (#37313) --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 3 ++- erpnext/buying/doctype/purchase_order/purchase_order.json | 3 ++- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index f3c01816cc..e4898826ec 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1385,6 +1385,7 @@ "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Supplier Warehouse", "no_copy": 1, "options": "Warehouse", @@ -1593,7 +1594,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-09-21 12:22:04.545106", + "modified": "2023-10-01 21:01:47.282533", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 5b5cc2b021..f74df6630e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -477,6 +477,7 @@ "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Supplier Warehouse", "options": "Warehouse" }, @@ -1274,7 +1275,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-09-13 16:21:07.361700", + "modified": "2023-10-01 20:58:07.851037", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 912b9086dd..c8a9e3e82e 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -464,6 +464,7 @@ "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Supplier Warehouse", "no_copy": 1, "oldfieldname": "supplier_warehouse", @@ -1241,7 +1242,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-07-04 17:23:17.025390", + "modified": "2023-10-01 21:00:44.556816", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From fb51cae88b20274a836165a9b7aa70fe1a436ff4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 2 Oct 2023 16:16:31 +0530 Subject: [PATCH 075/280] chore: fix shopping cart tests --- .../shopping_cart/test_shopping_cart.py | 103 +++++++++++++----- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py index 951039db4f..8210f9743d 100644 --- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py +++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py @@ -17,7 +17,6 @@ from erpnext.e_commerce.shopping_cart.cart import ( request_for_quotation, update_cart, ) -from erpnext.tests.utils import create_test_contact_and_address class TestShoppingCart(unittest.TestCase): @@ -28,7 +27,6 @@ class TestShoppingCart(unittest.TestCase): def setUp(self): frappe.set_user("Administrator") - create_test_contact_and_address() self.enable_shopping_cart() if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}): make_website_item(frappe.get_cached_doc("Item", "_Test Item")) @@ -46,48 +44,57 @@ class TestShoppingCart(unittest.TestCase): frappe.db.sql("delete from `tabTax Rule`") def test_get_cart_new_user(self): - self.login_as_new_user() - + self.login_as_customer( + "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" + ) + create_address_and_contact( + address_title="_Test Address for Customer 2", + first_name="_Test Contact for Customer 2", + email="test_contact_two_customer@example.com", + customer="_Test Customer 2", + ) # test if lead is created and quotation with new lead is fetched - quotation = _get_cart_quotation() + customer = frappe.get_doc("Customer", "_Test Customer 2") + quotation = _get_cart_quotation(party=customer) self.assertEqual(quotation.quotation_to, "Customer") self.assertEqual( quotation.contact_person, - frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")), + frappe.db.get_value("Contact", dict(email_id="test_contact_two_customer@example.com")), ) self.assertEqual(quotation.contact_email, frappe.session.user) return quotation - def test_get_cart_customer(self): - def validate_quotation(): + def test_get_cart_customer(self, customer="_Test Customer 2"): + def validate_quotation(customer_name): # test if quotation with customer is fetched - quotation = _get_cart_quotation() + party = frappe.get_doc("Customer", customer_name) + quotation = _get_cart_quotation(party=party) self.assertEqual(quotation.quotation_to, "Customer") - self.assertEqual(quotation.party_name, "_Test Customer") + self.assertEqual(quotation.party_name, customer_name) self.assertEqual(quotation.contact_email, frappe.session.user) return quotation - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - validate_quotation() - - self.login_as_customer() - quotation = validate_quotation() - + quotation = validate_quotation(customer) return quotation def test_add_to_cart(self): - self.login_as_customer() - + self.login_as_customer( + "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" + ) + create_address_and_contact( + address_title="_Test Address for Customer 2", + first_name="_Test Contact for Customer 2", + email="test_contact_two_customer@example.com", + customer="_Test Customer 2", + ) # clear existing quotations self.clear_existing_quotations() # add first item update_cart("_Test Item", 1) - quotation = self.test_get_cart_customer() + quotation = self.test_get_cart_customer("_Test Customer 2") self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") self.assertEqual(quotation.get("items")[0].qty, 1) @@ -95,7 +102,7 @@ class TestShoppingCart(unittest.TestCase): # add second item update_cart("_Test Item 2", 1) - quotation = self.test_get_cart_customer() + quotation = self.test_get_cart_customer("_Test Customer 2") self.assertEqual(quotation.get("items")[1].item_code, "_Test Item 2") self.assertEqual(quotation.get("items")[1].qty, 1) self.assertEqual(quotation.get("items")[1].amount, 20) @@ -108,7 +115,7 @@ class TestShoppingCart(unittest.TestCase): # update first item update_cart("_Test Item", 5) - quotation = self.test_get_cart_customer() + quotation = self.test_get_cart_customer("_Test Customer 2") self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") self.assertEqual(quotation.get("items")[0].qty, 5) self.assertEqual(quotation.get("items")[0].amount, 50) @@ -121,7 +128,7 @@ class TestShoppingCart(unittest.TestCase): # remove first item update_cart("_Test Item", 0) - quotation = self.test_get_cart_customer() + quotation = self.test_get_cart_customer("_Test Customer 2") self.assertEqual(quotation.get("items")[0].item_code, "_Test Item 2") self.assertEqual(quotation.get("items")[0].qty, 1) @@ -132,7 +139,17 @@ class TestShoppingCart(unittest.TestCase): @unittest.skip("Flaky in CI") def test_tax_rule(self): self.create_tax_rule() - self.login_as_customer() + + self.login_as_customer( + "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" + ) + create_address_and_contact( + address_title="_Test Address for Customer 2", + first_name="_Test Contact for Customer 2", + email="test_contact_two_customer@example.com", + customer="_Test Customer 2", + ) + quotation = self.create_quotation() from erpnext.accounts.party import set_taxes @@ -320,7 +337,7 @@ class TestShoppingCart(unittest.TestCase): if frappe.db.exists("User", email): return - frappe.get_doc( + user = frappe.get_doc( { "doctype": "User", "user_type": "Website User", @@ -330,6 +347,40 @@ class TestShoppingCart(unittest.TestCase): } ).insert(ignore_permissions=True) + user.add_roles("Customer") + + +def create_address_and_contact(**kwargs): + if not frappe.db.get_value("Address", {"address_title": kwargs.get("address_title")}): + frappe.get_doc( + { + "doctype": "Address", + "address_title": kwargs.get("address_title"), + "address_type": kwargs.get("address_type") or "Office", + "address_line1": kwargs.get("address_line1") or "Station Road", + "city": kwargs.get("city") or "_Test City", + "state": kwargs.get("state") or "Test State", + "country": kwargs.get("country") or "India", + "links": [ + {"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"} + ], + } + ).insert() + + if not frappe.db.get_value("Contact", {"first_name": kwargs.get("first_name")}): + contact = frappe.get_doc( + { + "doctype": "Contact", + "first_name": kwargs.get("first_name"), + "links": [ + {"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"} + ], + } + ) + contact.add_email(kwargs.get("email") or "test_contact_customer@example.com", is_primary=True) + contact.add_phone(kwargs.get("phone") or "+91 0000000000", is_primary_phone=True) + contact.insert() + test_dependencies = [ "Sales Taxes and Charges Template", From fa8483fe8caf1bb1415c470affa0d4f5ee562178 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Mon, 2 Oct 2023 17:45:43 +0200 Subject: [PATCH 076/280] fix(portal): used rounded total in transaction rows, if enabled --- erpnext/templates/includes/transaction_row.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 72d498c998..a498ba0eef 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -15,10 +15,14 @@ {{ doc.items_preview }} - {% if doc.get('grand_total') %} + {% if doc.is_rounded_total_disabled() and doc.get('grand_total') %}
{{ doc.get_formatted("grand_total") }}
+ {% elif doc.get('rounded_total') %} +
+ {{ doc.get_formatted("rounded_total") }} +
{% endif %} Link From 087f378a4fea7044a331f5c174ec0103909825cb Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 3 Oct 2023 00:15:30 +0530 Subject: [PATCH 077/280] refactor: rename loan type to loan product in lending (#37325) refactor: rename loan type to loan product --- .../accounts/doctype/bank_clearance/test_bank_clearance.py | 4 ++-- .../doctype/bank_transaction/test_bank_transaction.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py index c7404d11bc..2a18830746 100644 --- a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py @@ -35,13 +35,13 @@ class TestBankClearance(unittest.TestCase): from lending.loan_management.doctype.loan.test_loan import ( create_loan, create_loan_accounts, - create_loan_type, + create_loan_product, create_repayment_entry, make_loan_disbursement_entry, ) def create_loan_masters(): - create_loan_type( + create_loan_product( "Clearance Loan", 2000000, 13.5, diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 0c328ff46c..2a504f6caf 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -410,7 +410,7 @@ def add_vouchers(): def create_loan_and_repayment(): from lending.loan_management.doctype.loan.test_loan import ( create_loan, - create_loan_type, + create_loan_product, create_repayment_entry, make_loan_disbursement_entry, ) @@ -420,7 +420,7 @@ def create_loan_and_repayment(): from erpnext.setup.doctype.employee.test_employee import make_employee - create_loan_type( + create_loan_product( "Personal Loan", 500000, 8.4, @@ -441,7 +441,7 @@ def create_loan_and_repayment(): "applicant_type": "Employee", "company": "_Test Company", "applicant": applicant, - "loan_type": "Personal Loan", + "loan_product": "Personal Loan", "loan_amount": 5000, "repayment_method": "Repay Fixed Amount per Period", "monthly_repayment_amount": 500, From fed94845ceca1a755cb997ec4a1f069b2f2a0534 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 3 Oct 2023 12:21:17 +0530 Subject: [PATCH 078/280] feat: asset salvage_value_percentage (#37302) * feat: asset salvage_value_percentage * chore: add missing parameter in get_item_details * chore: change asset depr table colors --- erpnext/assets/doctype/asset/asset.js | 31 +++++++++++++++++-- erpnext/assets/doctype/asset/asset.py | 12 +++++-- .../asset_activity/asset_activity.json | 5 ++- .../asset_finance_book.json | 8 ++++- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 962292b8ee..ba7940c5e2 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -239,7 +239,7 @@ frappe.ui.form.on('Asset', { datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem', 'margin-left': '0.35rem', 'margin-right': '0.35rem'}); datatable.style.setStyle(`.dt-header`, {'margin-left': '0.35rem', 'margin-right': '0.35rem'}); - datatable.style.setStyle(`.dt-cell--header`, {'color': 'var(--text-muted)'}); + datatable.style.setStyle(`.dt-cell--header .dt-cell__content`, {'color': 'var(--gray-600)', 'font-size': 'var(--text-sm)'}); datatable.style.setStyle(`.dt-cell`, {'color': 'var(--text-color)'}); datatable.style.setStyle(`.dt-cell--col-1`, {'text-align': 'center'}); datatable.style.setStyle(`.dt-cell--col-2`, {'font-weight': 600}); @@ -340,7 +340,8 @@ frappe.ui.form.on('Asset', { method: "erpnext.assets.doctype.asset.asset.get_item_details", args: { item_code: frm.doc.item_code, - asset_category: frm.doc.asset_category + asset_category: frm.doc.asset_category, + gross_purchase_amount: frm.doc.gross_purchase_amount }, callback: function(r, rt) { if(r.message) { @@ -546,7 +547,21 @@ frappe.ui.form.on('Asset', { } }); } - } + }, + + set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) { + if (expected_value_after_useful_life_changed) { + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true; + const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row)); + frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage); + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false; + } else if (salvage_value_percentage_changed) { + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true; + const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount')); + frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life); + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false; + } + }, }); frappe.ui.form.on('Asset Finance Book', { @@ -557,9 +572,19 @@ frappe.ui.form.on('Asset Finance Book', { expected_value_after_useful_life: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; + if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) { + frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true); + } frm.events.set_depreciation_rate(frm, row); }, + salvage_value_percentage: function(frm, cdt, cdn) { + const row = locals[cdt][cdn]; + if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) { + frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false); + } + }, + frequency_of_depreciation: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; frm.events.set_depreciation_rate(frm, row); diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 0dbed87cf2..2e69fe5d5c 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -198,7 +198,9 @@ class Asset(AccountsController): self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") if self.item_code and not self.get("finance_books"): - finance_books = get_item_details(self.item_code, self.asset_category) + finance_books = get_item_details( + self.item_code, self.asset_category, self.gross_purchase_amount + ) self.set("finance_books", finance_books) def validate_finance_books(self): @@ -797,7 +799,7 @@ def transfer_asset(args): @frappe.whitelist() -def get_item_details(item_code, asset_category): +def get_item_details(item_code, asset_category, gross_purchase_amount): asset_category_doc = frappe.get_doc("Asset Category", asset_category) books = [] for d in asset_category_doc.finance_books: @@ -807,7 +809,11 @@ def get_item_details(item_code, asset_category): "depreciation_method": d.depreciation_method, "total_number_of_depreciations": d.total_number_of_depreciations, "frequency_of_depreciation": d.frequency_of_depreciation, - "start_date": nowdate(), + "daily_depreciation": d.daily_depreciation, + "salvage_value_percentage": d.salvage_value_percentage, + "expected_value_after_useful_life": flt(gross_purchase_amount) + * flt(d.salvage_value_percentage / 100), + "depreciation_start_date": d.depreciation_start_date or nowdate(), } ) diff --git a/erpnext/assets/doctype/asset_activity/asset_activity.json b/erpnext/assets/doctype/asset_activity/asset_activity.json index 476fb2732e..00992e2cfc 100644 --- a/erpnext/assets/doctype/asset_activity/asset_activity.json +++ b/erpnext/assets/doctype/asset_activity/asset_activity.json @@ -75,13 +75,14 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-08-01 11:09:52.584482", + "modified": "2023-09-29 15:56:17.608643", "modified_by": "Administrator", "module": "Assets", "name": "Asset Activity", "owner": "Administrator", "permissions": [ { + "delete": 1, "email": 1, "read": 1, "report": 1, @@ -89,6 +90,7 @@ "share": 1 }, { + "delete": 1, "email": 1, "read": 1, "report": 1, @@ -96,6 +98,7 @@ "share": 1 }, { + "delete": 1, "email": 1, "read": 1, "report": 1, diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 4121302c1e..2c27dc9aca 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -12,6 +12,7 @@ "column_break_5", "frequency_of_depreciation", "depreciation_start_date", + "salvage_value_percentage", "expected_value_after_useful_life", "value_after_depreciation", "rate_of_depreciation" @@ -91,12 +92,17 @@ "fieldname": "daily_depreciation", "fieldtype": "Check", "label": "Daily Depreciation" + }, + { + "fieldname": "salvage_value_percentage", + "fieldtype": "Percent", + "label": "Salvage Value Percentage" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-10 22:10:36.576199", + "modified": "2023-09-29 15:39:52.740594", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", From 67440c38aef1f94432c0ec97f9bef065363d3a1b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 3 Oct 2023 17:38:43 +0530 Subject: [PATCH 079/280] refactor: use `isinstance` over `type` --- .../accounts_receivable_summary/accounts_receivable_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index f89841523a..60274cd8b1 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -99,7 +99,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): # Add all amount columns for k in list(self.party_total[d.party]): - if type(self.party_total[d.party][k]) == float: + if isinstance(self.party_total[d.party][k], float): self.party_total[d.party][k] += d.get(k) or 0.0 # set territory, customer_group, sales person etc From 8d99f9a12acfa97c724ca426dc1713ca152674e9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:32:34 +0530 Subject: [PATCH 080/280] fix: currency symbol in the Supplier Quotation Comparison report (backport #37337) (#37342) fix: currency symbol in the Supplier Quotation Comparison report (#37337) fix: currency in the Supplier Quotation Comparison report (cherry picked from commit 82e8606b3c4403d79556c896a98f942762cd8fa7) Co-authored-by: rohitwaghchaure --- .../supplier_quotation_comparison.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index a728290961..01ff28d810 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -35,8 +35,12 @@ def get_data(filters): sq_item.parent, sq_item.item_code, sq_item.qty, + sq.currency, sq_item.stock_qty, sq_item.amount, + sq_item.base_rate, + sq_item.base_amount, + sq.price_list_currency, sq_item.uom, sq_item.stock_uom, sq_item.request_for_quotation, @@ -105,7 +109,11 @@ def prepare_data(supplier_quotation_data, filters): "qty": data.get("qty"), "price": flt(data.get("amount") * exchange_rate, float_precision), "uom": data.get("uom"), + "price_list_currency": data.get("price_list_currency"), + "currency": data.get("currency"), "stock_uom": data.get("stock_uom"), + "base_amount": flt(data.get("base_amount"), float_precision), + "base_rate": flt(data.get("base_rate"), float_precision), "request_for_quotation": data.get("request_for_quotation"), "valid_till": data.get("valid_till"), "lead_time_days": data.get("lead_time_days"), @@ -183,6 +191,8 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): def get_columns(filters): + currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") + group_by_columns = [ { "fieldname": "supplier_name", @@ -203,11 +213,18 @@ def get_columns(filters): columns = [ {"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90}, {"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80}, + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "width": 110, + }, { "fieldname": "price", "label": _("Price"), "fieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "currency", "width": 110, }, { @@ -221,9 +238,23 @@ def get_columns(filters): "fieldname": "price_per_unit", "label": _("Price per Unit (Stock UOM)"), "fieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "currency", "width": 120, }, + { + "fieldname": "base_amount", + "label": _("Price ({0})").format(currency), + "fieldtype": "Currency", + "options": "price_list_currency", + "width": 180, + }, + { + "fieldname": "base_rate", + "label": _("Price Per Unit ({0})").format(currency), + "fieldtype": "Currency", + "options": "price_list_currency", + "width": 180, + }, { "fieldname": "quotation", "label": _("Supplier Quotation"), From 7729c1a413ef7b2eda28855bd4ad5895c3da8098 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:05:01 +0530 Subject: [PATCH 081/280] =?UTF-8?q?fix:=20do=20not=20consider=20submitted?= =?UTF-8?q?=20Work=20Orders=20in=20the=20Production=20Plan=20Res=E2=80=A6?= =?UTF-8?q?=20(backport=20#37343)=20(#37347)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: do not consider submitted Work Orders in the Production Plan Res… (#37343) fix: do not consider submitted Work Orders in the Production Plan Reserve qty (cherry picked from commit c3aeb2dec58190d16a18e2609fd57054bda54e43) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 43 ++++++++++++++++-- .../production_plan/test_production_plan.py | 45 +++++++++++++++++++ .../doctype/work_order/work_order.py | 10 ++++- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index fabdafcec2..deef020220 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -8,6 +8,7 @@ import json import frappe from frappe import _, msgprint from frappe.model.document import Document +from frappe.query_builder import Case from frappe.query_builder.functions import IfNull, Sum from frappe.utils import ( add_days, @@ -1617,18 +1618,33 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): table = frappe.qb.DocType("Production Plan") child = frappe.qb.DocType("Material Request Plan Item") + completed_production_plans = get_completed_production_plans() + + case = Case() query = ( frappe.qb.from_(table) .inner_join(child) .on(table.name == child.parent) - .select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0))) + .select( + Sum( + child.quantity + * IfNull( + case.when(child.material_request_type == "Purchase", child.conversion_factor).else_(1.0), 1.0 + ) + ) + ) .where( (table.docstatus == 1) & (child.item_code == item_code) & (child.warehouse == warehouse) & (table.status.notin(["Completed", "Closed"])) ) - ).run() + ) + + if completed_production_plans: + query = query.where(table.name.notin(completed_production_plans)) + + query = query.run() if not query: return 0.0 @@ -1636,7 +1652,9 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): reserved_qty_for_production_plan = flt(query[0][0]) reserved_qty_for_production = flt( - get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True) + get_reserved_qty_for_production( + item_code, warehouse, completed_production_plans, check_production_plan=True + ) ) if reserved_qty_for_production > reserved_qty_for_production_plan: @@ -1645,6 +1663,25 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): return reserved_qty_for_production_plan - reserved_qty_for_production +def get_completed_production_plans(): + table = frappe.qb.DocType("Production Plan") + child = frappe.qb.DocType("Production Plan Item") + + query = ( + frappe.qb.from_(table) + .inner_join(child) + .on(table.name == child.parent) + .select(table.name) + .where( + (table.docstatus == 1) + & (table.status.notin(["Completed", "Closed"])) + & (child.ordered_qty >= child.planned_qty) + ) + ).run(as_dict=True) + + return list(set([d.name for d in query])) + + def get_raw_materials_of_sub_assembly_items( item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1 ): diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 5d54c41538..47a89aa962 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -6,6 +6,7 @@ from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_completed_production_plans, get_items_for_material_requests, get_sales_orders, get_warehouse_list, @@ -1103,6 +1104,50 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_resered_qty_for_production_plan_for_less_rm_qty(self): + from erpnext.stock.utils import get_or_make_bin + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + pln = create_production_plan(item_code="Test Production Item 1", planned_qty=10) + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty - before_qty, 10) + + pln.make_work_order() + + plans = [] + for row in frappe.get_all("Work Order", filters={"production_plan": pln.name}, fields=["name"]): + wo_doc = frappe.get_doc("Work Order", row.name) + wo_doc.source_warehouse = "_Test Warehouse - _TC" + wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC" + wo_doc.fg_warehouse = "_Test Warehouse - _TC" + for d in wo_doc.required_items: + d.source_warehouse = "_Test Warehouse - _TC" + print(d.required_qty, "before") + d.required_qty -= 5 + make_stock_entry( + item_code=d.item_code, + qty=d.required_qty, + rate=100, + target="_Test Warehouse - _TC", + ) + + wo_doc.submit() + plans.append(pln.name) + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty, before_qty) + + completed_plans = get_completed_production_plans() + for plan in plans: + self.assertTrue(plan in completed_plans) + def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self): from erpnext.stock.utils import get_or_make_bin diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index d8fc220386..3dc33ac578 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -358,10 +358,10 @@ class WorkOrder(Document): else: self.update_work_order_qty_in_so() + self.update_ordered_qty() self.update_reserved_qty_for_production() self.update_completed_qty_in_material_request() self.update_planned_qty() - self.update_ordered_qty() self.create_job_card() def on_cancel(self): @@ -1513,7 +1513,10 @@ def create_pick_list(source_name, target_doc=None, for_qty=None): def get_reserved_qty_for_production( - item_code: str, warehouse: str, check_production_plan: bool = False + item_code: str, + warehouse: str, + completed_production_plans: list = None, + check_production_plan: bool = False, ) -> float: """Get total reserved quantity for any item in specified warehouse""" wo = frappe.qb.DocType("Work Order") @@ -1546,6 +1549,9 @@ def get_reserved_qty_for_production( if check_production_plan: query = query.where(wo.production_plan.isnotnull()) + if completed_production_plans: + query = query.where(wo.production_plan.notin(completed_production_plans)) + return query.run()[0][0] or 0.0 From 9be554a147ed3a0fbd242155661e48ec2376b480 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:11:10 +0530 Subject: [PATCH 082/280] chore: fix linter issue (backport #37349) (#37350) chore: fix linter issue (#37349) (cherry picked from commit e975a10a751cb1eb190ea7e06cc1eccca80554d2) Co-authored-by: rohitwaghchaure --- .../doctype/production_plan/test_production_plan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 47a89aa962..4ff9d29e0b 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1127,7 +1127,6 @@ class TestProductionPlan(FrappeTestCase): wo_doc.fg_warehouse = "_Test Warehouse - _TC" for d in wo_doc.required_items: d.source_warehouse = "_Test Warehouse - _TC" - print(d.required_qty, "before") d.required_qty -= 5 make_stock_entry( item_code=d.item_code, From 115f0242600a519209a7acd0e59fba434fd8c1c5 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:06:48 +0200 Subject: [PATCH 083/280] fix(translations): suggestions from review --- erpnext/translations/de.csv | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index b9e010add9..577ba87ea4 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -3345,7 +3345,7 @@ Cannot Calculate Arrival Time as Driver Address is Missing.,"Die Ankunftszeit ka Cannot Optimize Route as Driver Address is Missing.,"Route kann nicht optimiert werden, da die Fahreradresse fehlt.", Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.,"Aufgabe {0} kann nicht abgeschlossen werden, da die abhängige Aufgabe {1} nicht abgeschlossen / abgebrochen wurde.", Cannot find a matching Item. Please select some other value for {0}.,Ein passender Artikel kann nicht gefunden werden. Bitte einen anderen Wert für {0} auswählen., -"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings","Artikel {0} in Zeile {1} kann nicht mehr als {2} in Rechnung gestellt werden. Um eine Überberechnung zuzulassen, legen Sie die Überberechnung in den Buchhaltungseinstellungen fest", +"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings","Für Artikel {0} in Zeile {1} kann nicht mehr als {2} zusätzlich in Rechnung gestellt werden. Um diese Überfakturierung zuzulassen, passen Sie bitte die Grenzwerte in den Buchhaltungseinstellungen an.", "Capacity Planning Error, planned start time can not be same as end time","Kapazitätsplanungsfehler, die geplante Startzeit darf nicht mit der Endzeit übereinstimmen", Categories,Kategorien, Changes in {0},Änderungen in {0}, @@ -4119,7 +4119,7 @@ Accounting Period,Abrechnungszeitraum, Period Name,Zeitraumname, Closed Documents,Geschlossene Dokumente, Accounts Settings,Buchhaltungseinstellungen, -Settings for Accounts,Einstellungen für das Buchhaltungsmodul, +Settings for Accounts,Einstellungen für die Buchhaltung, Make Accounting Entry For Every Stock Movement,Eine Buchung für jede Lagerbewegung erstellen, Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,Benutzer mit dieser Rolle sind berechtigt Konten zu sperren und Buchungen zu gesperrten Konten zu erstellen/verändern, Determine Address Tax Category From,Adresssteuerkategorie bestimmen von, @@ -7884,7 +7884,7 @@ Checking this will automatically create a Sales Invoice whenever an appointment Healthcare Service Items,Artikel im Gesundheitswesen, "You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ",Sie können ein Serviceelement für die Gebühr für stationäre Besuche erstellen und hier festlegen. Ebenso können Sie in diesem Abschnitt andere Gesundheitsposten für die Abrechnung einrichten. Klicken, Set up default Accounts for the Healthcare Facility,Richten Sie Standardkonten für die Gesundheitseinrichtung ein, -"If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.","Wenn Sie die Buchhaltungseinstellungen überschreiben und spezielle Einkommens- und Debitorenkonten für das Gesundheitswesen konfigurieren möchten, können Sie dies hier tun.", +"If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.","Wenn Sie die Standardkonteneinstellungen überschreiben und die Einkommens- und Debitorenkonten für das Gesundheitswesen konfigurieren möchten, können Sie dies hier tun.", Out Patient SMS alerts,Out Patient SMS-Benachrichtigungen, "If you want to send SMS alert on Patient Registration, you can enable this option. Similary, you can set up Out Patient SMS alerts for other functionalities in this section. Click ","Wenn Sie bei der Patientenregistrierung eine SMS-Benachrichtigung senden möchten, können Sie diese Option aktivieren. Ebenso können Sie in diesem Abschnitt SMS-Benachrichtigungen für andere Patienten einrichten. Klicken", Admission Order Details,Details zur Zulassungsbestellung, @@ -8835,9 +8835,9 @@ WhatsApp,WhatsApp, Make a call,Einen Anruf tätigen, Enable Automatic Party Matching,Automatisches Zuordnen von Parteien aktivieren, Auto match and set the Party in Bank Transactions,"Partei automatisch anhand der Kontonummer bzw. IBAN zuordnen", -Enable Fuzzy Matching,Unscharfe Zuordnung aktivieren, -Approximately match the description/party name against parties,"Partei automatisch anhand grober Übereinstimmung des Names zuordnen" -Accounts Closing,Kontoabschluss, +Enable Fuzzy Matching,Fuzzy Matching aktivieren, +Approximately match the description/party name against parties,"Partei automatisch anhand grober Übereinstimmung des Namens zuordnen" +Accounts Closing,Kontenabschluss, Period Closing Settings,Periodenabschlusseinstellungen, Ignore Account Closing Balance,Saldo des Kontos zum Periodenabschluss ignorieren, Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing),"Finanzberichte werden anhand des Hauptbuchs erstellt (sollte aktiviert sein, wenn Periodenabschlüsse fehlen oder nicht sequentiell für alle Jahre gebucht werden)", @@ -8854,7 +8854,7 @@ Delete Accounting and Stock Ledger Entries on deletion of Transaction,Beim Lösc Enable Common Party Accounting,Verknüpfung von Kunden und Liefeanten erlauben, Allow multi-currency invoices against single party account,Rechnungsbeträge in Fremdwährungen dürfen umgerechnet und in der Hauptwährung gebucht werden, Enabling this will allow creation of multi-currency invoices against single party account in company currency,Bei Aktivierung können Rechnungen in Fremdwährungen gegen ein Konto in der Hauptwährung gebucht werden, -Payment Terms from orders will be fetched into the invoices as is,Die Zahlungsbedingungen aus einem Auftragen werden eins zu eins in die Rechnungen übernommen, +Payment Terms from orders will be fetched into the invoices as is,Zahlungsbedingungen aus Aufträgen werden eins zu eins in Rechnungen übernommen, Automatically Fetch Payment Terms from Order,Zahlungsbedingungen aus Auftrag in die Rechnung übernehmen, Enable Custom Cash Flow Format,Individuelles Cashflow-Format aktivieren, Tax Settings,Umsatzsteuer-Einstellungen, From f4f40cc776994a9fbeedd01dbe444fdc18ec5759 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:29:18 +0530 Subject: [PATCH 084/280] feat: composite WIP asset (copy #37352) (#37353) * feat: wip composite asset (cherry picked from commit 4907e7acd4ff549f34f6c9d0144af83db0ad9cc1) # Conflicts: # erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json # erpnext/assets/doctype/asset/asset.json # erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json * chore: resolving conflicts --------- Co-authored-by: anandbaburajan --- .../purchase_invoice/purchase_invoice.js | 6 + .../purchase_invoice_item.json | 9 +- erpnext/assets/doctype/asset/asset.js | 44 ++++- erpnext/assets/doctype/asset/asset.json | 29 +++- erpnext/assets/doctype/asset/asset.py | 11 +- erpnext/assets/doctype/asset/test_asset.py | 1 + .../asset_capitalization.js | 72 ++++++-- .../asset_capitalization.json | 36 ++-- .../asset_capitalization.py | 159 ++++++++++++++++-- .../test_asset_capitalization.py | 74 ++++++++ .../purchase_receipt/purchase_receipt.js | 6 + .../purchase_receipt_item.json | 9 +- 12 files changed, 404 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 095617dbcf..2eaa33767c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -479,6 +479,12 @@ cur_frm.set_query("expense_account", "items", function(doc) { } }); +cur_frm.set_query("wip_composite_asset", "items", function() { + return { + filters: {'is_composite_asset': 1, 'docstatus': 0 } + } +}); + cur_frm.cscript.expense_account = function(doc, cdt, cdn){ var d = locals[cdt][cdn]; if(d.idx == 1 && d.expense_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 3690142aac..424e942990 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -77,6 +77,7 @@ "manufacturer_part_no", "accounting", "expense_account", + "wip_composite_asset", "col_break5", "is_fixed_asset", "asset_location", @@ -903,12 +904,18 @@ "no_copy": 1, "options": "Serial and Batch Bundle", "print_hide": 1 + }, + { + "fieldname": "wip_composite_asset", + "fieldtype": "Link", + "label": "WIP Composite Asset", + "options": "Asset" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-26 12:54:53.178156", + "modified": "2023-10-03 21:01:01.824892", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index ba7940c5e2..5395f15e7a 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -148,6 +148,15 @@ frappe.ui.form.on('Asset', { if (frm.doc.docstatus == 0) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); + + if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) { + $('.primary-action').prop('hidden', true); + $('.form-message').text('Capitalize this asset to confirm'); + + frm.add_custom_button(__("Capitalize Asset"), function() { + frm.trigger("create_asset_capitalization"); + }); + } } }, @@ -169,7 +178,7 @@ frappe.ui.form.on('Asset', { frm.set_df_property('purchase_invoice', 'read_only', 1); frm.set_df_property('purchase_receipt', 'read_only', 1); } - else if (frm.doc.is_existing_asset) { + else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) { frm.toggle_reqd('purchase_receipt', 0); frm.toggle_reqd('purchase_invoice', 0); } @@ -353,7 +362,17 @@ frappe.ui.form.on('Asset', { is_existing_asset: function(frm) { frm.trigger("toggle_reference_doc"); - // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); + }, + + is_composite_asset: function(frm) { + if(frm.doc.is_composite_asset) { + frm.set_value('gross_purchase_amount', 0); + frm.set_df_property('gross_purchase_amount', 'read_only', 1); + } else { + frm.set_df_property('gross_purchase_amount', 'read_only', 0); + } + + frm.trigger("toggle_reference_doc"); }, make_sales_invoice: function(frm) { @@ -403,6 +422,19 @@ frappe.ui.form.on('Asset', { }); }, + create_asset_capitalization: function(frm) { + frappe.call({ + args: { + "asset": frm.doc.name, + }, + method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization", + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }, + split_asset: function(frm) { const title = __('Split Asset'); @@ -466,9 +498,11 @@ frappe.ui.form.on('Asset', { }, gross_purchase_amount: function(frm) { - frm.doc.finance_books.forEach(d => { - frm.events.set_depreciation_rate(frm, d); - }) + if (frm.doc.finance_books) { + frm.doc.finance_books.forEach(d => { + frm.events.set_depreciation_rate(frm, d); + }) + } }, purchase_receipt: (frm) => { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index befb5248d5..c7d08e2041 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -14,6 +14,7 @@ "asset_owner", "asset_owner_company", "is_existing_asset", + "is_composite_asset", "supplier", "customer", "image", @@ -72,7 +73,8 @@ "purchase_receipt_amount", "default_finance_book", "depr_entry_posting_status", - "amended_from" + "amended_from", + "capitalized_in" ], "fields": [ { @@ -199,7 +201,7 @@ "fieldtype": "Date", "label": "Purchase Date", "read_only": 1, - "read_only_depends_on": "eval:!doc.is_existing_asset", + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset", "reqd": 1 }, { @@ -237,10 +239,12 @@ "default": "0", "fieldname": "calculate_depreciation", "fieldtype": "Check", - "label": "Calculate Depreciation" + "label": "Calculate Depreciation", + "read_only_depends_on": "eval:doc.is_composite_asset && !doc.gross_purchase_amount" }, { "default": "0", + "depends_on": "eval:!doc.is_composite_asset", "fieldname": "is_existing_asset", "fieldtype": "Check", "label": "Is Existing Asset" @@ -478,7 +482,7 @@ "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", - "read_only_depends_on": "eval:!doc.is_existing_asset" + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset" }, { "fieldname": "depr_entry_posting_status", @@ -507,6 +511,21 @@ "fieldname": "is_fully_depreciated", "fieldtype": "Check", "label": "Is Fully Depreciated" + }, + { + "default": "0", + "depends_on": "eval:!doc.is_existing_asset", + "fieldname": "is_composite_asset", + "fieldtype": "Check", + "label": "Is Composite Asset" + }, + { + "fieldname": "capitalized_in", + "fieldtype": "Link", + "hidden": 1, + "label": "Capitalized In", + "options": "Asset Capitalization", + "read_only": 1 } ], "idx": 72, @@ -545,7 +564,7 @@ "table_fieldname": "accounts" } ], - "modified": "2023-07-28 20:12:44.819616", + "modified": "2023-10-03 23:28:26.732269", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 2e69fe5d5c..9d35634933 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -228,7 +228,7 @@ class Asset(AccountsController): if not self.asset_category: self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") - if not flt(self.gross_purchase_amount): + if not flt(self.gross_purchase_amount) and not self.is_composite_asset: frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) if is_cwip_accounting_enabled(self.asset_category): @@ -768,6 +768,15 @@ def create_asset_repair(asset, asset_name): return asset_repair +@frappe.whitelist() +def create_asset_capitalization(asset): + asset_capitalization = frappe.new_doc("Asset Capitalization") + asset_capitalization.update( + {"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"} + ) + return asset_capitalization + + @frappe.whitelist() def create_asset_value_adjustment(asset, asset_category, company): asset_value_adjustment = frappe.new_doc("Asset Value Adjustment") diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 39fcb21cdb..88ef69cddc 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1744,6 +1744,7 @@ def create_asset(**args): "location": args.location or "Test Location", "asset_owner": args.asset_owner or "Company", "is_existing_asset": args.is_existing_asset or 1, + "is_composite_asset": args.is_composite_asset or 0, "asset_quantity": args.get("asset_quantity") or 1, "depr_entry_posting_status": args.depr_entry_posting_status or "", } diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 6d55d7772b..be78d9ebdc 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -16,9 +16,15 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s refresh() { this.show_general_ledger(); + if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) { this.show_stock_ledger(); } + + if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") { + this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset); + this.get_target_asset_details(); + } } setup_queries() { @@ -35,18 +41,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s }); me.frm.set_query("target_asset", function() { - var filters = {}; - - if (me.frm.doc.target_item_code) { - filters['item_code'] = me.frm.doc.target_item_code; - } - - filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]]; - filters['docstatus'] = 1; - return { - filters: filters - }; + filters: {'is_composite_asset': 1, 'docstatus': 0 } + } }); me.frm.set_query("asset", "asset_items", function() { @@ -128,6 +125,39 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s return this.get_target_item_details(); } + target_asset() { + if (this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") { + this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset); + this.get_target_asset_details(); + } + } + + set_consumed_stock_items_tagged_to_wip_composite_asset(asset) { + var me = this; + + if (asset) { + return me.frm.call({ + method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset", + args: { + asset: asset, + }, + callback: function (r) { + if (!r.exc && r.message) { + me.frm.clear_table("stock_items"); + + for (let item of r.message) { + me.frm.add_child("stock_items", item); + } + + refresh_field("stock_items"); + + me.calculate_totals(); + } + } + }); + } + } + item_code(doc, cdt, cdn) { var row = frappe.get_doc(cdt, cdn); if (cdt === "Asset Capitalization Stock Item") { @@ -242,6 +272,26 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } } + get_target_asset_details() { + var me = this; + + if (me.frm.doc.target_asset) { + return me.frm.call({ + method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details", + child: me.frm.doc, + args: { + asset: me.frm.doc.target_asset, + company: me.frm.doc.company, + }, + callback: function (r) { + if (!r.exc) { + me.frm.refresh_fields(); + } + } + }); + } + } + get_consumed_stock_item_details(row) { var me = this; diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index 04b0c4e513..9ddc44212f 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -8,24 +8,25 @@ "engine": "InnoDB", "field_order": [ "title", + "company", "naming_series", "entry_type", - "target_item_code", - "target_asset", "target_item_name", "target_is_fixed_asset", "target_has_batch_no", "target_has_serial_no", "column_break_9", - "target_asset_name", + "capitalization_method", + "target_item_code", "target_asset_location", + "target_asset", + "target_asset_name", "target_warehouse", "target_qty", "target_stock_uom", "target_batch_no", "target_serial_no", "column_break_5", - "company", "finance_book", "posting_date", "posting_time", @@ -57,12 +58,13 @@ "label": "Title" }, { + "depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')", "fieldname": "target_item_code", "fieldtype": "Link", "in_standard_filter": 1, "label": "Target Item Code", - "options": "Item", - "reqd": 1 + "mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'", + "options": "Item" }, { "depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code", @@ -86,16 +88,18 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')", "fieldname": "target_asset", "fieldtype": "Link", "in_standard_filter": 1, "label": "Target Asset", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'", "no_copy": 1, "options": "Asset", - "read_only": 1 + "read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')" }, { - "depends_on": "eval:doc.entry_type=='Capitalization'", + "depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')", "fetch_from": "target_asset.asset_name", "fieldname": "target_asset_name", "fieldtype": "Data", @@ -186,12 +190,14 @@ }, { "default": "1", + "depends_on": "eval:doc.entry_type=='Decapitalization'", "fieldname": "target_qty", "fieldtype": "Float", "label": "Target Qty", "read_only_depends_on": "eval:doc.entry_type=='Capitalization'" }, { + "depends_on": "eval:doc.entry_type=='Decapitalization'", "fetch_from": "target_item_code.stock_uom", "fieldname": "target_stock_uom", "fieldtype": "Link", @@ -331,18 +337,26 @@ "read_only": 1 }, { - "depends_on": "eval:doc.entry_type=='Capitalization'", + "depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'", "fieldname": "target_asset_location", "fieldtype": "Link", "label": "Target Asset Location", - "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'", "options": "Location" + }, + { + "depends_on": "eval:doc.entry_type=='Capitalization'", + "fieldname": "capitalization_method", + "fieldtype": "Select", + "label": "Capitalization Method", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", + "options": "\nCreate a new composite asset\nChoose a WIP composite asset" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-22 14:17:07.995120", + "modified": "2023-10-03 22:55:59.461456", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization", diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 662e4b983b..0d6f6b4da1 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -53,6 +53,7 @@ class AssetCapitalization(StockController): self.validate_posting_time() self.set_missing_values(for_validate=True) self.validate_target_item() + self.validate_target_asset() self.validate_consumed_stock_item() self.validate_consumed_asset_item() self.validate_service_item() @@ -67,12 +68,12 @@ class AssetCapitalization(StockController): def before_submit(self): self.validate_source_mandatory() - if self.entry_type == "Capitalization": - self.create_target_asset() + self.create_target_asset() def on_submit(self): self.update_stock_ledger() self.make_gl_entries() + self.update_target_asset() def on_cancel(self): self.ignore_linked_doctypes = ( @@ -94,6 +95,11 @@ class AssetCapitalization(StockController): if self.meta.has_field(k) and (not self.get(k) or k in force_fields): self.set(k, v) + target_asset_details = get_target_asset_details(self.target_asset, self.company) + for k, v in target_asset_details.items(): + if self.meta.has_field(k) and (not self.get(k) or k in force_fields): + self.set(k, v) + for d in self.stock_items: args = self.as_dict() args.update(d.as_dict()) @@ -155,6 +161,33 @@ class AssetCapitalization(StockController): self.validate_item(target_item) + def validate_target_asset(self): + if self.target_asset: + target_asset = self.get_asset_for_validation(self.target_asset) + + if not target_asset.is_composite_asset: + frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name)) + + if target_asset.item_code != self.target_item_code: + frappe.throw( + _("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code) + ) + + if target_asset.status in ("Scrapped", "Sold", "Capitalized", "Decapitalized"): + frappe.throw( + _("Target Asset {0} cannot be {1}").format(target_asset.name, target_asset.status) + ) + + if target_asset.docstatus == 1: + frappe.throw(_("Target Asset {0} cannot be submitted").format(target_asset.name)) + elif target_asset.docstatus == 2: + frappe.throw(_("Target Asset {0} cannot be cancelled").format(target_asset.name)) + + if target_asset.company != self.company: + frappe.throw( + _("Target Asset {0} does not belong to company {1}").format(target_asset.name, self.company) + ) + def validate_consumed_stock_item(self): for d in self.stock_items: if d.item_code: @@ -179,7 +212,23 @@ class AssetCapitalization(StockController): ) asset = self.get_asset_for_validation(d.asset) - self.validate_asset(asset) + + if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"): + frappe.throw( + _("Row #{0}: Consumed Asset {1} cannot be {2}").format(d.idx, asset.name, asset.status) + ) + + if asset.docstatus == 0: + frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be Draft").format(d.idx, asset.name)) + elif asset.docstatus == 2: + frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be cancelled").format(d.idx, asset.name)) + + if asset.company != self.company: + frappe.throw( + _("Row #{0}: Consumed Asset {1} does not belong to company {2}").format( + d.idx, asset.name, self.company + ) + ) def validate_service_item(self): for d in self.service_items: @@ -214,21 +263,12 @@ class AssetCapitalization(StockController): def get_asset_for_validation(self, asset): return frappe.db.get_value( - "Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1 + "Asset", + asset, + ["name", "item_code", "company", "status", "docstatus", "is_composite_asset"], + as_dict=1, ) - def validate_asset(self, asset): - if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"): - frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status)) - - if asset.docstatus == 0: - frappe.throw(_("Asset {0} is Draft").format(asset.name)) - if asset.docstatus == 2: - frappe.throw(_("Asset {0} is cancelled").format(asset.name)) - - if asset.company != self.company: - frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company)) - @frappe.whitelist() def set_warehouse_details(self): for d in self.get("stock_items"): @@ -495,16 +535,25 @@ class AssetCapitalization(StockController): ) def create_target_asset(self): + if ( + self.entry_type != "Capitalization" + or self.capitalization_method != "Create a new composite asset" + ): + return + total_target_asset_value = flt(self.total_value, self.precision("total_value")) + asset_doc = frappe.new_doc("Asset") asset_doc.company = self.company asset_doc.item_code = self.target_item_code - asset_doc.is_existing_asset = 1 + asset_doc.is_composite_asset = 1 asset_doc.location = self.target_asset_location asset_doc.available_for_use_date = self.posting_date asset_doc.purchase_date = self.posting_date asset_doc.gross_purchase_amount = total_target_asset_value asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.flags.asset_created_via_asset_capitalization = True asset_doc.insert() @@ -528,6 +577,28 @@ class AssetCapitalization(StockController): ).format(get_link_to_form("Asset", asset_doc.name)) ) + def update_target_asset(self): + if ( + self.entry_type != "Capitalization" + or self.capitalization_method != "Choose a WIP composite asset" + ): + return + + total_target_asset_value = flt(self.total_value, self.precision("total_value")) + + asset_doc = frappe.get_doc("Asset", self.target_asset) + asset_doc.gross_purchase_amount = total_target_asset_value + asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.capitalized_in = self.name + asset_doc.flags.ignore_validate = True + asset_doc.save() + + frappe.msgprint( + _( + "Asset {0} has been updated. Please set the depreciation details if any and submit it." + ).format(get_link_to_form("Asset", asset_doc.name)) + ) + def restore_consumed_asset_items(self): for item in self.asset_items: asset = frappe.get_doc("Asset", item.asset) @@ -612,6 +683,33 @@ def get_target_item_details(item_code=None, company=None): return out +@frappe.whitelist() +def get_target_asset_details(asset=None, company=None): + out = frappe._dict() + + # Get Asset Details + asset_details = frappe._dict() + if asset: + asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1) + if not asset_details: + frappe.throw(_("Asset {0} does not exist").format(asset)) + + # Re-set item code from Asset + out.target_item_code = asset_details.item_code + + # Set Asset Details + out.asset_name = asset_details.asset_name + + if asset_details.item_code: + out.target_fixed_asset_account = get_asset_category_account( + "fixed_asset_account", item=asset_details.item_code, company=company + ) + else: + out.target_fixed_asset_account = None + + return out + + @frappe.whitelist() def get_consumed_stock_item_details(args): if isinstance(args, str): @@ -760,3 +858,30 @@ def get_service_item_details(args): ) return out + + +@frappe.whitelist() +def get_items_tagged_to_wip_composite_asset(asset): + fields = [ + "item_code", + "item_name", + "batch_no", + "serial_no", + "stock_qty", + "stock_uom", + "warehouse", + "cost_center", + "qty", + "valuation_rate", + "amount", + ] + + pi_items = frappe.get_all( + "Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields + ) + + pr_items = frappe.get_all( + "Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields + ) + + return pi_items + pr_items diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 6e0a6856f5..4a78595cb5 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -58,6 +58,7 @@ class TestAssetCapitalization(unittest.TestCase): # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( entry_type="Capitalization", + capitalization_method="Create a new composite asset", target_item_code="Macbook Pro", target_asset_location="Test Location", stock_qty=stock_qty, @@ -147,6 +148,7 @@ class TestAssetCapitalization(unittest.TestCase): # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( entry_type="Capitalization", + capitalization_method="Create a new composite asset", target_item_code="Macbook Pro", target_asset_location="Test Location", stock_qty=stock_qty, @@ -211,6 +213,77 @@ class TestAssetCapitalization(unittest.TestCase): self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + def test_capitalization_with_wip_composite_asset(self): + company = "_Test Company with perpetual inventory" + set_depreciation_settings_in_company(company=company) + + stock_rate = 1000 + stock_qty = 2 + stock_amount = 2000 + + total_amount = 2000 + + wip_composite_asset = create_asset( + asset_name="Asset Capitalization WIP Composite Asset", + is_composite_asset=1, + warehouse="Stores - TCP1", + company=company, + ) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization( + entry_type="Capitalization", + capitalization_method="Choose a WIP composite asset", + target_asset=wip_composite_asset, + target_asset_location="Test Location", + stock_qty=stock_qty, + stock_rate=stock_rate, + service_expense_account="Expenses Included In Asset Valuation - TCP1", + company=company, + submit=1, + ) + + # Test Asset Capitalization values + self.assertEqual(asset_capitalization.entry_type, "Capitalization") + self.assertEqual(asset_capitalization.capitalization_method, "Choose a WIP composite asset") + self.assertEqual(asset_capitalization.target_qty, 1) + + self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate) + self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount) + self.assertEqual(asset_capitalization.stock_items_total, stock_amount) + + self.assertEqual(asset_capitalization.total_value, total_amount) + self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) + + # Test Target Asset values + target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) + self.assertEqual(target_asset.gross_purchase_amount, total_amount) + self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + + # Test General Ledger Entries + expected_gle = { + "_Test Fixed Asset - TCP1": 2000, + "_Test Warehouse - TCP1": -2000, + } + actual_gle = get_actual_gle_dict(asset_capitalization.name) + + self.assertEqual(actual_gle, expected_gle) + + # Test Stock Ledger Entries + expected_sle = { + ("Capitalization Source Stock Item", "_Test Warehouse - TCP1"): { + "actual_qty": -stock_qty, + "stock_value_difference": -stock_amount, + } + } + actual_sle = get_actual_sle_dict(asset_capitalization.name) + self.assertEqual(actual_sle, expected_sle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + def test_decapitalization_with_depreciation(self): # Variables purchase_date = "2020-01-01" @@ -347,6 +420,7 @@ def create_asset_capitalization(**args): asset_capitalization.update( { "entry_type": args.entry_type or "Capitalization", + "capitalization_method": args.capitalization_method or None, "company": company, "posting_date": args.posting_date or now.strftime("%Y-%m-%d"), "posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"), diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 2bb479b8bc..6552cd7fce 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -37,6 +37,12 @@ frappe.ui.form.on("Purchase Receipt", { } }); + frm.set_query("wip_composite_asset", "items", function() { + return { + filters: {'is_composite_asset': 1, 'docstatus': 0 } + } + }); + frm.set_query("taxes_and_charges", function() { return { filters: {'company': frm.doc.company } diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 5eb3656c00..d7419dcc37 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -117,6 +117,7 @@ "accounting_details_section", "expense_account", "item_tax_rate", + "wip_composite_asset", "column_break_102", "provisional_expense_account", "accounting_dimensions_section", @@ -1056,12 +1057,18 @@ "fieldname": "add_serial_batch_bundle", "fieldtype": "Button", "label": "Add Serial / Batch No" + }, + { + "fieldname": "wip_composite_asset", + "fieldtype": "Link", + "label": "WIP Composite Asset", + "options": "Asset" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-11 16:16:16.504549", + "modified": "2023-10-03 21:11:50.547261", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 9f33cc5cf3f273df8c4a4c76721f61596fca633d Mon Sep 17 00:00:00 2001 From: "Jignesh (GreyCube Technologies)" Date: Wed, 4 Oct 2023 13:10:27 +0530 Subject: [PATCH 085/280] fix(Employee): enable `no_copy` for `relieving_date` (#37344) Co-authored-by: Rucha Mahabal --- erpnext/setup/doctype/employee/employee.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 6cb4292226..1143ccb7b1 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -616,6 +616,7 @@ "fieldname": "relieving_date", "fieldtype": "Date", "label": "Relieving Date", + "no_copy": 1, "mandatory_depends_on": "eval:doc.status == \"Left\"", "oldfieldname": "relieving_date", "oldfieldtype": "Date" @@ -822,7 +823,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2023-03-30 15:57:05.174592", + "modified": "2023-10-04 10:57:05.174592", "modified_by": "Administrator", "module": "Setup", "name": "Employee", @@ -870,4 +871,4 @@ "sort_order": "DESC", "states": [], "title_field": "employee_name" -} \ No newline at end of file +} From 9468513d7cf692624127d235129589b94a58d4b5 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Wed, 4 Oct 2023 22:11:15 +0530 Subject: [PATCH 086/280] test: fixing test_capitalization_with_wip_composite_asset --- .../doctype/asset_capitalization/test_asset_capitalization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 4a78595cb5..ac7c90d9e6 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -234,7 +234,7 @@ class TestAssetCapitalization(unittest.TestCase): asset_capitalization = create_asset_capitalization( entry_type="Capitalization", capitalization_method="Choose a WIP composite asset", - target_asset=wip_composite_asset, + target_asset=wip_composite_asset.name, target_asset_location="Test Location", stock_qty=stock_qty, stock_rate=stock_rate, From 81591a34c29977f78825a687a6cf206a4eb7855a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 4 Oct 2023 15:23:29 +0530 Subject: [PATCH 087/280] refactor: introduce access_key field --- .../currency_exchange_settings.json | 9 ++++++++- .../currency_exchange_settings.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json index c62b711f2c..df232a5848 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json @@ -9,6 +9,7 @@ "disabled", "service_provider", "api_endpoint", + "access_key", "url", "column_break_3", "help", @@ -84,12 +85,18 @@ "fieldname": "disabled", "fieldtype": "Check", "label": "Disabled" + }, + { + "depends_on": "eval:doc.service_provider == 'exchangerate.host';", + "fieldname": "access_key", + "fieldtype": "Data", + "label": "Access Key" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-01-09 12:19:03.955906", + "modified": "2023-10-04 15:30:25.333860", "modified_by": "Administrator", "module": "Accounts", "name": "Currency Exchange Settings", diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py index d618c5ca11..117d5ff21e 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -18,11 +18,21 @@ class CurrencyExchangeSettings(Document): def set_parameters_and_result(self): if self.service_provider == "exchangerate.host": + + if not self.access_key: + frappe.throw( + _("Access Key is required for Service Provider: {0}").format( + frappe.bold(self.service_provider) + ) + ) + self.set("result_key", []) self.set("req_params", []) self.api_endpoint = "https://api.exchangerate.host/convert" self.append("result_key", {"key": "result"}) + self.append("req_params", {"key": "access_key", "value": self.access_key}) + self.append("req_params", {"key": "amount", "value": "1"}) self.append("req_params", {"key": "date", "value": "{transaction_date}"}) self.append("req_params", {"key": "from", "value": "{from_currency}"}) self.append("req_params", {"key": "to", "value": "{to_currency}"}) From c8e3dc6c4c6dbf248670fd553b39f6ba69232c2c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 4 Oct 2023 17:18:26 +0530 Subject: [PATCH 088/280] chore: refactor test case for exchangerate.host provider --- .../setup/doctype/currency_exchange/test_currency_exchange.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 3b48c2b312..8477984c3c 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -121,6 +121,7 @@ class TestCurrencyExchange(unittest.TestCase): # Update Currency Exchange Rate settings = frappe.get_single("Currency Exchange Settings") settings.service_provider = "exchangerate.host" + settings.access_key = "12345667890" settings.save() # Update exchange From f388864fd5a3ce95e0349d7fd37fb3878834262c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 5 Oct 2023 13:13:11 +0530 Subject: [PATCH 089/280] fix: fetch company details for Lead based quotation --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 105c58d110..e897ba41eb 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -379,7 +379,7 @@ def get_lead_details(lead, posting_date=None, company=None): } ) - set_address_details(out, lead, "Lead") + set_address_details(out, lead, "Lead", company=company) taxes_and_charges = set_taxes( None, From d2aa4d5d6bfed8bc0dd91f67d33f574cdef8e1ad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 18:13:23 +0530 Subject: [PATCH 090/280] fix: validation message for valuation rate (backport #37301) (#37351) fix: validation message for valuation rate (#37301) (cherry picked from commit 643bb0511ce6b858d84613d08b551b7f15c6364a) Co-authored-by: rohitwaghchaure --- erpnext/controllers/selling_controller.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 901466267b..418a56f5fe 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -288,7 +288,9 @@ class SellingController(StockController): last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1) if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom): - throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate") + throw_message( + item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate (Moving Average)" + ) def get_item_list(self): il = [] From 4b4efbc7a699b27b79ef4fb148066bca68575004 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:55:29 +0200 Subject: [PATCH 091/280] refactor: migrate translations to HRMS --- erpnext/translations/af.csv | 7 ----- erpnext/translations/am.csv | 7 ----- erpnext/translations/ar.csv | 7 ----- erpnext/translations/bg.csv | 7 ----- erpnext/translations/bn.csv | 7 ----- erpnext/translations/bs.csv | 7 ----- erpnext/translations/ca.csv | 7 ----- erpnext/translations/cs.csv | 7 ----- erpnext/translations/da.csv | 7 ----- erpnext/translations/de.csv | 9 +----- erpnext/translations/el.csv | 7 ----- erpnext/translations/es.csv | 7 ----- erpnext/translations/et.csv | 7 ----- erpnext/translations/fa.csv | 7 ----- erpnext/translations/fi.csv | 7 ----- erpnext/translations/fr.csv | 7 ----- erpnext/translations/gu.csv | 7 ----- erpnext/translations/he.csv | 7 ----- erpnext/translations/hi.csv | 7 ----- erpnext/translations/hr.csv | 7 ----- erpnext/translations/hu.csv | 7 ----- erpnext/translations/id.csv | 7 ----- erpnext/translations/is.csv | 7 ----- erpnext/translations/it.csv | 7 ----- erpnext/translations/ja.csv | 7 ----- erpnext/translations/km.csv | 7 ----- erpnext/translations/kn.csv | 7 ----- erpnext/translations/ko.csv | 7 ----- erpnext/translations/ku.csv | 7 ----- erpnext/translations/lo.csv | 7 ----- erpnext/translations/lt.csv | 7 ----- erpnext/translations/lv.csv | 7 ----- erpnext/translations/mk.csv | 7 ----- erpnext/translations/ml.csv | 7 ----- erpnext/translations/mr.csv | 7 ----- erpnext/translations/ms.csv | 7 ----- erpnext/translations/my.csv | 7 ----- erpnext/translations/nl.csv | 7 ----- erpnext/translations/no.csv | 7 ----- erpnext/translations/pl.csv | 7 ----- erpnext/translations/ps.csv | 7 ----- erpnext/translations/pt-BR.csv | 7 ----- erpnext/translations/pt.csv | 7 ----- erpnext/translations/ro.csv | 7 ----- erpnext/translations/ru.csv | 7 ----- erpnext/translations/rw.csv | 7 ----- erpnext/translations/si.csv | 7 ----- erpnext/translations/sk.csv | 7 ----- erpnext/translations/sl.csv | 7 ----- erpnext/translations/sq.csv | 7 ----- erpnext/translations/sr-SP.csv | 51 ---------------------------------- erpnext/translations/sr.csv | 7 ----- erpnext/translations/sv.csv | 7 ----- erpnext/translations/sw.csv | 7 ----- erpnext/translations/ta.csv | 7 ----- erpnext/translations/te.csv | 7 ----- erpnext/translations/th.csv | 7 ----- erpnext/translations/tr.csv | 7 ----- erpnext/translations/uk.csv | 7 ----- erpnext/translations/ur.csv | 7 ----- erpnext/translations/uz.csv | 7 ----- erpnext/translations/vi.csv | 7 ----- erpnext/translations/zh-TW.csv | 4 --- erpnext/translations/zh.csv | 7 ----- 64 files changed, 1 insertion(+), 490 deletions(-) diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index 218bd692ba..3a34a4887a 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -286,7 +286,6 @@ Auto Repeat,Outo Herhaal, Auto repeat document updated,Outo-herhaal dokument opgedateer, Automotive,Automotive, Available,beskikbaar, -Available Leaves,Beskikbare blare, Available Qty,Beskikbare hoeveelheid, Available Selling,Beskikbaar verkoop, Available for use date is required,Beskikbaar vir gebruik datum is nodig, @@ -1122,7 +1121,6 @@ Hub Category,Hub Kategorie, Hub Sync ID,Hub-sinkronisasie-ID, Human Resource,Menslike hulpbronne, Human Resources,Menslike hulpbronne, -IFSC Code,IFSC-kode, IGST Amount,IGST Bedrag, IP Address,IP adres, ITC Available (whether in full op part),ITC beskikbaar (of dit volledig is), @@ -1666,7 +1664,6 @@ Organization Name,Organisasie Naam, Other,ander, Other Reports,Ander verslae, "Other outward supplies(Nil rated,Exempted)","Ander voorrade (nul beoordeel, vrygestel)", -Others,ander, Out Qty,Uit Aantal, Out Value,Uitwaarde, Out of Order,Buite werking, @@ -2812,7 +2809,6 @@ Total (Credit),Totaal (Krediet), Total (Without Tax),Totaal (Sonder Belasting), Total Achieved,Totaal behaal, Total Actual,Totaal Werklik, -Total Allocated Leaves,Totale toegekende blare, Total Amount,Totale bedrag, Total Amount Credited,Totale bedrag gekrediteer, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Totale Toepaslike Koste in Aankoopontvangste-items moet dieselfde wees as Totale Belasting en Heffings, @@ -2922,7 +2918,6 @@ Updating Variants...,Dateer variante op ..., Upload your letter head and logo. (you can edit them later).,Laai jou briefhoof en logo op. (jy kan dit later wysig)., Upper Income,Boonste Inkomste, Use Sandbox,Gebruik Sandbox, -Used Leaves,Gebruikte Blare, User,gebruiker, User ID,Gebruikers-ID, User ID not set for Employee {0},Gebruiker ID nie ingestel vir Werknemer {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Loonkostekoste, Approvers,Betogers, The first Approver in the list will be set as the default Approver.,Die eerste goedkeuring in die lys sal as die standaard goedkeuring gestel word., Shift Request Approver,Goedkeuring vir skofversoek, -PAN Number,PAN-nommer, Provident Fund Account,Voorsorgfondsrekening, MICR Code,MICR-kode, Repay unclaimed amount from salary,Betaal onopgeëiste bedrag terug van die salaris, Deduction from salary,Aftrekking van salaris, -Expired Leaves,Verlore blare, If this is not checked the loan by default will be considered as a Demand Loan,"As dit nie gekontroleer word nie, sal die lening by verstek as 'n vraaglening beskou word", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Hierdie rekening word gebruik om lenings van die lener terug te betaal en ook om lenings aan die lener uit te betaal, This account is capital account which is used to allocate capital for loan disbursal account ,Hierdie rekening is 'n kapitaalrekening wat gebruik word om kapitaal toe te ken vir die uitbetaling van lenings, diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv index 3ea1c78de3..958ebe12c5 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -286,7 +286,6 @@ Auto Repeat,ራስ ቀጥል, Auto repeat document updated,በቀጥታ ተዘምኗል, Automotive,አውቶሞቲቭ, Available,ይገኛል, -Available Leaves,የሚገኝ ቅጠሎች, Available Qty,ይገኛል ብዛት, Available Selling,ሊሸጥ የሚቻል, Available for use date is required,ለመጠቀም ቀን ሊገኝ ይችላል, @@ -1122,7 +1121,6 @@ Hub Category,Hub ምድብ, Hub Sync ID,የሃብ ማመሳሰል መታወቂያ, Human Resource,የሰው ኃይል, Human Resources,የሰው ሀይል አስተዳደር, -IFSC Code,የ IFSC ኮድ, IGST Amount,IGST ሂሳብ, IP Address,የአይፒ አድራሻ, ITC Available (whether in full op part),ITC አለ (በሙሉ ኦፕሬም ክፍል ውስጥም ቢሆን), @@ -1666,7 +1664,6 @@ Organization Name,የድርጅት ስም, Other,ሌላ, Other Reports,ሌሎች ሪፖርቶች, "Other outward supplies(Nil rated,Exempted)",ሌሎች የውጪ አቅርቦቶች (ኒል ደረጃ የተሰጠው ፣ ነፃ ...), -Others,ሌሎች, Out Qty,ብዛት ውጪ, Out Value,ውጪ ዋጋ, Out of Order,ከትዕዛዝ ውጪ, @@ -2812,7 +2809,6 @@ Total (Credit),ጠቅላላ (ምንጭ), Total (Without Tax),ጠቅላላ (ያለ ግብር), Total Achieved,ጠቅላላ አሳክቷል, Total Actual,ትክክለኛ ጠቅላላ, -Total Allocated Leaves,ጠቅላላ ድጐማዎችን, Total Amount,አጠቃላይ ድምሩ, Total Amount Credited,ጠቅላላ መጠን ተቀጠረ, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,የግዢ ደረሰኝ ንጥሎች ሰንጠረዥ ውስጥ ጠቅላላ የሚመለከታቸው ክፍያዎች ጠቅላላ ግብሮች እና ክፍያዎች እንደ አንድ አይነት መሆን አለበት, @@ -2922,7 +2918,6 @@ Updating Variants...,ተለዋጮችን ማዘመን ..., Upload your letter head and logo. (you can edit them later).,የእርስዎን ደብዳቤ ራስ እና አርማ ይስቀሉ. (ቆይተው አርትዕ ማድረግ ይችላሉ)., Upper Income,የላይኛው ገቢ, Use Sandbox,ይጠቀሙ ማጠሪያ, -Used Leaves,ጥቅም ላይ የዋሉ ቅጠሎች, User,ተጠቃሚው, User ID,የተጠቃሚው መለያ, User ID not set for Employee {0},የተጠቃሚ መታወቂያ ሰራተኛ ለ ካልተዋቀረ {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,የደመወዝ ክፍያ ዋጋ ማእከል, Approvers,አወዛጋቢ, The first Approver in the list will be set as the default Approver.,በዝርዝሩ ውስጥ የመጀመሪያው አጽዳቂ እንደ ነባሪው ማጽደቂያ ይቀመጣል።, Shift Request Approver,የሺፍት ጥያቄ ማጽደቅ, -PAN Number,የፓን ቁጥር, Provident Fund Account,የፕሮቪደንት ፈንድ ሂሳብ, MICR Code,MICR ኮድ, Repay unclaimed amount from salary,ከደመወዝ ያልተጠየቀውን መጠን ይክፈሉ, Deduction from salary,ከደመወዝ መቀነስ, -Expired Leaves,ጊዜው ያለፈባቸው ቅጠሎች, If this is not checked the loan by default will be considered as a Demand Loan,ይህ ካልተረጋገጠ ብድሩ በነባሪነት እንደ ብድር ይቆጠራል, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,ይህ ሂሳብ ከተበዳሪው የብድር ክፍያዎችን ለማስያዝ እና እንዲሁም ለተበዳሪው ብድሮችን ለማሰራጨት ያገለግላል, This account is capital account which is used to allocate capital for loan disbursal account ,ይህ አካውንት ለብድር ማስከፈያ ሂሳብ ካፒታል ለመመደብ የሚያገለግል የካፒታል ሂሳብ ነው, diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index 5858a25e4d..1d62b8a4ed 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -286,7 +286,6 @@ Auto Repeat,تكرار تلقائي, Auto repeat document updated,تكرار تلقائي للمستندات المحدثة, Automotive,سيارات, Available,متاح, -Available Leaves,المغادارت والاجازات المتاحة, Available Qty,الكمية المتاحة, Available Selling,المبيعات المتاحة, Available for use date is required,مطلوب تاريخ متاح للاستخدام, @@ -1122,7 +1121,6 @@ Hub Category,فئة المحور, Hub Sync ID,معرف مزامنة المحور, Human Resource,Human Resource, Human Resources,الموارد البشرية, -IFSC Code,رمز IFSC, IGST Amount,كمية IGST, IP Address,عنوان IP, ITC Available (whether in full op part),مركز التجارة الدولية متاح (سواء في جزء المرجع الكامل), @@ -1666,7 +1664,6 @@ Organization Name,اسم المنظمة, Other,آخر, Other Reports,تقارير أخرى, "Other outward supplies(Nil rated,Exempted)",اللوازم الخارجية الأخرى (بدون تقييم ، معفاة), -Others,بدلات أخرى, Out Qty,كمية خارجة, Out Value,القيمة الخارجه, Out of Order,خارج عن السيطرة, @@ -2812,7 +2809,6 @@ Total (Credit),الإجمالي (الائتمان), Total (Without Tax),الإجمالي (بدون ضريبة), Total Achieved,الإجمالي المحقق, Total Actual,الإجمالي الفعلي, -Total Allocated Leaves,مجموع الأوراق المخصصة, Total Amount,الاعتماد الأساسي, Total Amount Credited,مجموع المبلغ المعتمد, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,مجموع الرسوم المطبقة في شراء طاولة إيصال عناصر يجب أن يكون نفس مجموع الضرائب والرسوم, @@ -2922,7 +2918,6 @@ Updating Variants...,جارٍ تحديث المتغيرات ..., Upload your letter head and logo. (you can edit them later).,تحميل رئيس رسالتكم والشعار. (يمكنك تحريرها لاحقا)., Upper Income,أعلى دخل, Use Sandbox,استخدام ساندبوكس, -Used Leaves,مغادرات مستخدمة, User,المستعمل, User ID,تعريف المستخدم, User ID not set for Employee {0},هوية المستخدم لم يتم تعيين موظف ل {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,مركز تكلفة الرواتب, Approvers,الموافقون, The first Approver in the list will be set as the default Approver.,سيتم تعيين الموافق الأول في القائمة باعتباره الموافق الافتراضي., Shift Request Approver,الموافق على طلب التحول, -PAN Number,رقم PAN, Provident Fund Account,حساب صندوق الادخار, MICR Code,كود MICR, Repay unclaimed amount from salary,سداد المبلغ غير المطالب به من الراتب, Deduction from salary,خصم من الراتب, -Expired Leaves,أوراق منتهية الصلاحية, If this is not checked the loan by default will be considered as a Demand Loan,إذا لم يتم التحقق من ذلك ، فسيتم اعتبار القرض بشكل افتراضي كقرض تحت الطلب, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,يستخدم هذا الحساب لحجز أقساط سداد القرض من المقترض وأيضًا صرف القروض للمقترض, This account is capital account which is used to allocate capital for loan disbursal account ,هذا الحساب هو حساب رأس المال الذي يستخدم لتخصيص رأس المال لحساب صرف القرض, diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv index 8c94c5c97d..e2b8b13aeb 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -286,7 +286,6 @@ Auto Repeat,Автоматично повтаряне, Auto repeat document updated,Автоматичното повторение на документа е актуализиран, Automotive,автомобилен, Available,Наличен, -Available Leaves,Налични листа, Available Qty,В наличност Количество, Available Selling,Налични продажби, Available for use date is required,Необходима е дата за употреба, @@ -1122,7 +1121,6 @@ Hub Category,Категория хъб, Hub Sync ID,Идент, Human Resource,Човешки ресурси, Human Resources,Човешки ресурси, -IFSC Code,Кодекс на IFSC, IGST Amount,IGST Сума, IP Address,IP адрес, ITC Available (whether in full op part),Наличен ITC (независимо дали в пълната част), @@ -1666,7 +1664,6 @@ Organization Name,Наименование на организацията, Other,Друг, Other Reports,Други справки, "Other outward supplies(Nil rated,Exempted)","Други външни доставки (с нулева оценка, освободени)", -Others,Други, Out Qty,Изх. Количество, Out Value,Изх. стойност, Out of Order,Извънредно, @@ -2812,7 +2809,6 @@ Total (Credit),Общо (кредит), Total (Without Tax),Общо (без данъци), Total Achieved,Общо постигнати, Total Actual,Общо Край, -Total Allocated Leaves,Общо разпределени листа, Total Amount,Обща сума, Total Amount Credited,Общата сума е кредитирана, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Общо приложими такси в Покупка получаване артикули маса трябва да са същите, както Общо данъци и такси", @@ -2922,7 +2918,6 @@ Updating Variants...,Актуализиране на варианти ..., Upload your letter head and logo. (you can edit them later).,Качете ваш дизайн за заглавно писмо и лого. (Можете да ги редактирате по-късно)., Upper Income,Upper подоходно, Use Sandbox,Използвайте Sandbox, -Used Leaves,Използвани листа, User,потребител, User ID,User ID, User ID not set for Employee {0},User ID не е конфигуриран за служител {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Център за разходи за заплати, Approvers,Одобряващи, The first Approver in the list will be set as the default Approver.,Първият одобряващ в списъка ще бъде зададен като одобряващ по подразбиране., Shift Request Approver,Одобряващ заявка за смяна, -PAN Number,Номер на PAN, Provident Fund Account,Провиден фонд, MICR Code,MICR код, Repay unclaimed amount from salary,Изплатете непотърсена сума от заплата, Deduction from salary,Приспадане от заплата, -Expired Leaves,Листа с изтекъл срок на годност, If this is not checked the loan by default will be considered as a Demand Loan,"Ако това не е отметнато, заемът по подразбиране ще се счита за кредит за търсене", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Тази сметка се използва за резервиране на изплащане на заеми от кредитополучателя, както и за изплащане на заеми на кредитополучателя", This account is capital account which is used to allocate capital for loan disbursal account ,"Тази сметка е капиталова сметка, която се използва за разпределяне на капитал за сметка за оттегляне на заеми", diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv index 49617da960..d7b3744832 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -286,7 +286,6 @@ Auto Repeat,অটো পুনরাবৃত্তি, Auto repeat document updated,স্বতঃ পুনরাবৃত্ত নথি আপডেট করা হয়েছে, Automotive,স্বয়ংচালিত, Available,উপলভ্য, -Available Leaves,উপলব্ধ পাতা, Available Qty,প্রাপ্তিসাধ্য Qty, Available Selling,উপলভ্য বিক্রি, Available for use date is required,ব্যবহারের তারিখের জন্য উপলভ্য প্রয়োজন, @@ -1122,7 +1121,6 @@ Hub Category,হাব বিভাগ, Hub Sync ID,হাব সিঙ্ক আইডি, Human Resource,মানব সম্পদ, Human Resources,মানব সম্পদ, -IFSC Code,আইএফসিসি কোড, IGST Amount,IGST পরিমাণ, IP Address,আইপি ঠিকানা, ITC Available (whether in full op part),আইটিসি উপলব্ধ (সম্পূর্ণ বিকল্প অংশে থাকুক না কেন), @@ -1666,7 +1664,6 @@ Organization Name,প্রতিষ্ঠানের নাম, Other,অন্যান্য, Other Reports,অন্যান্য রিপোর্ট, "Other outward supplies(Nil rated,Exempted)","অন্যান্য বাহ্যিক সরবরাহ (নিল রেটড, অব্যাহতিপ্রাপ্ত)", -Others,অন্যরা, Out Qty,Qty আউট, Out Value,আউট মূল্য, Out of Order,অর্ডার আউট, @@ -2812,7 +2809,6 @@ Total (Credit),মোট (ক্রেডিট), Total (Without Tax),মোট (কর ছাড়), Total Achieved,মোট অর্জন, Total Actual,প্রকৃত মোট, -Total Allocated Leaves,মোট বরাদ্দ পাতা, Total Amount,মোট পরিমাণ, Total Amount Credited,মোট পরিমাণ কৃতিত্ব, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,ক্রয় রশিদ সামগ্রী টেবিলের মোট প্রযোজ্য চার্জ মোট কর ও চার্জ হিসাবে একই হতে হবে, @@ -2922,7 +2918,6 @@ Updating Variants...,বৈকল্পিকগুলি আপডেট কর Upload your letter head and logo. (you can edit them later).,আপনার চিঠি মাথা এবং লোগো আপলোড করুন. (আপনি তাদের পরে সম্পাদনা করতে পারেন)., Upper Income,আপার আয়, Use Sandbox,ব্যবহারের স্যান্ডবক্স, -Used Leaves,ব্যবহৃত পাখি, User,ব্যবহারকারী, User ID,ব্যবহারকারী আইডি, User ID not set for Employee {0},ইউজার আইডি কর্মচারী জন্য নির্ধারণ করে না {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,বেতন ব্যয় কেন্দ্র, Approvers,বিতর্ক, The first Approver in the list will be set as the default Approver.,তালিকার প্রথম অনুমোদিতটি ডিফল্ট অনুমোদনকারী হিসাবে সেট করা হবে।, Shift Request Approver,শিফট অনুরোধ অনুমোদনকারী, -PAN Number,প্যান নম্বর, Provident Fund Account,প্রভিডেন্ট ফান্ড অ্যাকাউন্ট, MICR Code,এমআইসিআর কোড, Repay unclaimed amount from salary,বেতন থেকে দায়হীন পরিমাণ পরিশোধ করুন ay, Deduction from salary,বেতন থেকে ছাড়, -Expired Leaves,মেয়াদ শেষ হয়ে গেছে, If this is not checked the loan by default will be considered as a Demand Loan,এটি যদি চেক না করা হয় তবে ডিফল্ট হিসাবে loanণকে ডিমান্ড anণ হিসাবে বিবেচনা করা হবে, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,এই অ্যাকাউন্টটি orণগ্রহীতা থেকে repণ পরিশোধের বুকিং এবং orণগ্রহীতাকে loansণ বিতরণের জন্য ব্যবহৃত হয়, This account is capital account which is used to allocate capital for loan disbursal account ,এই অ্যাকাউন্টটি মূলধন অ্যাকাউন্ট যা disণ বিতরণ অ্যাকাউন্টের জন্য মূলধন বরাদ্দ করতে ব্যবহৃত হয়, diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv index 08052282af..5040f83ef5 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Repeat, Auto repeat document updated,Automatsko ponavljanje dokumenta je ažurirano, Automotive,Automobilska industrija, Available,Dostupno, -Available Leaves,Raspoložive liste, Available Qty,Dostupno Količina, Available Selling,Dostupna prodaja, Available for use date is required,Potreban je datum upotrebe, @@ -1122,7 +1121,6 @@ Hub Category,Glavna kategorija, Hub Sync ID,Hub Sync ID, Human Resource,Human Resource, Human Resources,Ljudski resursi, -IFSC Code,IFSC kod, IGST Amount,IGST Iznos, IP Address,IP adresa, ITC Available (whether in full op part),Dostupan ITC (bilo u cjelini op. Dio), @@ -1666,7 +1664,6 @@ Organization Name,Naziv organizacije, Other,Drugi, Other Reports,Ostali izveštaji, "Other outward supplies(Nil rated,Exempted)","Ostale vanjske zalihe (Nil ocijenjeno, Izuzeti)", -Others,Drugi, Out Qty,Od kol, Out Value,out vrijednost, Out of Order,Ne radi, @@ -2812,7 +2809,6 @@ Total (Credit),Ukupno (kredit), Total (Without Tax),Ukupno (bez poreza), Total Achieved,Ukupno Ostvareni, Total Actual,Ukupno Actual, -Total Allocated Leaves,Ukupno izdvojene liste, Total Amount,Ukupan iznos, Total Amount Credited,Ukupan iznos kredita, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Ukupno Primjenjivo Optužbe na račun za prodaju Predmeti sto mora biti isti kao Ukupni porezi i naknada, @@ -2922,7 +2918,6 @@ Updating Variants...,Ažuriranje varijanti ..., Upload your letter head and logo. (you can edit them later).,Unos glavu pismo i logo. (Možete ih kasnije uređivanje)., Upper Income,Viši Prihodi, Use Sandbox,Koristite Sandbox, -Used Leaves,Korišćeni listovi, User,User, User ID,Korisnički ID, User ID not set for Employee {0},Korisnik ID nije postavljen za zaposlenika {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centar troškova troškova zarada, Approvers,Odobrivači, The first Approver in the list will be set as the default Approver.,Prvi odobravatelj na listi postavit će se kao zadani odobravatelj., Shift Request Approver,Odobrivač zahtjeva za smjenom, -PAN Number,PAN broj, Provident Fund Account,Račun osiguravajućeg fonda, MICR Code,MICR kod, Repay unclaimed amount from salary,Otplatite neiskorišteni iznos iz plate, Deduction from salary,Odbitak od plate, -Expired Leaves,Isteklo lišće, If this is not checked the loan by default will be considered as a Demand Loan,"Ako ovo nije potvrđeno, zajam će se prema zadanim postavkama smatrati zajmom na zahtjev", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ovaj račun koristi se za rezerviranje otplate zajma od zajmoprimca i za isplatu zajmova zajmoprimcu, This account is capital account which is used to allocate capital for loan disbursal account ,Ovaj račun je račun kapitala koji se koristi za alokaciju kapitala za račun izdvajanja kredita, diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv index ba108e220d..9789b0b69f 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -286,7 +286,6 @@ Auto Repeat,Repetició automàtica, Auto repeat document updated,S'ha actualitzat el document de repetició automàtica, Automotive,Automòbil, Available,Disponible, -Available Leaves,Fulles disponibles, Available Qty,Disponible Quantitat, Available Selling,Venda disponible, Available for use date is required,Disponible per a la data d'ús, @@ -1122,7 +1121,6 @@ Hub Category,Categoria de concentrador, Hub Sync ID,Identificador de sincronització del concentrador, Human Resource,Recursos humans, Human Resources,Recursos humans, -IFSC Code,Codi IFSC, IGST Amount,Import de l'IGST, IP Address,Adreça IP, ITC Available (whether in full op part),TIC disponible (en qualsevol part opcional), @@ -1666,7 +1664,6 @@ Organization Name,Nom de l'organització, Other,Un altre, Other Reports,Altres informes, "Other outward supplies(Nil rated,Exempted)","Altres subministraments externs (Nil, eximitat)", -Others,Altres, Out Qty,Quantitat de sortida, Out Value,Valor fora, Out of Order,No funciona, @@ -2812,7 +2809,6 @@ Total (Credit),Total (de crèdit), Total (Without Tax),Total (sense impostos), Total Achieved,Total aconseguit, Total Actual,Actual total, -Total Allocated Leaves,Total Allocated Leaves, Total Amount,Quantitat total, Total Amount Credited,Import total acreditat, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total d'comissions aplicables en la compra Taula de rebuts Els articles han de ser iguals que les taxes totals i càrrecs, @@ -2922,7 +2918,6 @@ Updating Variants...,S'estan actualitzant les variants ..., Upload your letter head and logo. (you can edit them later).,Puja el teu cap lletra i logotip. (Pots editar més tard)., Upper Income,Ingrés Alt, Use Sandbox,ús Sandbox, -Used Leaves,Fulles utilitzades, User,Usuari, User ID,ID d'usuari, User ID not set for Employee {0},ID d'usuari no entrat per l'Empleat {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centre de costos de nòmines, Approvers,Aprovadors, The first Approver in the list will be set as the default Approver.,El primer aprovador de la llista s'establirà com a aprovador predeterminat., Shift Request Approver,Aprovador de sol·licituds de torn, -PAN Number,Número PAN, Provident Fund Account,Compte de fons de previsió, MICR Code,Codi MICR, Repay unclaimed amount from salary,Reemborsar l’import no reclamat del salari, Deduction from salary,Deducció del salari, -Expired Leaves,Fulles caducades, If this is not checked the loan by default will be considered as a Demand Loan,"Si no es comprova això, el préstec per defecte es considerarà un préstec a la demanda", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Aquest compte s’utilitza per reservar els pagaments de préstecs del prestatari i també per desemborsar préstecs al prestatari, This account is capital account which is used to allocate capital for loan disbursal account ,Aquest compte és un compte de capital que s’utilitza per assignar capital per al compte de desemborsament del préstec, diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv index 4cd3ac5a48..c619b853b6 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto opakování, Auto repeat document updated,Dokument byl aktualizován automaticky, Automotive,Automobilový, Available,K dispozici, -Available Leaves,Dostupné listy, Available Qty,Množství k dispozici, Available Selling,Dostupné prodeje, Available for use date is required,K dispozici je datum k dispozici pro použití, @@ -1122,7 +1121,6 @@ Hub Category,Kategorie Hubu, Hub Sync ID,ID synchronizace Hubu, Human Resource,Lidské zdroje, Human Resources,Lidské zdroje, -IFSC Code,Kód IFSC, IGST Amount,IGST částka, IP Address,IP adresa, ITC Available (whether in full op part),ITC k dispozici (ať už v plné op části), @@ -1666,7 +1664,6 @@ Organization Name,Název organizace, Other,Ostatní, Other Reports,Ostatní zprávy, "Other outward supplies(Nil rated,Exempted)","Ostatní pasivní dodávky (bez hodnocení, osvobozeno)", -Others,Ostatní, Out Qty,Out Množství, Out Value,limitu, Out of Order,Mimo provoz, @@ -2812,7 +2809,6 @@ Total (Credit),Celkový (Credit), Total (Without Tax),Celkem (bez daně), Total Achieved,Celkem Dosažená, Total Actual,Celkem Aktuální, -Total Allocated Leaves,Celkové přidělené listy, Total Amount,Celková částka, Total Amount Credited,Celková částka připsána, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Celkový počet použitelných poplatcích v dokladu o koupi zboží, které tabulky musí být stejná jako celkem daní a poplatků", @@ -2922,7 +2918,6 @@ Updating Variants...,Aktualizace variant ..., Upload your letter head and logo. (you can edit them later).,Nahrajte svůj dopis hlavu a logo. (Můžete je upravit později)., Upper Income,Horní příjmů, Use Sandbox,použití Sandbox, -Used Leaves,Použité listy, User,Uživatel, User ID,User ID, User ID not set for Employee {0},User ID není nastavena pro zaměstnance {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Mzdové náklady, Approvers,Schvalovatelé, The first Approver in the list will be set as the default Approver.,První schvalovatel v seznamu bude nastaven jako výchozí schvalovatel., Shift Request Approver,Schvalovatel žádosti o změnu, -PAN Number,PAN číslo, Provident Fund Account,Účet fondu Provident, MICR Code,Kód MICR, Repay unclaimed amount from salary,Vrátit nevyzvednutou částku z platu, Deduction from salary,Srážka z platu, -Expired Leaves,Vypršela platnost listů, If this is not checked the loan by default will be considered as a Demand Loan,"Pokud to není zaškrtnuto, bude se úvěr ve výchozím nastavení považovat za půjčku na vyžádání", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Tento účet slouží k rezervaci splátek půjčky od dlužníka a také k vyplácení půjček dlužníkovi, This account is capital account which is used to allocate capital for loan disbursal account ,"Tento účet je kapitálovým účtem, který se používá k přidělení kapitálu pro účet vyplácení půjček", diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv index b89f39ccef..6d43829fa1 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -286,7 +286,6 @@ Auto Repeat,Automatisk gentagelse, Auto repeat document updated,Automatisk gentag dokument opdateret, Automotive,Bil, Available,Tilgængelig, -Available Leaves,Tilgængelige blade, Available Qty,Tilgængelig antal, Available Selling,Tilgængelig salg, Available for use date is required,Tilgængelig til brug dato er påkrævet, @@ -1122,7 +1121,6 @@ Hub Category,Nav kategori, Hub Sync ID,Hub Sync ID, Human Resource,Menneskelige ressourcer, Human Resources,Medarbejdere, -IFSC Code,IFSC-kode, IGST Amount,IGST Beløb, IP Address,IP-adresse, ITC Available (whether in full op part),ITC tilgængelig (uanset om det er i fuld op-del), @@ -1666,7 +1664,6 @@ Organization Name,Organisationens navn, Other,Andre, Other Reports,Andre rapporter, "Other outward supplies(Nil rated,Exempted)","Andre udgående leverancer (Nul bedømt, undtaget)", -Others,Andre, Out Qty,Out Antal, Out Value,Out Value, Out of Order,Virker ikke, @@ -2812,7 +2809,6 @@ Total (Credit),I alt (kredit), Total (Without Tax),I alt (uden skat), Total Achieved,Total Opnået, Total Actual,Samlede faktiske, -Total Allocated Leaves,Samlede tildelte blade, Total Amount,Samlet beløb, Total Amount Credited,Samlede beløb krediteret, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total gældende takster i købskvitteringsvaretabel skal være det samme som de samlede skatter og afgifter, @@ -2922,7 +2918,6 @@ Updating Variants...,Opdaterer varianter ..., Upload your letter head and logo. (you can edit them later).,Upload dit brevhoved og logo. (du kan redigere dem senere)., Upper Income,Upper Indkomst, Use Sandbox,Brug Sandbox, -Used Leaves,Brugte blade, User,Bruger, User ID,Bruger-id, User ID not set for Employee {0},Bruger-id ikke indstillet til Medarbejder {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Lønomkostningscenter, Approvers,Approverser, The first Approver in the list will be set as the default Approver.,Den første godkender på listen indstilles som standardgodkenderen., Shift Request Approver,Godkendelse af skiftanmodning, -PAN Number,PAN-nummer, Provident Fund Account,Provident Fund-konto, MICR Code,MICR-kode, Repay unclaimed amount from salary,Tilbagebetal ikke-krævet beløb fra løn, Deduction from salary,Fradrag fra løn, -Expired Leaves,Udløbne blade, If this is not checked the loan by default will be considered as a Demand Loan,"Hvis dette ikke er markeret, vil lånet som standard blive betragtet som et behovslån", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Denne konto bruges til at booke tilbagebetaling af lån fra låntager og også udbetale lån til låntager, This account is capital account which is used to allocate capital for loan disbursal account ,"Denne konto er en kapitalkonto, der bruges til at allokere kapital til udbetaling af lånekonto", diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 03e9de4c55..28749908ef 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -286,7 +286,6 @@ Auto Repeat,Automatische Wiederholung, Auto repeat document updated,Automatisches Wiederholungsdokument aktualisiert, Automotive,Fahrzeugbau, Available,Verfügbar, -Available Leaves,Verfügbare Blätter, Available Qty,Verfügbare Menge, Available Selling,Verfügbarer Verkauf, Available for use date is required,Verfügbar für das Nutzungsdatum ist erforderlich, @@ -1129,7 +1128,6 @@ Hub Category,Hub-Kategorie, Hub Sync ID,Hub-Synchronisierungs-ID, Human Resource,Personal, Human Resources,Personalwesen, -IFSC Code,IFSC-Code, IGST Amount,IGST Betrag, IP Address,IP Adresse, ITC Available (whether in full op part),ITC verfügbar (ob vollständig oder teilweise), @@ -1675,7 +1673,6 @@ Organization Name,Firmenname, Other,Andere, Other Reports,Weitere Berichte, "Other outward supplies(Nil rated,Exempted)","Sonstige Auslandslieferungen (ohne Rating, ausgenommen)", -Others,Andere, Out Qty,Ausgabe-Menge, Out Value,Out Wert, Out of Order,Außer Betrieb, @@ -2825,7 +2822,6 @@ Total (Credit),Insgesamt (Credit), Total (Without Tax),Summe (ohne Steuern), Total Achieved,Gesamtsumme erreicht, Total Actual,Summe Tatsächlich, -Total Allocated Leaves,Insgesamt zugeteilte Blätter, Total Amount,Gesamtsumme, Total Amount Credited,Gesamtbetrag der Gutschrift, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Gesamt Die Gebühren in Kauf Eingangspositionen Tabelle muss als Gesamt Steuern und Abgaben gleich sein, @@ -2935,7 +2931,6 @@ Updating Variants...,Varianten werden aktualisiert ..., Upload your letter head and logo. (you can edit them later).,Briefkopf und Logo hochladen. (Beides kann später noch bearbeitet werden.), Upper Income,Gehobenes Einkommen, Use Sandbox,Sandkastenmodus verwenden, -Used Leaves,Genutzter Urlaub, User,Nutzer, User ID,Benutzer-ID, User ID not set for Employee {0},Benutzer-ID ist für Mitarbeiter {0} nicht eingegeben, @@ -7966,12 +7961,10 @@ Payroll Cost Center,Lohnkostenstelle, Approvers,Genehmiger, The first Approver in the list will be set as the default Approver.,Der erste Genehmiger in der Liste wird als Standardgenehmiger festgelegt., Shift Request Approver,Schichtanforderungsgenehmiger, -PAN Number,PAN-Nummer, Provident Fund Account,Vorsorgefonds-Konto, MICR Code,MICR-Code, Repay unclaimed amount from salary,Nicht zurückgeforderten Betrag vom Gehalt zurückzahlen, Deduction from salary,Abzug vom Gehalt, -Expired Leaves,Abgelaufene Blätter, If this is not checked the loan by default will be considered as a Demand Loan,"Wenn dies nicht aktiviert ist, wird das Darlehen standardmäßig als Nachfragedarlehen betrachtet", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Dieses Konto wird zur Buchung von Kreditrückzahlungen vom Kreditnehmer und zur Auszahlung von Krediten an den Kreditnehmer verwendet, This account is capital account which is used to allocate capital for loan disbursal account ,"Dieses Konto ist ein Kapitalkonto, das zur Zuweisung von Kapital für das Darlehensauszahlungskonto verwendet wird", @@ -8670,7 +8663,7 @@ Please set default Cash or Bank account in Mode of Payment {},Bitte setzen Sie d Please set default Cash or Bank account in Mode of Payments {},Bitte setzen Sie das Standard-Bargeld- oder Bankkonto im Zahlungsmodus {}, Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.,"Bitte stellen Sie sicher, dass das Konto {} ein Bilanzkonto ist. Sie können das übergeordnete Konto in ein Bilanzkonto ändern oder ein anderes Konto auswählen.", Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.,"Bitte stellen Sie sicher, dass das Konto {} ein zahlbares Konto ist. Ändern Sie den Kontotyp in "Verbindlichkeiten" oder wählen Sie ein anderes Konto aus.", -"Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account","Zeile {0}: Aufwandskonto geändert zu {1}, weil das Konto {2} nicht mit dem Lager {3} verknüpft ist oder es nicht das Standard-Inventarkonto ist", +Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account,"Zeile {0}: Aufwandskonto geändert zu {1}, weil das Konto {2} nicht mit dem Lager {3} verknüpft ist oder es nicht das Standard-Inventarkonto ist", Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2},"Zeile {0}: Aufwandskonto geändert zu {1}, da dieses bereits in Eingangsbeleg {2} verwendet wurde", Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.,"Zeile {0}: Aufwandskonto geändert zu {1}, da kein Eingangsbeleg für Artikel {2} erstellt wird.", This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice,"Dies erfolgt zur Abrechnung von Fällen, in denen der Eingangsbeleg nach der Eingangsrechnung erstellt wird", diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv index 8d48c3a5a1..95977c621d 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -286,7 +286,6 @@ Auto Repeat,Αυτόματη επανάληψη, Auto repeat document updated,Το έγγραφο αυτόματης επανάληψης ενημερώθηκε, Automotive,Αυτοκίνητο, Available,Διαθέσιμος, -Available Leaves,Διαθέσιμα φύλλα, Available Qty,Διαθέσιμη ποσότητα, Available Selling,Διαθέσιμη πώληση, Available for use date is required,Απαιτείται ημερομηνία διαθέσιμη για χρήση, @@ -1122,7 +1121,6 @@ Hub Category,Κατηγορία Hub, Hub Sync ID,Αναγνωριστικό συγχρονισμού Hub, Human Resource,Ανθρώπινο δυναμικό, Human Resources,Ανθρώπινοι πόροι, -IFSC Code,Κωδικός IFSC, IGST Amount,Ποσό IGST, IP Address,Διεύθυνση IP, ITC Available (whether in full op part),ITC Διαθέσιμο (είτε σε πλήρη op μέρος), @@ -1666,7 +1664,6 @@ Organization Name,Όνομα Οργανισμού, Other,Άλλος, Other Reports,άλλες εκθέσεις, "Other outward supplies(Nil rated,Exempted)","Άλλες παροχές προς το εξωτερικό (μη διαβαθμισμένες, απαλλαγμένες)", -Others,Άλλα, Out Qty,Ποσότητα εκτός, Out Value,από Αξία, Out of Order,Εκτός λειτουργίας, @@ -2812,7 +2809,6 @@ Total (Credit),Συνολική (πίστωση), Total (Without Tax),Σύνολο (χωρίς Φόρο), Total Achieved,Σύνολο που επιτεύχθηκε, Total Actual,Πραγματικό σύνολο, -Total Allocated Leaves,Συνολικά κατανεμημένα φύλλα, Total Amount,Συνολικό ποσό, Total Amount Credited,Συνολικό ποσό που πιστώνεται, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Σύνολο χρεώσεων που επιβάλλονται στην Αγορά Παραλαβή Είδη πίνακα πρέπει να είναι ίδιο με το συνολικό φόροι και επιβαρύνσεις, @@ -2922,7 +2918,6 @@ Updating Variants...,Ενημέρωση παραλλαγών ..., Upload your letter head and logo. (you can edit them later).,Ανεβάστε την κεφαλίδα επιστολόχαρτου και το λογότυπό σας. (Μπορείτε να τα επεξεργαστείτε αργότερα)., Upper Income,Άνω εισοδήματος, Use Sandbox,χρήση Sandbox, -Used Leaves,Χρησιμοποιημένα φύλλα, User,Χρήστης, User ID,ID χρήστη, User ID not set for Employee {0},Το ID χρήστη δεν έχει οριστεί για τον υπάλληλο {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Κέντρο κόστους μισθοδοσίας, Approvers,Εγκρίνει, The first Approver in the list will be set as the default Approver.,Η πρώτη έγκριση στη λίστα θα οριστεί ως η προεπιλεγμένη έγκριση., Shift Request Approver,Έγκριση αιτήματος Shift, -PAN Number,Αριθμός PAN, Provident Fund Account,Λογαριασμός Ταμείου Προνοίας, MICR Code,Κωδικός MICR, Repay unclaimed amount from salary,Επιστρέψτε το ποσό που δεν ζητήθηκε από το μισθό, Deduction from salary,Έκπτωση από το μισθό, -Expired Leaves,Έληξε φύλλα, If this is not checked the loan by default will be considered as a Demand Loan,"Εάν αυτό δεν ελεγχθεί, το δάνειο από προεπιλογή θα θεωρείται ως Δάνειο Ζήτησης", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Αυτός ο λογαριασμός χρησιμοποιείται για την κράτηση αποπληρωμών δανείου από τον δανειολήπτη και επίσης για την εκταμίευση δανείων προς τον οφειλέτη, This account is capital account which is used to allocate capital for loan disbursal account ,Αυτός ο λογαριασμός είναι λογαριασμός κεφαλαίου που χρησιμοποιείται για την κατανομή κεφαλαίου για λογαριασμό εκταμίευσης δανείου, diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv index 50af180da4..5ea5a8b668 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -286,7 +286,6 @@ Auto Repeat,Repetición Automática, Auto repeat document updated,Documento automático editado, Automotive,Automotores, Available,Disponible, -Available Leaves,Licencias Disponibles, Available Qty,Cantidad disponible, Available Selling,Venta disponible, Available for use date is required,Disponible para la fecha de uso es obligatorio, @@ -1122,7 +1121,6 @@ Hub Category,Categoría de Hub, Hub Sync ID,ID de Sincronización del Hub, Human Resource,Recursos humanos, Human Resources,Recursos humanos, -IFSC Code,Código IFSC, IGST Amount,Monto IGST, IP Address,Dirección IP, ITC Available (whether in full op part),ITC disponible (ya sea en la parte op completa), @@ -1666,7 +1664,6 @@ Organization Name,Nombre de la Organización, Other,Otro, Other Reports,Otros Reportes, "Other outward supplies(Nil rated,Exempted)","Otros suministros externos (sin calificación, exentos)", -Others,Otros, Out Qty,Cant. enviada, Out Value,Fuera de Valor, Out of Order,Fuera de servicio, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Crédito), Total (Without Tax),Total (Sin Impuestos), Total Achieved,Total Conseguido, Total Actual,Total Actual, -Total Allocated Leaves,Total de Licencias Asignadas, Total Amount,Importe total, Total Amount Credited,Monto Total Acreditado, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total de comisiones aplicables en la compra Tabla de recibos Los artículos deben ser iguales que las tasas totales y cargos, @@ -2922,7 +2918,6 @@ Updating Variants...,Actualizando Variantes ..., Upload your letter head and logo. (you can edit them later).,Cargue su membrete y el logotipo. (Estos pueden editarse más tarde)., Upper Income,Ingresos superior, Use Sandbox,Utilizar Sandbox, -Used Leaves,Licencias Usadas, User,Usuario, User ID,ID de usuario, User ID not set for Employee {0},ID de usuario no establecido para el empleado {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centro de costos de nómina, Approvers,Aprobadores, The first Approver in the list will be set as the default Approver.,El primer Aprobador de la lista se establecerá como Aprobador predeterminado., Shift Request Approver,Aprobador de solicitud de turno, -PAN Number,Número PAN, Provident Fund Account,Cuenta del fondo de previsión, MICR Code,Código MICR, Repay unclaimed amount from salary,Reembolsar la cantidad no reclamada del salario, Deduction from salary,Deducción del salario, -Expired Leaves,Hojas caducadas, If this is not checked the loan by default will be considered as a Demand Loan,"Si no se marca, el préstamo por defecto se considerará Préstamo a la vista.", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Esta cuenta se utiliza para registrar los reembolsos de préstamos del prestatario y también para desembolsar préstamos al prestatario., This account is capital account which is used to allocate capital for loan disbursal account ,Esta cuenta es una cuenta de capital que se utiliza para asignar capital para la cuenta de desembolso de préstamos., diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv index e5da343513..4e047c9be1 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -286,7 +286,6 @@ Auto Repeat,Automaatne kordamine, Auto repeat document updated,Auto kordusdokument uuendatud, Automotive,Autod, Available,Saadaval, -Available Leaves,Saadaolevad lehed, Available Qty,Saadaval Kogus, Available Selling,Saadaval müügil, Available for use date is required,Kasutatav kasutuskuupäev on vajalik, @@ -1122,7 +1121,6 @@ Hub Category,Rummu kategooria, Hub Sync ID,Hub Sync ID, Human Resource,Inimressurss, Human Resources,Inimressursid, -IFSC Code,IFSC kood, IGST Amount,IGST summa, IP Address,IP-aadress, ITC Available (whether in full op part),ITC saadaval (kas täielikult op), @@ -1666,7 +1664,6 @@ Organization Name,Organisatsiooni nimi, Other,Muud, Other Reports,Teised aruanded, "Other outward supplies(Nil rated,Exempted)","Muud välistarbed (null, erand)", -Others,Teised, Out Qty,Out Kogus, Out Value,välja väärtus, Out of Order,Korrast ära, @@ -2812,7 +2809,6 @@ Total (Credit),Kokku (Credit), Total (Without Tax),Kokku (maksudeta), Total Achieved,Kokku saavutatud, Total Actual,Kokku Tegelik, -Total Allocated Leaves,Kokku eraldatud lehed, Total Amount,Kogu summa, Total Amount Credited,Kogu summa krediteeritakse, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Kokku kohaldatavate tasude kohta ostutšekk Esemed tabel peab olema sama Kokku maksud ja tasud, @@ -2922,7 +2918,6 @@ Updating Variants...,Variantide värskendamine ..., Upload your letter head and logo. (you can edit them later).,Laadi üles oma kirjas pea ja logo. (seda saab muuta hiljem)., Upper Income,Ülemine tulu, Use Sandbox,Kasuta liivakasti, -Used Leaves,Kasutatud lehed, User,Kasutaja, User ID,kasutaja ID, User ID not set for Employee {0},Kasutaja ID ei seatud Töötaja {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Palgaarvestuse kulukeskus, Approvers,Heakskiitjad, The first Approver in the list will be set as the default Approver.,Loendi esimene heakskiitja määratakse vaikimisi kinnitajaks., Shift Request Approver,Vahetustaotluse kinnitaja, -PAN Number,PAN-number, Provident Fund Account,Providence Fundi konto, MICR Code,MICR-kood, Repay unclaimed amount from salary,Tagasimakstud summa palgast tagasi maksta, Deduction from salary,Palgast mahaarvamine, -Expired Leaves,Aegunud lehed, If this is not checked the loan by default will be considered as a Demand Loan,"Kui seda ei kontrollita, käsitatakse laenu vaikimisi nõudmislaenuna", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Seda kontot kasutatakse laenusaaja tagasimaksete broneerimiseks ja ka laenuvõtjale laenude väljamaksmiseks, This account is capital account which is used to allocate capital for loan disbursal account ,"See konto on kapitalikonto, mida kasutatakse kapitali eraldamiseks laenu väljamaksekontole", diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv index e59023d505..545198fbd5 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -286,7 +286,6 @@ Auto Repeat,خودکار تکرار, Auto repeat document updated,تکرار خودکار سند به روز شد, Automotive,خودرو, Available,در دسترس, -Available Leaves,برگهای موجود, Available Qty,در دسترس تعداد, Available Selling,فروش موجود, Available for use date is required,برای تاریخ استفاده لازم است, @@ -1122,7 +1121,6 @@ Hub Category,رده هاب, Hub Sync ID,شناسه همگام سازی هاب, Human Resource,منابع انسانی, Human Resources,منابع انسانی, -IFSC Code,کد IFSC, IGST Amount,مقدار IGST, IP Address,نشانی آیپی, ITC Available (whether in full op part),ITC موجود (چه در بخش کامل عمل), @@ -1666,7 +1664,6 @@ Organization Name,نام سازمان, Other,دیگر, Other Reports,سایر گزارش, "Other outward supplies(Nil rated,Exempted)",سایر لوازم خارجی (امتیاز صفر ، معافیت), -Others,دیگران, Out Qty,از تعداد, Out Value,ارزش از, Out of Order,خارج از سفارش, @@ -2812,7 +2809,6 @@ Total (Credit),مجموع (اعتباری), Total (Without Tax),مجموع (بدون مالیات), Total Achieved,مجموع بهدستآمده, Total Actual,مجموع واقعی, -Total Allocated Leaves,مجموع برگه های برگزیده, Total Amount,مقدار کل, Total Amount Credited,مبلغ کل اعتبار, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,مجموع اتهامات قابل اجرا در خرید اقلام دریافت جدول باید همان مجموع مالیات و هزینه شود, @@ -2922,7 +2918,6 @@ Updating Variants...,در حال به روزرسانی انواع ..., Upload your letter head and logo. (you can edit them later).,آپلود سر نامه و آرم خود را. (شما می توانید آنها را بعد از ویرایش)., Upper Income,درآمد بالاتر, Use Sandbox,استفاده از گودال ماسهبازی, -Used Leaves,برگهای مورد استفاده, User,کاربر, User ID,ID کاربر, User ID not set for Employee {0},ID کاربر برای کارمند تنظیم نشده {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,مرکز هزینه حقوق و دستمزد, Approvers,متقن, The first Approver in the list will be set as the default Approver.,اولین تأیید کننده در لیست به عنوان تأیید کننده پیش فرض تنظیم می شود., Shift Request Approver,تأیید کننده درخواست شیفت, -PAN Number,شماره PAN, Provident Fund Account,حساب صندوق تأمین مالی, MICR Code,کد MICR, Repay unclaimed amount from salary,مبلغ مطالبه نشده از حقوق را بازپرداخت کنید, Deduction from salary,کسر از حقوق, -Expired Leaves,برگهای منقضی شده, If this is not checked the loan by default will be considered as a Demand Loan,اگر این مورد بررسی نشود ، وام به طور پیش فرض به عنوان وام تقاضا در نظر گرفته می شود, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,این حساب برای رزرو بازپرداخت وام از وام گیرنده و همچنین پرداخت وام به وام گیرنده استفاده می شود, This account is capital account which is used to allocate capital for loan disbursal account ,این حساب حساب سرمایه ای است که برای تخصیص سرمایه برای حساب پرداخت وام استفاده می شود, diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv index 6a58343463..e9ea261d88 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -286,7 +286,6 @@ Auto Repeat,Automaattinen toisto, Auto repeat document updated,Automaattinen toistuva asiakirja päivitetty, Automotive,Automotive, Available,saatavissa, -Available Leaves,Saatavilla olevat lehdet, Available Qty,saatava yksikkömäärä, Available Selling,Saatavana myyntiin, Available for use date is required,Käytettävä päivämäärä on pakollinen, @@ -1122,7 +1121,6 @@ Hub Category,Hub-luokka, Hub Sync ID,Hub-synkronointitunnus, Human Resource,henkilöstöresurssi, Human Resources,Henkilöstöresurssit, -IFSC Code,IFSC-koodi, IGST Amount,IGST Määrä, IP Address,IP-osoite, ITC Available (whether in full op part),ITC käytettävissä (onko täysin op-osa), @@ -1666,7 +1664,6 @@ Organization Name,Organisaatio, Other,muut, Other Reports,Muut raportit, "Other outward supplies(Nil rated,Exempted)","Muut ulkoiset tarvikkeet (nolla, vapautettu)", -Others,Muut, Out Qty,ulkona yksikkömäärä, Out Value,out Arvo, Out of Order,Epäkunnossa, @@ -2812,7 +2809,6 @@ Total (Credit),Yhteensä (luotto), Total (Without Tax),Yhteensä (ilman veroa), Total Achieved,"Yhteensä, saavutettu", Total Actual,Kiinteä summa yhteensä, -Total Allocated Leaves,Kokonaisrajaiset lehdet, Total Amount,Yhteensä, Total Amount Credited,Laskettu kokonaismäärä, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Saapumistositteen riveillä olevat maksut pitää olla sama kuin verot ja maksut osiossa, @@ -2922,7 +2918,6 @@ Updating Variants...,Päivitetään variantteja ..., Upload your letter head and logo. (you can edit them later).,Tuo kirjeen ylätunniste ja logo. (voit muokata niitä myöhemmin), Upper Income,Ylemmät tulot, Use Sandbox,Käytä Sandbox, -Used Leaves,Käytetyt lehdet, User,käyttäjä, User ID,käyttäjätunnus, User ID not set for Employee {0},Käyttäjätunnusta ei asetettu työntekijälle {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Palkkahallinnon kustannuskeskus, Approvers,Hyväksyjät, The first Approver in the list will be set as the default Approver.,Luettelon ensimmäinen hyväksyntä asetetaan oletuksen hyväksyjäksi., Shift Request Approver,Vaihtopyynnön hyväksyntä, -PAN Number,PAN-numero, Provident Fund Account,Provident Fund -tili, MICR Code,MICR-koodi, Repay unclaimed amount from salary,Palauta takaisin perimät palkat, Deduction from salary,Vähennys palkasta, -Expired Leaves,Vanhentuneet lehdet, If this is not checked the loan by default will be considered as a Demand Loan,"Jos tätä ei ole valittu, laina katsotaan oletuksena kysyntälainaksi", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Tätä tiliä käytetään lainan takaisinmaksun varaamiseen luotonsaajalta ja lainojen maksamiseen myös luotonottajalle, This account is capital account which is used to allocate capital for loan disbursal account ,"Tämä tili on pääomatili, jota käytetään pääoman kohdistamiseen lainan maksamiseen", diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 4f4a61ffca..9a67835705 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -286,7 +286,6 @@ Auto Repeat,Répétition automatique, Auto repeat document updated,Document de répétition automatique mis à jour, Automotive,Automobile, Available,Disponible, -Available Leaves,Congés disponibles, Available Qty,Qté disponible, Available Selling,Vente disponible, Available for use date is required,La date de mise en service est nécessaire, @@ -1122,7 +1121,6 @@ Hub Category,Catégorie du Hub, Hub Sync ID,Hub Sync ID, Human Resource,Ressource humaine, Human Resources,Ressources humaines, -IFSC Code,Code IFSC, IGST Amount,IGST Montant, IP Address,Adresse IP, ITC Available (whether in full op part),CIT Disponible (que ce soit en partie op), @@ -1665,7 +1663,6 @@ Organization Name,Nom de l'Organisation, Other,Autre, Other Reports,Autres rapports, "Other outward supplies(Nil rated,Exempted)","Autres livraisons sortantes (cotations nulles, exemptées)", -Others,Autres, Out Qty,Qté Sortante, Out Value,Valeur Sortante, Out of Order,Hors service, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Crédit), Total (Without Tax),Total (hors taxes), Total Achieved,Total Obtenu, Total Actual,Total réel, -Total Allocated Leaves,Total des congés alloués, Total Amount,Montant total, Total Amount Credited,Montant total crédité, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total des Frais Applicables dans la Table des Articles de Reçus d’Achat doit être égal au Total des Taxes et Frais, @@ -2922,7 +2918,6 @@ Updating Variants...,Mise à jour des variantes ..., Upload your letter head and logo. (you can edit them later).,Charger votre en-tête et logo. (vous pouvez les modifier ultérieurement)., Upper Income,Revenu Élevé, Use Sandbox,Utiliser Sandbox, -Used Leaves,Congés utilisés, User,Utilisateur, User ID,Identifiant d'utilisateur, User ID not set for Employee {0},ID de l'Utilisateur non défini pour l'Employé {0}, @@ -7945,12 +7940,10 @@ Payroll Cost Center,Centre de coûts de la paie, Approvers,Approbateurs, The first Approver in the list will be set as the default Approver.,Le premier approbateur de la liste sera défini comme approbateur par défaut., Shift Request Approver,Approbateur de demande de quart, -PAN Number,Numéro PAN, Provident Fund Account,Compte de prévoyance, MICR Code,Code MICR, Repay unclaimed amount from salary,Rembourser le montant non réclamé sur le salaire, Deduction from salary,Déduction du salaire, -Expired Leaves,Feuilles expirées, If this is not checked the loan by default will be considered as a Demand Loan,"Si cette case n'est pas cochée, le prêt par défaut sera considéré comme un prêt à vue", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ce compte est utilisé pour enregistrer les remboursements de prêts de l'emprunteur et également pour décaisser les prêts à l'emprunteur, This account is capital account which is used to allocate capital for loan disbursal account ,Ce compte est un compte de capital qui est utilisé pour allouer du capital au compte de décaissement du prêt, diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv index 3e22bd975d..6cfceef5e7 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -286,7 +286,6 @@ Auto Repeat,સ્વતઃ પુનરાવર્તન કરો, Auto repeat document updated,સ્વતઃ પુનરાવર્તિત દસ્તાવેજ અપડેટ થયો, Automotive,ઓટોમોટિવ, Available,ઉપલબ્ધ, -Available Leaves,ઉપલબ્ધ પાંદડા, Available Qty,ઉપલબ્ધ Qty, Available Selling,ઉપલબ્ધ વેચાણ, Available for use date is required,ઉપયોગ તારીખ માટે ઉપલબ્ધ જરૂરી છે, @@ -1122,7 +1121,6 @@ Hub Category,હબ કેટેગરી, Hub Sync ID,હબ સમન્વયન ID, Human Resource,માનવ સંસાધન, Human Resources,માનવ સંસાધન, -IFSC Code,આઈએફએસસી કોડ, IGST Amount,IGST રકમ, IP Address,IP સરનામું, ITC Available (whether in full op part),આઇટીસી ઉપલબ્ધ છે (સંપૂર્ણ ઓપ ભાગમાં છે કે નહીં), @@ -1666,7 +1664,6 @@ Organization Name,સંસ્થા નામ, Other,અન્ય, Other Reports,અન્ય અહેવાલો, "Other outward supplies(Nil rated,Exempted)","અન્ય બાહ્ય પુરવઠો (નીલ રેટેડ, મુકત)", -Others,અન્ય, Out Qty,Qty આઉટ, Out Value,મૂલ્ય, Out of Order,હુકમ બહાર, @@ -2812,7 +2809,6 @@ Total (Credit),કુલ (ક્રેડિટ), Total (Without Tax),કુલ (કર વગર), Total Achieved,કુલ પ્રાપ્ત, Total Actual,વાસ્તવિક કુલ, -Total Allocated Leaves,કુલ ફાળવેલ પાંદડા, Total Amount,કુલ રકમ, Total Amount Credited,કુલ રકમનો શ્રેય, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,ખરીદી રસીદ વસ્તુઓ ટેબલ કુલ લાગુ ખર્ચ કુલ કર અને ખર્ચ તરીકે જ હોવી જોઈએ, @@ -2922,7 +2918,6 @@ Updating Variants...,ચલોને અપડેટ કરી રહ્યુ Upload your letter head and logo. (you can edit them later).,તમારો પત્ર વડા અને લોગો અપલોડ કરો. (જો તમે પછીથી તેમને ફેરફાર કરી શકો છો)., Upper Income,ઉચ્ચ આવક, Use Sandbox,ઉપયોગ સેન્ડબોક્સ, -Used Leaves,વપરાયેલ પાંદડા, User,વપરાશકર્તા, User ID,વપરાશકર્તા ID, User ID not set for Employee {0},વપરાશકર્તા ID કર્મચારી માટે સેટ નથી {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,પેરોલ ખર્ચ કેન્દ્ર, Approvers,વિવાદ, The first Approver in the list will be set as the default Approver.,સૂચિમાં પ્રથમ મંજૂરી આપનારને ડિફ defaultલ્ટ મંજૂરી તરીકે સેટ કરવામાં આવશે., Shift Request Approver,શિફ્ટ વિનંતી મંજૂરી, -PAN Number,પાન નંબર, Provident Fund Account,પ્રોવિડન્ટ ફંડ એકાઉન્ટ, MICR Code,એમઆઈસીઆર કોડ, Repay unclaimed amount from salary,પગારમાંથી દાવેદારી રકમ પરત કરો, Deduction from salary,પગારમાંથી કપાત, -Expired Leaves,સમાપ્ત પાંદડા, If this is not checked the loan by default will be considered as a Demand Loan,જો આને તપાસવામાં નહીં આવે તો ડિફોલ્ટ રૂપે લોનને ડિમાન્ડ લોન માનવામાં આવશે, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,આ એકાઉન્ટનો ઉપયોગ orણ લેનારા પાસેથી લોન ચુકવણી બુક કરવા અને લેનારાને લોન વહેંચવા માટે થાય છે., This account is capital account which is used to allocate capital for loan disbursal account ,આ એકાઉન્ટ કેપિટલ એકાઉન્ટ છે જેનો ઉપયોગ લોન વિતરણ ખાતા માટે મૂડી ફાળવવા માટે થાય છે, diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv index b0e63067ca..fb11d6b774 100644 --- a/erpnext/translations/he.csv +++ b/erpnext/translations/he.csv @@ -286,7 +286,6 @@ Auto Repeat,חזור אוטומטי, Auto repeat document updated,מסמך חזרה אוטומטית עודכן, Automotive,רכב, Available,זמין, -Available Leaves,עלים זמינים, Available Qty,כמות זמינה, Available Selling,מכירה זמינה, Available for use date is required,נדרש לתאריך השימוש, @@ -1122,7 +1121,6 @@ Hub Category,קטגוריית רכזת, Hub Sync ID,מזהה סנכרון רכזת, Human Resource,משאבי אנוש, Human Resources,משאבי אנוש, -IFSC Code,קוד IFSC, IGST Amount,סכום IGST, IP Address,כתובת ה - IP, ITC Available (whether in full op part),ITC זמין (אם בחלק אופ מלא), @@ -1666,7 +1664,6 @@ Organization Name,שם ארגון, Other,אחר, Other Reports,דוחות נוספים, "Other outward supplies(Nil rated,Exempted)","אספקה חיצונית אחרת (מדורג אפס, פטור)", -Others,אחרים, Out Qty,מתוך כמות, Out Value,ערך מתוך, Out of Order,מקולקל, @@ -2812,7 +2809,6 @@ Total (Credit),סה"כ (אשראי), Total (Without Tax),סה"כ (ללא מס), Total Achieved,"סה""כ הושג", Total Actual,"סה""כ בפועל", -Total Allocated Leaves,סה"כ עלים שהוקצו, Total Amount,"סה""כ לתשלום", Total Amount Credited,סכום כולל אשראי, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,סה"כ החיובים החלים בטבלת פריטי קבלה לרכישה חייבים להיות זהים לסכומי המסים והחיובים, @@ -2922,7 +2918,6 @@ Updating Variants...,מעדכן גרסאות ..., Upload your letter head and logo. (you can edit them later).,העלה ראש המכתב ואת הלוגו שלך. (אתה יכול לערוך אותם מאוחר יותר)., Upper Income,עליון הכנסה, Use Sandbox,השתמש בארגז חול, -Used Leaves,עלים משומשים, User,מִשׁתַמֵשׁ, User ID,זיהוי משתמש, User ID not set for Employee {0},זיהוי משתמש לא נקבע לעובדים {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,מרכז עלות שכר, Approvers,מחלוקת, The first Approver in the list will be set as the default Approver.,המאשר הראשון ברשימה יוגדר כמאשר ברירת המחדל., Shift Request Approver,אישור בקשת משמרת, -PAN Number,מספר PAN, Provident Fund Account,חשבון קופת גמל, MICR Code,קוד MICR, Repay unclaimed amount from salary,החזר סכום שלא נתבע מהמשכורת, Deduction from salary,ניכוי משכר, -Expired Leaves,עלים שפג תוקפם, If this is not checked the loan by default will be considered as a Demand Loan,אם זה לא מסומן ההלוואה כברירת מחדל תיחשב כהלוואת דרישה, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,חשבון זה משמש להזמנת החזרי הלוואות מהלווה וגם להוצאת הלוואות ללווה, This account is capital account which is used to allocate capital for loan disbursal account ,חשבון זה הוא חשבון הון המשמש להקצאת הון לחשבון פרסום הלוואות, diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv index 35c4b81d9a..4090a458de 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -286,7 +286,6 @@ Auto Repeat,ऑटो दोहराना, Auto repeat document updated,ऑटो दोहराना दस्तावेज़ अद्यतन, Automotive,मोटर वाहन, Available,उपलब्ध, -Available Leaves,उपलब्ध पत्तियां, Available Qty,उपलब्ध मात्रा, Available Selling,उपलब्ध बेचना, Available for use date is required,उपयोग की तारीख के लिए उपलब्ध है, @@ -1122,7 +1121,6 @@ Hub Category,हब श्रेणी, Hub Sync ID,हब सिंक आईडी, Human Resource,मानव संसाधन, Human Resources,मानवीय संसाधन, -IFSC Code,आईएफएससी कोड, IGST Amount,आईजीएसटी राशि, IP Address,आईपी पता, ITC Available (whether in full op part),ITC उपलब्ध (पूर्ण ऑप भाग में), @@ -1666,7 +1664,6 @@ Organization Name,संगठन का नाम, Other,अन्य, Other Reports,अन्य रिपोर्टें, "Other outward supplies(Nil rated,Exempted)","अन्य बाहरी आपूर्ति (निल रेटेड, छूट)", -Others,दूसरों, Out Qty,मात्रा बाहर, Out Value,आउट मान, Out of Order,खराब, @@ -2812,7 +2809,6 @@ Total (Credit),कुल (क्रेडिट), Total (Without Tax),कुल (कर के बिना), Total Achieved,कुल प्राप्त, Total Actual,वास्तविक कुल, -Total Allocated Leaves,कुल आवंटित पत्तियां, Total Amount,कुल राशि, Total Amount Credited,कुल राशि क्रेडिट, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,खरीद रसीद आइटम तालिका में कुल लागू शुल्कों के कुल करों और शुल्कों के रूप में ही होना चाहिए, @@ -2922,7 +2918,6 @@ Updating Variants...,वेरिएंट अपडेट हो रहा ह Upload your letter head and logo. (you can edit them later).,अपने पत्र सिर और लोगो अपलोड करें। (आप उन्हें बाद में संपादित कर सकते हैं)।, Upper Income,ऊपरी आय, Use Sandbox,उपयोग सैंडबॉक्स, -Used Leaves,प्रयुक्त पत्तियां, User,उपयोगकर्ता, User ID,प्रयोक्ता आईडी, User ID not set for Employee {0},यूजर आईडी कर्मचारी के लिए सेट नहीं {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,पेरोल लागत केंद्र, Approvers,approvers, The first Approver in the list will be set as the default Approver.,सूची में पहले अनुमोदन डिफ़ॉल्ट डिफ़ॉल्ट के रूप में सेट किया जाएगा।, Shift Request Approver,शिफ्ट रिक्वेस्ट अप्रूवर, -PAN Number,पैन नंबर, Provident Fund Account,भविष्य निधि खाता, MICR Code,MICR कोड, Repay unclaimed amount from salary,वेतन से लावारिस राशि को चुकाएं, Deduction from salary,वेतन से कटौती, -Expired Leaves,समय सीमा समाप्त, If this is not checked the loan by default will be considered as a Demand Loan,"यदि यह डिफ़ॉल्ट रूप से ऋण की जाँच नहीं है, तो इसे डिमांड लोन माना जाएगा", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,इस खाते का उपयोग उधारकर्ता से ऋण चुकौती की बुकिंग के लिए किया जाता है और उधारकर्ता को ऋण का वितरण भी किया जाता है, This account is capital account which is used to allocate capital for loan disbursal account ,यह खाता पूंजी खाता है जिसका उपयोग ऋण वितरण खाते के लिए पूंजी आवंटित करने के लिए किया जाता है, diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv index c44d421527..bafdcc1eca 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -286,7 +286,6 @@ Auto Repeat,Automatsko ponavljanje, Auto repeat document updated,Ažurira se automatski ponavljanje dokumenta, Automotive,Automobilska industrija, Available,Dostupno, -Available Leaves,Dostupni lišće, Available Qty,Dostupno Količina, Available Selling,Dostupna prodaja, Available for use date is required,Dostupan je za datum upotrebe, @@ -1122,7 +1121,6 @@ Hub Category,Kategorija hubova, Hub Sync ID,ID sinkronizacije huba, Human Resource,Ljudski resursi, Human Resources,Ljudski resursi, -IFSC Code,IFSC kod, IGST Amount,Iznos IGST, IP Address,IP adresa, ITC Available (whether in full op part),Dostupan ITC (bilo u cijelom op. Dijelu), @@ -1666,7 +1664,6 @@ Organization Name,Naziv organizacije, Other,Drugi, Other Reports,Ostala izvješća, "Other outward supplies(Nil rated,Exempted)","Ostale vanjske zalihe (ocjenjivano bez vrijednosti, izuzeće)", -Others,Ostali, Out Qty,Od kol, Out Value,Iz vrijednost, Out of Order,Izvanredno, @@ -2812,7 +2809,6 @@ Total (Credit),Ukupno (Credit), Total (Without Tax),Ukupno (bez poreza), Total Achieved,Ukupno Ostvareno, Total Actual,Ukupno Stvarni, -Total Allocated Leaves,Ukupno dopuštena lišća, Total Amount,Ukupan iznos, Total Amount Credited,Ukupan iznos je odobren, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Ukupno odgovarajuće naknade u potvrdi o kupnji stavke stolu mora biti ista kao i Total poreza i naknada, @@ -2922,7 +2918,6 @@ Updating Variants...,Ažuriranje varijanti ..., Upload your letter head and logo. (you can edit them later).,Upload Vaše pismo glavu i logotip. (Možete ih uređivati kasnije)., Upper Income,Gornja Prihodi, Use Sandbox,Sandbox, -Used Leaves,Koristi lišće, User,Korisnik, User ID,Korisnički ID, User ID not set for Employee {0},Korisnik ID nije postavljen za zaposlenika {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Mjesto troška plaća, Approvers,Odobrivači, The first Approver in the list will be set as the default Approver.,Prvi odobravatelj s popisa postavit će se kao zadani odobravatelj., Shift Request Approver,Odobritelj zahtjeva za smjenom, -PAN Number,PAN broj, Provident Fund Account,Račun osiguravajućeg fonda, MICR Code,MICR kod, Repay unclaimed amount from salary,Otplatite neiskorišteni iznos iz plaće, Deduction from salary,Odbitak od plaće, -Expired Leaves,Isteklo lišće, If this is not checked the loan by default will be considered as a Demand Loan,"Ako se ovo ne potvrdi, zajam će se prema zadanim postavkama smatrati zajmom na zahtjev", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ovaj se račun koristi za rezerviranje otplate zajma od zajmoprimca i za isplatu zajmova zajmoprimcu, This account is capital account which is used to allocate capital for loan disbursal account ,Ovaj račun je račun kapitala koji se koristi za raspodjelu kapitala za račun izdvajanja zajma, diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv index c707a1e59a..9fc3a0382d 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -286,7 +286,6 @@ Auto Repeat,Automatikus ismétlés, Auto repeat document updated,Az automatikus ismétlődő dokumentum frissítve, Automotive,Autóipar, Available,Elérhető, -Available Leaves,Lehetséges távollétek, Available Qty,Elérhető Menny., Available Selling,Elérhető értékesítés, Available for use date is required,Rendelkezésre állási dátum szükséges, @@ -1122,7 +1121,6 @@ Hub Category,Hub kategória, Hub Sync ID,Hub szinkronizálási azonosító ID, Human Resource,Emberi Erőforrás HR, Human Resources,Emberi erőforrások HR, -IFSC Code,IFSC kód, IGST Amount,IGST összeg, IP Address,IP-cím, ITC Available (whether in full op part),ITC elérhető (teljes opcióban), @@ -1666,7 +1664,6 @@ Organization Name,Vállalkozás neve, Other,Egyéb, Other Reports,Más jelentések, "Other outward supplies(Nil rated,Exempted)","Egyéb külső készletek (nulla, mentes)", -Others,Egyéb, Out Qty,Mennyiségen kívül, Out Value,Értéken kívül, Out of Order,Üzemen kívül, @@ -2812,7 +2809,6 @@ Total (Credit),Összesen (Követelés), Total (Without Tax),Összesen (adó nélkül/nettó), Total Achieved,Összes Elért, Total Actual,Összes Aktuális, -Total Allocated Leaves,Teljes elosztott távollétek száma, Total Amount,Összesen, Total Amount Credited,Összesen jóváírt összeg, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Összesen alkalmazandó díjak a vásárlási nyugta tételek táblázatban egyeznie kell az Összes adókkal és illetékekkel, @@ -2922,7 +2918,6 @@ Updating Variants...,Változat frissítése ..., Upload your letter head and logo. (you can edit them later).,Kérjük töltse fel levele fejlécét és logo-ját. (Ezeket később szerkesztheti)., Upper Income,Magasabb jövedelem, Use Sandbox,Sandbox felhasználása, -Used Leaves,Felhasznált távollétek, User,használó, User ID,Felhasználó ID, User ID not set for Employee {0},Felhasználói azonosítót nem állított be az Alkalmazotthoz: {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Bérszámfejtési Központ, Approvers,Jóváhagyó, The first Approver in the list will be set as the default Approver.,A lista első jóváhagyója lesz az alapértelmezett jóváhagyó., Shift Request Approver,Shift Request Approver, -PAN Number,PAN szám, Provident Fund Account,Provident Fund számla, MICR Code,MICR kód, Repay unclaimed amount from salary,Visszafizetni az igényelt összeget a fizetésből, Deduction from salary,A fizetés levonása, -Expired Leaves,Lejárt levelek, If this is not checked the loan by default will be considered as a Demand Loan,"Ha ez nincs bejelölve, akkor a hitelt alapértelmezés szerint Igény szerinti kölcsönnek kell tekinteni", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Ezt a számlát arra használják, hogy foglalják le a hiteltől származó hiteltörlesztéseket, és kölcsönöket is folyósítanak a hitelfelvevőnek", This account is capital account which is used to allocate capital for loan disbursal account ,"Ez a számla tőkeszámla, amelyet a hitel folyósítási számlájához szükséges tőke allokálására használnak", diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv index 2ec17e6872..e30951296d 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -286,7 +286,6 @@ Auto Repeat,Ulangi Otomatis, Auto repeat document updated,Pembaruan dokumen otomatis diperbarui, Automotive,Otomotif, Available,Tersedia, -Available Leaves,Cuti Yang Tersedia, Available Qty,Qty Tersedia, Available Selling,Jual Beli, Available for use date is required,Tersedia untuk tanggal penggunaan diperlukan, @@ -1122,7 +1121,6 @@ Hub Category,Kategori Hub, Hub Sync ID,Hub Sync ID, Human Resource,Sumber daya manusia, Human Resources,Sumber daya manusia, -IFSC Code,Kode IFSC, IGST Amount,Jumlah IGST, IP Address,Alamat IP, ITC Available (whether in full op part),ITC Tersedia (baik dalam bagian op penuh), @@ -1666,7 +1664,6 @@ Organization Name,Nama Organisasi, Other,Lain-lain, Other Reports,Laporan Lainnya, "Other outward supplies(Nil rated,Exempted)","Persediaan luar lainnya (nilai nol, Dibebaskan)", -Others,Lainnya, Out Qty,Out Qty, Out Value,out Nilai, Out of Order,Habis, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Kredit), Total (Without Tax),Total (Tanpa Pajak), Total Achieved,Total Dicapai, Total Actual,Total Aktual, -Total Allocated Leaves,Total Cuti Yang Dialokasikan, Total Amount,Nilai Total, Total Amount Credited,Jumlah Total Dikreditkan, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total Biaya Berlaku di Purchase meja Jenis Penerimaan harus sama dengan jumlah Pajak dan Biaya, @@ -2922,7 +2918,6 @@ Updating Variants...,Memperbarui Varian ..., Upload your letter head and logo. (you can edit them later).,Unggah kop surat dan logo. (Anda dapat mengubahnya nanti)., Upper Income,Penghasilan Atas, Use Sandbox,Gunakan Sandbox, -Used Leaves,Cuti Yang Telah Digunakan, User,Pengguna, User ID,ID Pengguna, User ID not set for Employee {0},User ID tidak ditetapkan untuk Karyawan {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Pusat Biaya Penggajian, Approvers,Pemberi persetujuan, The first Approver in the list will be set as the default Approver.,Pemberi Persetujuan pertama dalam daftar akan ditetapkan sebagai Pemberi Persetujuan default., Shift Request Approver,Penyetuju Permintaan Shift, -PAN Number,Nomor PAN, Provident Fund Account,Rekening Dana Provident, MICR Code,Kode MICR, Repay unclaimed amount from salary,Membayar kembali jumlah gaji yang belum diklaim, Deduction from salary,Pemotongan gaji, -Expired Leaves,Daun kadaluarsa, If this is not checked the loan by default will be considered as a Demand Loan,"Jika tidak dicentang, pinjaman secara default akan dianggap sebagai Pinjaman Permintaan", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Rekening ini digunakan untuk membukukan pembayaran pinjaman dari peminjam dan juga menyalurkan pinjaman kepada peminjam, This account is capital account which is used to allocate capital for loan disbursal account ,Akun ini adalah akun modal yang digunakan untuk mengalokasikan modal ke akun pencairan pinjaman, diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv index 93df452b7a..89c0de679d 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -286,7 +286,6 @@ Auto Repeat,Sjálfvirk endurtaka, Auto repeat document updated,Sjálfvirk endurtaka skjal uppfært, Automotive,Automotive, Available,Laus, -Available Leaves,Lausar blöð, Available Qty,Laus magn, Available Selling,Laus selja, Available for use date is required,Til staðar er hægt að nota dagsetninguna, @@ -1122,7 +1121,6 @@ Hub Category,Hub Flokkur, Hub Sync ID,Hub Sync ID, Human Resource,Mannauðs, Human Resources,Mannauður, -IFSC Code,IFSC-kóði, IGST Amount,IGST upphæð, IP Address,IP-tölu, ITC Available (whether in full op part),ITC í boði (hvort sem það er í heild hluta), @@ -1666,7 +1664,6 @@ Organization Name,nafn samtaka, Other,Annað, Other Reports,Aðrar skýrslur, "Other outward supplies(Nil rated,Exempted)","Aðrar birgðir út á við (ekki metnar, undanþegnar)", -Others,Aðrir, Out Qty,Út magn, Out Value,út Value, Out of Order,Bilað, @@ -2812,7 +2809,6 @@ Total (Credit),Alls (Credit), Total (Without Tax),Samtals (án skatta), Total Achieved,alls Náð, Total Actual,alls Raunveruleg, -Total Allocated Leaves,Samtals úthlutað blöð, Total Amount,Heildarupphæð, Total Amount Credited,Heildarfjárhæð innheimt, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Samtals greiðsla í kvittun atriðum borðið verður að vera það sama og Samtals skatta og gjöld, @@ -2922,7 +2918,6 @@ Updating Variants...,Uppfærir afbrigði ..., Upload your letter head and logo. (you can edit them later).,Hlaða bréf höfuðið og merki. (Þú getur breytt þeim síðar)., Upper Income,Efri tekjur, Use Sandbox,Nota Sandbox, -Used Leaves,Notaðar blöð, User,Notandi, User ID,notandanafn, User ID not set for Employee {0},User ID ekki sett fyrir Starfsmaður {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Launakostnaður, Approvers,Ágreiningsmenn, The first Approver in the list will be set as the default Approver.,Fyrsti samþykkur listans verður stilltur sem sjálfgefinn samþykkur., Shift Request Approver,Samþykki vaktabeiðni, -PAN Number,PAN númer, Provident Fund Account,Reikningur sjóðsins, MICR Code,MICR kóði, Repay unclaimed amount from salary,Endurgreiða óheimta upphæð af launum, Deduction from salary,Frádráttur frá launum, -Expired Leaves,Útrunnið lauf, If this is not checked the loan by default will be considered as a Demand Loan,Ef ekki er hakað við þetta verður lánið sjálfgefið litið á sem eftirspurnarlán, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Þessi reikningur er notaður til að bóka endurgreiðslur lána frá lántakanda og einnig til að greiða út lán til lántakanda, This account is capital account which is used to allocate capital for loan disbursal account ,Þessi reikningur er fjármagnsreikningur sem er notaður til að úthluta fjármagni til útborgunarreiknings lána, diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv index 558b091d36..9ae43e3d4c 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -286,7 +286,6 @@ Auto Repeat,Ripetizione automatica, Auto repeat document updated,Ripetizione automatica aggiornata, Automotive,Automotive, Available,Disponibile, -Available Leaves,Ferie disponibili, Available Qty,Disponibile Quantità, Available Selling,Vendita disponibile, Available for use date is required,Disponibile per la data di utilizzo è richiesto, @@ -1122,7 +1121,6 @@ Hub Category,Categoria Hub, Hub Sync ID,ID di sincronizzazione Hub, Human Resource,Risorsa Umana, Human Resources,Risorse umane, -IFSC Code,Codice IFSC, IGST Amount,Quantità IGST, IP Address,Indirizzo IP, ITC Available (whether in full op part),ITC disponibile (sia nella parte operativa completa), @@ -1666,7 +1664,6 @@ Organization Name,Nome organizzazione, Other,Altro, Other Reports,Altri Reports, "Other outward supplies(Nil rated,Exempted)","Altre forniture esterne (esenti da zero, esenti)", -Others,Altri, Out Qty,out Quantità, Out Value,Valore out, Out of Order,Guasto, @@ -2812,7 +2809,6 @@ Total (Credit),Totale (credito), Total (Without Tax),Totale (senza tasse), Total Achieved,Totale raggiunto, Total Actual,Totale Actual, -Total Allocated Leaves,Ferie totali allocate, Total Amount,Totale Importo, Total Amount Credited,Importo totale accreditato, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Totale oneri addebitati in Acquisto tabella di carico Gli articoli devono essere uguale Totale imposte e oneri, @@ -2922,7 +2918,6 @@ Updating Variants...,Aggiornamento delle varianti ..., Upload your letter head and logo. (you can edit them later).,Carica la tua testa lettera e logo. (È possibile modificare in un secondo momento)., Upper Income,Reddito superiore, Use Sandbox,Usa Sandbox, -Used Leaves,Ferie Usate, User,Utente, User ID,ID utente, User ID not set for Employee {0},ID utente non impostato per Dipedente {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centro di costo del libro paga, Approvers,Approvatori, The first Approver in the list will be set as the default Approver.,Il primo approvatore nell'elenco verrà impostato come approvatore predefinito., Shift Request Approver,Approvatore richiesta di turno, -PAN Number,Numero PAN, Provident Fund Account,Conto del fondo di previdenza, MICR Code,Codice MICR, Repay unclaimed amount from salary,Rimborsare l'importo non reclamato dallo stipendio, Deduction from salary,Detrazione dallo stipendio, -Expired Leaves,Foglie scadute, If this is not checked the loan by default will be considered as a Demand Loan,"Se questa opzione non è selezionata, il prestito di default sarà considerato come un prestito a vista", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Questo conto viene utilizzato per la prenotazione del rimborso del prestito dal mutuatario e anche per l'erogazione dei prestiti al mutuatario, This account is capital account which is used to allocate capital for loan disbursal account ,Questo conto è un conto capitale utilizzato per allocare il capitale per il conto di erogazione del prestito, diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv index 9a7bebebe2..c61a70e888 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -286,7 +286,6 @@ Auto Repeat,自動繰り返し, Auto repeat document updated,自動繰り返し文書が更新されました, Automotive,自動車, Available,利用可, -Available Leaves,利用可能な葉, Available Qty,利用可能な数量, Available Selling,販売可能, Available for use date is required,使用可能な日付が必要です, @@ -1122,7 +1121,6 @@ Hub Category,ハブカテゴリ, Hub Sync ID,ハブ同期ID, Human Resource,人材, Human Resources,人事, -IFSC Code,IFSCコード, IGST Amount,IGST金額, IP Address,IPアドレス, ITC Available (whether in full op part),利用可能なITC(完全な一部かどうかにかかわらず), @@ -1666,7 +1664,6 @@ Organization Name,組織名, Other,その他, Other Reports,その他のレポート, "Other outward supplies(Nil rated,Exempted)",その他の外部供給品(定格なし、免除), -Others,その他, Out Qty,出量, Out Value,タイムアウト値, Out of Order,故障中, @@ -2812,7 +2809,6 @@ Total (Credit),合計(クレジット), Total (Without Tax),合計(税なし), Total Achieved,達成計, Total Actual,実費計, -Total Allocated Leaves,割り当てられた合計葉, Total Amount,合計, Total Amount Credited,合計金額, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,購入レシートItemsテーブル内の合計有料合計税金、料金と同じでなければなりません, @@ -2922,7 +2918,6 @@ Updating Variants...,バリアントを更新しています..., Upload your letter head and logo. (you can edit them later).,レターヘッドとロゴをアップロードします(後で編集可能です), Upper Income,高収益, Use Sandbox,サンドボックスを使用, -Used Leaves,中古の葉, User,ユーザー, User ID,ユーザー ID, User ID not set for Employee {0},従業員{0}のユーザーIDが未設定です。, @@ -7949,12 +7944,10 @@ Payroll Cost Center,給与コストセンター, Approvers,承認者, The first Approver in the list will be set as the default Approver.,リストの最初の承認者がデフォルトの承認者として設定されます。, Shift Request Approver,シフトリクエスト承認者, -PAN Number,PAN番号, Provident Fund Account,プロビデントファンドアカウント, MICR Code,MICRコード, Repay unclaimed amount from salary,未請求額を給与から返済する, Deduction from salary,給与からの控除, -Expired Leaves,期限切れの葉, If this is not checked the loan by default will be considered as a Demand Loan,これがチェックされていない場合、デフォルトでローンはデマンドローンと見なされます, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,このアカウントは、借り手からのローン返済の予約と、借り手へのローンの支払いに使用されます。, This account is capital account which is used to allocate capital for loan disbursal account ,この口座は、ローン実行口座に資本を割り当てるために使用される資本口座です。, diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv index 425940caa8..90f9013b9c 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -286,7 +286,6 @@ Auto Repeat,ធ្វើម្តងទៀតដោយស្វ័យប្រ Auto repeat document updated,បានធ្វើបច្ចុប្បន្នភាពឯកសារដោយស្វ័យប្រវត្តិ, Automotive,រថយន្ដ, Available,ដែលអាចប្រើបាន, -Available Leaves,មានស្លឹក, Available Qty,ដែលអាចប្រើបាន Qty, Available Selling,លក់ដែលអាចប្រើបាន, Available for use date is required,អាចរកបានសម្រាប់កាលបរិច្ឆេទប្រើ, @@ -1122,7 +1121,6 @@ Hub Category,ប្រភេទ Hub, Hub Sync ID,Hub Sync ID, Human Resource,ធនធានមនុស្ស, Human Resources,ធនធានមនុស្ស, -IFSC Code,កូដ IFSC, IGST Amount,បរិមាណ IGST, IP Address,អាសយដ្ឋាន IP, ITC Available (whether in full op part),មានអាយ។ ស៊ី។ ធី។ (មិនថាជាផ្នែកពេញលេញ), @@ -1666,7 +1664,6 @@ Organization Name,ឈ្មោះអង្គភាព, Other,ផ្សេងទៀត, Other Reports,របាយការណ៍ផ្សេងទៀត, "Other outward supplies(Nil rated,Exempted)",ការផ្គត់ផ្គង់ខាងក្រៅផ្សេងទៀត (បានវាយតម្លៃមិនលើកលែង), -Others,អ្នកផ្សេងទៀត, Out Qty,ចេញ Qty, Out Value,តម្លៃចេញ, Out of Order,ចេញពីលំដាប់, @@ -2812,7 +2809,6 @@ Total (Credit),សរុប (ឥណទាន), Total (Without Tax),សរុប (ដោយគ្មានពន្ធ), Total Achieved,សរុបសម្រេច, Total Actual,សរុបជាក់ស្តែង, -Total Allocated Leaves,ស្លឹកបម្រុងសរុប, Total Amount,ចំនួនសរុប, Total Amount Credited,ចំនួនទឹកប្រាក់សរុបដែលបានផ្ទៀងផ្ទាត់, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,ការចោទប្រកាន់អនុវត្តសរុបនៅក្នុងការទិញតារាងការទទួលធាតុត្រូវដូចគ្នាដែលជាពន្ធសរុបនិងការចោទប្រកាន់, @@ -2922,7 +2918,6 @@ Updating Variants...,កំពុងធ្វើបច្ចុប្បន្ Upload your letter head and logo. (you can edit them later).,ផ្ទុកឡើងក្បាលលិខិតនិងស្លាកសញ្ញារបស់អ្នក។ (អ្នកអាចកែសម្រួលពួកវានៅពេលក្រោយ) ។, Upper Income,ផ្នែកខាងលើប្រាក់ចំណូល, Use Sandbox,ប្រើសេនបក់, -Used Leaves,ប្រើស្លឹក, User,អ្នកប្រើ, User ID,លេខសម្គាល់អ្នកប្រើ, User ID not set for Employee {0},លេខសម្គាល់អ្នកប្រើដែលមិនបានកំណត់សម្រាប់បុគ្គលិក {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,មជ្ឈមណ្ឌលចំណាយប្រាក់ Approvers,គួរឱ្យសង្ស័យ, The first Approver in the list will be set as the default Approver.,អ្នកផ្តល់យោបល់ដំបូងគេនៅក្នុងបញ្ជីនឹងត្រូវបានកំណត់ជាអ្នកផ្តល់យោបល់លំនាំដើម, Shift Request Approver,ផ្លាស់ប្តូរសំណើរសុំការផ្លាស់ប្តូរ, -PAN Number,លេខ PAN, Provident Fund Account,គណនីមូលនិធិអ្នកផ្តល់សេវា, MICR Code,MICR កូដ, Repay unclaimed amount from salary,សងចំនួនទឹកប្រាក់ដែលមិនបានទាមទារពីប្រាក់ខែ, Deduction from salary,ការកាត់បន្ថយពីប្រាក់ខែ, -Expired Leaves,ស្លឹកផុតកំណត់, If this is not checked the loan by default will be considered as a Demand Loan,ប្រសិនបើនេះមិនត្រូវបានពិនិត្យប្រាក់កម្ចីតាមលំនាំដើមនឹងត្រូវបានចាត់ទុកថាជាប្រាក់កម្ចីតម្រូវការ, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,គណនីនេះត្រូវបានប្រើសម្រាប់ការកក់ការសងប្រាក់កម្ចីពីអ្នកខ្ចីហើយថែមទាំងផ្តល់ប្រាក់កម្ចីដល់អ្នកខ្ចីផងដែរ, This account is capital account which is used to allocate capital for loan disbursal account ,គណនីនេះគឺជាគណនីដើមទុនដែលត្រូវបានប្រើដើម្បីបែងចែកដើមទុនសម្រាប់គណនីប្រាក់កម្ចី, diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv index 5b421132a4..02eab1d831 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -286,7 +286,6 @@ Auto Repeat,ಆಟೋ ಪುನರಾವರ್ತನೆ, Auto repeat document updated,ಸ್ವಯಂ ಪುನರಾವರ್ತಿತ ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ, Automotive,ಆಟೋಮೋಟಿವ್, Available,ಲಭ್ಯ, -Available Leaves,ಲಭ್ಯವಿರುವ ಎಲೆಗಳು, Available Qty,ಲಭ್ಯವಿರುವ ಪ್ರಮಾಣ, Available Selling,ಲಭ್ಯವಿರುವ ಮಾರಾಟ, Available for use date is required,ಬಳಕೆ ದಿನಾಂಕಕ್ಕೆ ಲಭ್ಯವಿದೆ, @@ -1122,7 +1121,6 @@ Hub Category,ಹಬ್ ವರ್ಗ, Hub Sync ID,ಹಬ್ ಸಿಂಕ್ ID, Human Resource,ಮಾನವ ಸಂಪನ್ಮೂಲ, Human Resources,ಮಾನವ ಸಂಪನ್ಮೂಲ, -IFSC Code,IFSC ಕೋಡ್, IGST Amount,IGST ಮೊತ್ತ, IP Address,IP ವಿಳಾಸ, ITC Available (whether in full op part),ಐಟಿಸಿ ಲಭ್ಯವಿದೆ (ಪೂರ್ಣ ಆಪ್ ಭಾಗದಲ್ಲಿರಲಿ), @@ -1666,7 +1664,6 @@ Organization Name,ಸಂಸ್ಥೆ ಹೆಸರು, Other,ಇತರ, Other Reports,ಇತರ ವರದಿಗಳು, "Other outward supplies(Nil rated,Exempted)","ಇತರ ಬಾಹ್ಯ ಸರಬರಾಜುಗಳು (ನಿಲ್ ರೇಟ್, ವಿನಾಯಿತಿ)", -Others,ಇತರೆ, Out Qty,ಪ್ರಮಾಣ ಔಟ್, Out Value,ಔಟ್ ಮೌಲ್ಯ, Out of Order,ಆದೇಶದ ಹೊರಗೆ, @@ -2812,7 +2809,6 @@ Total (Credit),ಒಟ್ಟು (ಕ್ರೆಡಿಟ್), Total (Without Tax),ಒಟ್ಟು (ತೆರಿಗೆ ಇಲ್ಲದೆ), Total Achieved,ಒಟ್ಟು ಸಾಧಿಸಿದ, Total Actual,ನಿಜವಾದ ಒಟ್ಟು, -Total Allocated Leaves,ಒಟ್ಟು ಹಂಚಲ್ಪಟ್ಟ ಎಲೆಗಳು, Total Amount,ಒಟ್ಟು ಪ್ರಮಾಣ, Total Amount Credited,ಒಟ್ಟು ಮೊತ್ತವನ್ನು ಕ್ರೆಡಿಟ್ ಮಾಡಲಾಗಿದೆ, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,ಖರೀದಿ ರಸೀತಿ ವಸ್ತುಗಳು ಕೋಷ್ಟಕದಲ್ಲಿ ಒಟ್ಟು ಅನ್ವಯಿಸುವ ತೆರಿಗೆ ಒಟ್ಟು ತೆರಿಗೆಗಳು ಮತ್ತು ಶುಲ್ಕಗಳು ಅದೇ ಇರಬೇಕು, @@ -2922,7 +2918,6 @@ Updating Variants...,ರೂಪಾಂತರಗಳನ್ನು ನವೀಕರಿ Upload your letter head and logo. (you can edit them later).,ನಿಮ್ಮ ಪತ್ರ ತಲೆ ಮತ್ತು ಲೋಗೋ ಅಪ್ಲೋಡ್. (ನೀವು ಅವುಗಳನ್ನು ನಂತರ ಸಂಪಾದಿಸಬಹುದು)., Upper Income,ಮೇಲ್ ವರಮಾನ, Use Sandbox,ಸ್ಯಾಂಡ್ಬಾಕ್ಸ್ ಬಳಸಿ, -Used Leaves,ಉಪಯೋಗಿಸಿದ ಎಲೆಗಳು, User,ಬಳಕೆದಾರ, User ID,ಬಳಕೆದಾರ ID, User ID not set for Employee {0},ಬಳಕೆದಾರ ID ನೌಕರ ಸೆಟ್ {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,ವೇತನದಾರರ ವೆಚ್ಚ ಕೇಂದ್ರ, Approvers,ಅನುಮೋದಕರು, The first Approver in the list will be set as the default Approver.,ಪಟ್ಟಿಯಲ್ಲಿನ ಮೊದಲ ಅನುಮೋದಕವನ್ನು ಡೀಫಾಲ್ಟ್ ಅನುಮೋದಕ ಎಂದು ಹೊಂದಿಸಲಾಗುವುದು., Shift Request Approver,ಶಿಫ್ಟ್ ವಿನಂತಿ ಅನುಮೋದನೆ, -PAN Number,ಪ್ಯಾನ್ ಸಂಖ್ಯೆ, Provident Fund Account,ಭವಿಷ್ಯ ನಿಧಿ ಖಾತೆ, MICR Code,MICR ಕೋಡ್, Repay unclaimed amount from salary,ಹಕ್ಕು ಪಡೆಯದ ಮೊತ್ತವನ್ನು ಸಂಬಳದಿಂದ ಮರುಪಾವತಿ ಮಾಡಿ, Deduction from salary,ಸಂಬಳದಿಂದ ಕಡಿತ, -Expired Leaves,ಅವಧಿ ಮುಗಿದ ಎಲೆಗಳು, If this is not checked the loan by default will be considered as a Demand Loan,ಇದನ್ನು ಪರಿಶೀಲಿಸದಿದ್ದರೆ ಸಾಲವನ್ನು ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಬೇಡಿಕೆ ಸಾಲವೆಂದು ಪರಿಗಣಿಸಲಾಗುತ್ತದೆ, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,ಈ ಖಾತೆಯನ್ನು ಸಾಲಗಾರರಿಂದ ಸಾಲ ಮರುಪಾವತಿಯನ್ನು ಕಾಯ್ದಿರಿಸಲು ಮತ್ತು ಸಾಲಗಾರನಿಗೆ ಸಾಲವನ್ನು ವಿತರಿಸಲು ಬಳಸಲಾಗುತ್ತದೆ, This account is capital account which is used to allocate capital for loan disbursal account ,"ಈ ಖಾತೆಯು ಬಂಡವಾಳ ಖಾತೆಯಾಗಿದ್ದು, ಸಾಲ ವಿತರಣಾ ಖಾತೆಗೆ ಬಂಡವಾಳವನ್ನು ನಿಯೋಜಿಸಲು ಬಳಸಲಾಗುತ್ತದೆ", diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv index 48a3e0c674..ae99764268 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -286,7 +286,6 @@ Auto Repeat,자동 반복, Auto repeat document updated,자동 반복 문서 업데이트 됨, Automotive,자동차, Available,사용 가능함, -Available Leaves,사용 가능한 잎, Available Qty,사용 가능한 수량, Available Selling,판매 가능, Available for use date is required,사용 가능한 날짜가 필요합니다., @@ -1122,7 +1121,6 @@ Hub Category,허브 카테고리, Hub Sync ID,허브 동기화 ID, Human Resource,인적 자원, Human Resources,인적 자원, -IFSC Code,IFSC 코드, IGST Amount,IGST 금액, IP Address,IP 주소, ITC Available (whether in full op part),ITC 가능 (전체 운영 부분 포함), @@ -1666,7 +1664,6 @@ Organization Name,조직 이름, Other,기타, Other Reports,기타 보고서, "Other outward supplies(Nil rated,Exempted)","기타 외부 공급 (Nil rated, Exempted)", -Others,기타사항, Out Qty,수량 아웃, Out Value,제한 값, Out of Order,고장난, @@ -2812,7 +2809,6 @@ Total (Credit),합계 (신용), Total (Without Tax),합계 (세금 제외), Total Achieved,전체 달성, Total Actual,실제 총, -Total Allocated Leaves,총 할당 된 잎, Total Amount,총액, Total Amount Credited,총 크레딧 금액, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,구매 영수증 항목 테이블에 전체 적용 요금은 총 세금 및 요금과 동일해야합니다, @@ -2922,7 +2918,6 @@ Updating Variants...,변형 업데이트 중 ..., Upload your letter head and logo. (you can edit them later).,편지의 머리와 로고를 업로드합니다. (나중에 편집 할 수 있습니다)., Upper Income,위 소득, Use Sandbox,사용 샌드 박스, -Used Leaves,중고 잎, User,사용자, User ID,사용자 ID, User ID not set for Employee {0},사용자 ID 직원에 대한 설정하지 {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,급여 비용 센터, Approvers,승인자, The first Approver in the list will be set as the default Approver.,목록의 첫 번째 승인자가 기본 승인자로 설정됩니다., Shift Request Approver,교대 요청 승인자, -PAN Number,PAN 번호, Provident Fund Account,프로 비 던트 펀드 계정, MICR Code,MICR 코드, Repay unclaimed amount from salary,급여에서 미 청구 금액 상환, Deduction from salary,급여에서 공제, -Expired Leaves,만료 된 잎, If this is not checked the loan by default will be considered as a Demand Loan,선택하지 않으면 기본적으로 대출이 수요 대출로 간주됩니다., This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,이 계정은 차용인의 대출 상환을 예약하고 차용인에게 대출금을 지급하는 데 사용됩니다., This account is capital account which is used to allocate capital for loan disbursal account ,이 계정은 대출 지급 계정에 자본을 할당하는 데 사용되는 자본 계정입니다., diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv index f1972e8701..d94a37e453 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Repeat, Auto repeat document updated,Vebijêrkek belgekirinê nûve bike, Automotive,Automotive, Available,Berdeste, -Available Leaves,Leaves Available, Available Qty,Available Qty, Available Selling,Bazirganiya Bazirganî, Available for use date is required,Ji bo karanîna karanîna pêdivî ye, @@ -1122,7 +1121,6 @@ Hub Category,Kategorî, Hub Sync ID,Nasnameya Hub Sync, Human Resource,çavkaniyê binirxîne mirovan, Human Resources,Çavkaniyên Mirovî, -IFSC Code,Kodê IFSC, IGST Amount,Amûr IGST, IP Address,Navnîşana IP'yê, ITC Available (whether in full op part),Danûstandinên ITC (gelo di beşa tevahiya opoyê de), @@ -1666,7 +1664,6 @@ Organization Name,Navê rêxistina, Other,Yên din, Other Reports,din Reports, "Other outward supplies(Nil rated,Exempted)","Materyalên din ên derveyî (Nil nirxandin, Pezakirin)", -Others,yên din, Out Qty,out Qty, Out Value,Nirx out, Out of Order,Xirab, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Credit), Total (Without Tax),Tiştek Bacê, Total Achieved,Total nebine, Total Actual,Total Actual, -Total Allocated Leaves,Niştecîhên Teva Allocated, Total Amount,Temamê meblaxa, Total Amount Credited,Jimareya Giştî ya Credited, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Total doz li wergirtinê li Purchase Nawy Meqbûz sifrê divê eynî wek Total Bac, û doz li be", @@ -2922,7 +2918,6 @@ Updating Variants...,Variantên nûvekirin ..., Upload your letter head and logo. (you can edit them later).,Upload nameya serokê û logo xwe. (Tu ji wan paşê biguherîne)., Upper Income,Dahata Upper, Use Sandbox,bikaranîna Ceriban, -Used Leaves,Leaves Used, User,Bikaranîvan, User ID,ID'ya bikarhêner, User ID not set for Employee {0},ID'ya bikarhêner ji bo karkirinê set ne {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Navenda Lêçûna Meaşê, Approvers,Têgihiştin, The first Approver in the list will be set as the default Approver.,Di lîsteyê de Pejirandina yekem dê wekî Destûra Pêşniyar were saz kirin., Shift Request Approver,Pejirandina Daxwaza Guherînê, -PAN Number,Hejmara PAN, Provident Fund Account,Hesabê Fona Pêşbîn, MICR Code,MICR Code, Repay unclaimed amount from salary,Mûçeya nevekirî ji meaşê paşde bidin, Deduction from salary,Daxistina ji meaş, -Expired Leaves,Pelên Dawî, If this is not checked the loan by default will be considered as a Demand Loan,Ger ev neyê kontrol kirin dê deyn bi default dê wekî Krediyek Daxwaz were hesibandin, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ev hesab ji bo veqetandina vegerandinên deyn ji deyndêr û her weha dayîna deyn ji deyndêr re tê bikar anîn, This account is capital account which is used to allocate capital for loan disbursal account ,Ev hesab hesabê sermiyan e ku ji bo veqetandina sermaye ji bo hesabê dayîna kredî tê bikar anîn, diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv index cdb6f66b31..3213a1aa59 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Repeat, Auto repeat document updated,ການປັບປຸງເອກະສານຊ້ໍາອັດຕະໂນມັດ, Automotive,ຍານຍົນ, Available,ສາມາດໃຊ້ໄດ້, -Available Leaves,Available Leaves, Available Qty,ມີຈໍານວນ, Available Selling,Available Selling, Available for use date is required,ຕ້ອງມີວັນທີ່ໃຊ້ສໍາລັບການນໍາໃຊ້, @@ -1122,7 +1121,6 @@ Hub Category,Category Hub, Hub Sync ID,Hub Sync ID, Human Resource,ຊັບພະຍາກອນມະນຸດ, Human Resources,ຊັບພະຍາກອນມະນຸດ, -IFSC Code,IFSC Code, IGST Amount,IGST Amount, IP Address,ທີ່ຢູ່ IP, ITC Available (whether in full op part),ມີ ITC (ບໍ່ວ່າຈະຢູ່ໃນສ່ວນເຕັມ), @@ -1666,7 +1664,6 @@ Organization Name,ຊື່ອົງການຈັດຕັ້ງ, Other,ອື່ນ ໆ, Other Reports,ບົດລາຍງານອື່ນ ໆ, "Other outward supplies(Nil rated,Exempted)","ເຄື່ອງໃຊ້ພາຍນອກອື່ນໆ (ອັນດັບ, ຍົກເວັ້ນ)", -Others,ຄົນອື່ນ, Out Qty,ອອກຈໍານວນ, Out Value,ມູນຄ່າອອກ, Out of Order,ອອກຈາກຄໍາສັ່ງ, @@ -2812,7 +2809,6 @@ Total (Credit),ທັງຫມົດ (Credit), Total (Without Tax),ລວມ (ໂດຍບໍ່ມີພາສີ), Total Achieved,ທັງຫມົດບັນລຸ, Total Actual,ທັງຫມົດທີ່ເກີດຂຶ້ນຈິງ, -Total Allocated Leaves,ຈໍານວນໃບທີ່ຖືກມອບຫມາຍ, Total Amount,ຈໍານວນທັງຫມົດ, Total Amount Credited,ຈໍານວນເງິນທີ່ໄດ້ຮັບການຢັ້ງຢືນ, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,ຄ່າໃຊ້ຈ່າຍທັງຫມົດໃນການຊື້ຕາຕະລາງໃບລາຍການຈະຕ້ອງເຊັ່ນດຽວກັນກັບພາສີອາກອນທັງຫມົດແລະຄ່າບໍລິການ, @@ -2922,7 +2918,6 @@ Updating Variants...,ກຳ ລັງປັບປຸງ Variants ..., Upload your letter head and logo. (you can edit them later).,ອັບຫົວຈົດຫມາຍສະບັບແລະສັນຍາລັກຂອງທ່ານ. (ທ່ານສາມາດແກ້ໄຂໃຫ້ເຂົາເຈົ້າຕໍ່ມາ)., Upper Income,Upper ລາຍໄດ້, Use Sandbox,ການນໍາໃຊ້ Sandbox, -Used Leaves,Used Leaves, User,ຜູ້ໃຊ້, User ID,User ID, User ID not set for Employee {0},User ID ບໍ່ກໍານົດສໍາລັບພະນັກງານ {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,ສູນຈ່າຍເງິນເດືອນ, Approvers,ຄວາມເປັນຫ່ວງ, The first Approver in the list will be set as the default Approver.,The Approver ຄັ້ງ ທຳ ອິດໃນລາຍຊື່ຈະຖືກຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນຂອງ Approver., Shift Request Approver,ການຂໍວິທີການປ່ຽນ, -PAN Number,ເລກ PAN, Provident Fund Account,ບັນຊີກອງທຶນຜູ້ໃຫ້ບໍລິການ, MICR Code,ລະຫັດ MICR, Repay unclaimed amount from salary,ຈ່າຍຄືນ ຈຳ ນວນທີ່ບໍ່ໄດ້ຮັບຈາກເງິນເດືອນ, Deduction from salary,ການຫັກລົບຈາກເງິນເດືອນ, -Expired Leaves,ໃບ ໝົດ ອາຍຸ, If this is not checked the loan by default will be considered as a Demand Loan,ຖ້າສິ່ງນີ້ບໍ່ຖືກກວດກາເງິນກູ້ໂດຍຄ່າເລີ່ມຕົ້ນຈະຖືກພິຈາລະນາເປັນເງິນກູ້ຕາມຄວາມຕ້ອງການ, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,ບັນຊີນີ້ໃຊ້ເພື່ອຈອງການຈ່າຍຄືນເງິນກູ້ຈາກຜູ້ກູ້ຢືມແລະຍັງຈ່າຍເງິນກູ້ໃຫ້ຜູ້ກູ້ຢືມອີກດ້ວຍ, This account is capital account which is used to allocate capital for loan disbursal account ,ບັນຊີນີ້ແມ່ນບັນຊີທຶນເຊິ່ງຖືກ ນຳ ໃຊ້ເພື່ອຈັດສັນທຶນ ສຳ ລັບບັນຊີການເບີກຈ່າຍເງິນກູ້, diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv index 44bd069a7d..72294ee527 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -286,7 +286,6 @@ Auto Repeat,Automatinis kartojimas, Auto repeat document updated,Auto pakartotinis dokumentas atnaujintas, Automotive,Automobiliai, Available,pasiekiamas, -Available Leaves,Galimos lapai, Available Qty,Turimas Kiekis, Available Selling,Galima parduoti, Available for use date is required,Reikia naudoti datą, @@ -1122,7 +1121,6 @@ Hub Category,Hub kategorija, Hub Sync ID,Hub Sync ID, Human Resource,Žmogiškasis išteklis, Human Resources,Žmogiškieji ištekliai, -IFSC Code,IFSC kodas, IGST Amount,IGST suma, IP Address,IP adresas, ITC Available (whether in full op part),ITC yra prieinamas (ar visa jo dalis), @@ -1666,7 +1664,6 @@ Organization Name,Organizacijos pavadinimas, Other,kitas, Other Reports,Kiti pranešimai, "Other outward supplies(Nil rated,Exempted)","Kiti išoriniai reikmenys (nulinis, neapmokestinamas)", -Others,Kiti, Out Qty,iš Kiekis, Out Value,iš Vertė, Out of Order,Neveikia, @@ -2812,7 +2809,6 @@ Total (Credit),Iš viso (kreditų), Total (Without Tax),Iš viso (be mokesčio), Total Achieved,Iš viso Pasiektas, Total Actual,Iš viso Tikrasis, -Total Allocated Leaves,Iš viso paskirstytų lapų, Total Amount,Visas kiekis, Total Amount Credited,Iš viso kredituota suma, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Iš viso taikomi mokesčiai į pirkimo kvito sumų lentelė turi būti tokios pačios kaip viso mokesčių ir rinkliavų, @@ -2922,7 +2918,6 @@ Updating Variants...,Atnaujinami variantai ..., Upload your letter head and logo. (you can edit them later).,Įkelti savo laiške galvą ir logotipą. (Galite redaguoti juos vėliau)., Upper Income,viršutinė pajamos, Use Sandbox,Naudokite Smėlio, -Used Leaves,Naudotos lapai, User,Vartotojas, User ID,Vartotojo ID, User ID not set for Employee {0},Vartotojo ID nenustatyti Darbuotojo {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Darbo užmokesčio išlaidų centras, Approvers,Patvirtina, The first Approver in the list will be set as the default Approver.,Pirmasis patvirtintojas sąraše bus nustatytas kaip numatytasis patvirtintojas., Shift Request Approver,„Shift Request Approver“, -PAN Number,PAN numeris, Provident Fund Account,„Provident Fund“ sąskaita, MICR Code,MICR kodas, Repay unclaimed amount from salary,Iš atlyginimo grąžinkite neprašytą sumą, Deduction from salary,Išskaičiavimas iš atlyginimo, -Expired Leaves,Pasibaigę lapai, If this is not checked the loan by default will be considered as a Demand Loan,"Jei tai nėra pažymėta, paskola pagal numatytuosius nustatymus bus laikoma paklausos paskola", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ši sąskaita naudojama paskolos grąžinimo iš skolininko rezervavimui ir paskolos paskolos gavėjui išmokėjimui, This account is capital account which is used to allocate capital for loan disbursal account ,"Ši sąskaita yra kapitalo sąskaita, naudojama paskirstyti kapitalą paskolos išmokėjimo sąskaitai", diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv index 131f94acb6..c95189c29a 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto atkārtot, Auto repeat document updated,Auto atkārtots dokuments ir atjaunināts, Automotive,Automobiļu, Available,Pieejams, -Available Leaves,Pieejamie lapas, Available Qty,Pieejams Daudz, Available Selling,Pieejams pārdošana, Available for use date is required,Pieejams izmantošanas datums ir nepieciešams, @@ -1122,7 +1121,6 @@ Hub Category,Hub kategorijas, Hub Sync ID,Hub Sync ID, Human Resource,Cilvēkresursi, Human Resources,Cilvēkresursi, -IFSC Code,IFSC kods, IGST Amount,IGST summa, IP Address,IP adrese, ITC Available (whether in full op part),Pieejams ITC (vai nu pilnā variantā), @@ -1666,7 +1664,6 @@ Organization Name,Organizācijas nosaukums, Other,Cits, Other Reports,citas Ziņojumi, "Other outward supplies(Nil rated,Exempted)","Citi izejmateriāli (bez nulles, ar atbrīvojumu)", -Others,Pārējie, Out Qty,Out Daudz, Out Value,out Value, Out of Order,Nestrādā, @@ -2812,7 +2809,6 @@ Total (Credit),Kopā (kredīts), Total (Without Tax),Kopā (bez nodokļiem), Total Achieved,Kopā Izpildīts, Total Actual,Kopā Faktiskais, -Total Allocated Leaves,Kopējais izdalīto lapu skaits, Total Amount,Kopējā summa, Total Amount Credited,Kopējā kredīta summa, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Kopā piemērojamām izmaksām, kas pirkuma čeka Items galda jābūt tāds pats kā Kopā nodokļiem un nodevām", @@ -2922,7 +2918,6 @@ Updating Variants...,Notiek variantu atjaunināšana ..., Upload your letter head and logo. (you can edit them later).,Augšupielādēt jūsu vēstules galva un logo. (Jūs varat rediģēt tos vēlāk)., Upper Income,Upper Ienākumi, Use Sandbox,Izmantot Sandbox, -Used Leaves,Izmantotās lapas, User,Lietotājs, User ID,Lietotāja ID, User ID not set for Employee {0},"Lietotāja ID nav noteikts, Darbinieka {0}", @@ -7949,12 +7944,10 @@ Payroll Cost Center,Algas izmaksu centrs, Approvers,Apstiprinātāji, The first Approver in the list will be set as the default Approver.,Pirmais apstiprinātājs sarakstā tiks iestatīts kā noklusējuma apstiprinātājs., Shift Request Approver,Maiņas pieprasījuma apstiprinātājs, -PAN Number,PAN numurs, Provident Fund Account,Provident Fund konts, MICR Code,MICR kods, Repay unclaimed amount from salary,Atmaksājiet nepieprasīto summu no algas, Deduction from salary,Atskaitīšana no algas, -Expired Leaves,"Lapas, kurām beidzies derīguma termiņš", If this is not checked the loan by default will be considered as a Demand Loan,"Ja tas nav pārbaudīts, aizdevums pēc noklusējuma tiks uzskatīts par Pieprasījuma aizdevumu", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Šis konts tiek izmantots, lai rezervētu aizdevuma atmaksu no aizņēmēja un arī aizdevuma izmaksu aizņēmējam", This account is capital account which is used to allocate capital for loan disbursal account ,"Šis konts ir kapitāla konts, ko izmanto, lai piešķirtu kapitālu aizdevuma izmaksas kontam", diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv index 1084b1213f..a322861413 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -286,7 +286,6 @@ Auto Repeat,Автоматско повторување, Auto repeat document updated,Автоматско повторување на документот се ажурира, Automotive,Автомобилски, Available,Достапни, -Available Leaves,Достапни листови, Available Qty,На располагање Количина, Available Selling,Достапно Продажба, Available for use date is required,Достапен е датум за користење, @@ -1122,7 +1121,6 @@ Hub Category,Категорија на категории, Hub Sync ID,Hub Sync ID, Human Resource,Човечки ресурси, Human Resources,Човечки ресурси, -IFSC Code,IFSC код, IGST Amount,Износ на IGST, IP Address,IP адреса, ITC Available (whether in full op part),ИТЦ достапен (без разлика дали е во целост опција), @@ -1666,7 +1664,6 @@ Organization Name,Име на организацијата, Other,Други, Other Reports,Други извештаи, "Other outward supplies(Nil rated,Exempted)","Други надворешни материјали (нула со оценка, Ослободена)", -Others,"Други, пак,", Out Qty,Од Количина, Out Value,Од вредност, Out of Order,Надвор од нарачката, @@ -2812,7 +2809,6 @@ Total (Credit),Вкупно (кредит), Total (Without Tax),Вкупно (без данок), Total Achieved,Вкупно Постигнати, Total Actual,Вкупно Крај, -Total Allocated Leaves,Вкупно распределени листови, Total Amount,Вкупен износ, Total Amount Credited,Вкупен износ на кредит, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Вкупно применливи давачки во Набавка Потврда Предмети маса мора да биде иста како и вкупните даноци и давачки, @@ -2922,7 +2918,6 @@ Updating Variants...,Ажурирање на варијантите ..., Upload your letter head and logo. (you can edit them later).,Внеси писмо главата и логото. (Можете да ги менувате подоцна)., Upper Income,Горниот дел од приходите, Use Sandbox,Користете Sandbox, -Used Leaves,Користени листови, User,Корисник, User ID,ID на корисникот, User ID not set for Employee {0},ID на корисникот не е поставена за вработените {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Центар за плати на плати, Approvers,Аверверзно, The first Approver in the list will be set as the default Approver.,Првиот Одобрувач во списокот ќе биде поставен како стандарден Одобрувач., Shift Request Approver,Одобрувач на барање за смена, -PAN Number,Број на ПАН, Provident Fund Account,Сметка на фондот за провиденција, MICR Code,МИКР-код, Repay unclaimed amount from salary,Отплати небаран износ од плата, Deduction from salary,Намалување од плата, -Expired Leaves,Истечени лисја, If this is not checked the loan by default will be considered as a Demand Loan,"Доколку ова не е проверено, заемот по дифолт ќе се смета како заем за побарувачка", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Оваа сметка се користи за резервирање на отплати на заеми од заемопримачот и исто така за исплата на заеми на заемопримачот, This account is capital account which is used to allocate capital for loan disbursal account ,Оваа сметка е капитална сметка што се користи за алокација на капитал за сметка за исплата на заем, diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv index 2ddec217da..c74d4a0dc5 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -286,7 +286,6 @@ Auto Repeat,സ്വയം ആവർത്തിക്കുക, Auto repeat document updated,സ്വയം ആവർത്തന പ്രമാണം അപ്ഡേറ്റുചെയ്തു, Automotive,ഓട്ടോമോട്ടീവ്, Available,ലഭ്യമായ, -Available Leaves,ലഭ്യമായ ഇലകൾ, Available Qty,ലഭ്യമായ Qty, Available Selling,വിൽക്കൽ ലഭ്യമാണ്, Available for use date is required,ഉപയോഗ തീയതിക്ക് ആവശ്യമാണ്, @@ -1122,7 +1121,6 @@ Hub Category,ഹബ് വിഭാഗം, Hub Sync ID,ഹബ് സമന്വയ ID, Human Resource,മാനവ വിഭവശേഷി, Human Resources,ഹ്യൂമൻ റിസോഴ്സസ്, -IFSC Code,IFSC കോഡ്, IGST Amount,IGST തുക, IP Address,IP വിലാസം, ITC Available (whether in full op part),ഐടിസി ലഭ്യമാണ് (പൂർണ്ണമായ ഭാഗമാണെങ്കിലും), @@ -1666,7 +1664,6 @@ Organization Name,സംഘടനയുടെ പേര്, Other,മറ്റുള്ളവ, Other Reports,മറ്റ് റിപ്പോർട്ടുകളിൽ, "Other outward supplies(Nil rated,Exempted)","മറ്റ് ബാഹ്യ വിതരണങ്ങൾ (ഇല്ല റേറ്റുചെയ്തു, ഒഴിവാക്കി)", -Others,മറ്റുള്ളവ, Out Qty,Qty ഔട്ട്, Out Value,മൂല്യം ഔട്ട്, Out of Order,പ്രവർത്തനരഹിതം, @@ -2812,7 +2809,6 @@ Total (Credit),ആകെ (ക്രെഡിറ്റ്), Total (Without Tax),ആകെ (നികുതി കൂടാതെ), Total Achieved,മികച്ച വിജയം ആകെ, Total Actual,യഥാർത്ഥ ആകെ, -Total Allocated Leaves,ആകെ അനുവദിച്ച ഇലകൾ, Total Amount,മൊത്തം തുക, Total Amount Credited,മൊത്തം തുക ലഭിച്ചു, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,വാങ്ങൽ രസീത് ഇനങ്ങൾ പട്ടികയിൽ ആകെ ബാധകമായ നിരക്കുകളും ആകെ നികുതി ചാർജുകളും തുല്യമായിരിക്കണം, @@ -2922,7 +2918,6 @@ Updating Variants...,വേരിയന്റുകൾ അപ്‌ഡേറ് Upload your letter head and logo. (you can edit them later).,നിങ്ങളുടെ കത്ത് തലയും ലോഗോ അപ്ലോഡ്. (നിങ്ങൾക്ക് പിന്നീട് എഡിറ്റ് ചെയ്യാൻ കഴിയും)., Upper Income,അപ്പർ ആദായ, Use Sandbox,താങ്കളെ, -Used Leaves,ഉപയോഗിച്ച ഇലകൾ, User,ഉപയോക്താവ്, User ID,യൂസർ ഐഡി, User ID not set for Employee {0},ഉപയോക്തൃ ഐഡി ജീവനക്കാരുടെ {0} വെച്ചിരിക്കുന്നു അല്ല, @@ -7949,12 +7944,10 @@ Payroll Cost Center,ശമ്പള ചെലവ് കേന്ദ്രം, Approvers,അംഗങ്ങൾ, The first Approver in the list will be set as the default Approver.,ലിസ്റ്റിലെ ആദ്യ അംഗീകാരം സ്ഥിരസ്ഥിതി അംഗീകാരമായി സജ്ജമാക്കും., Shift Request Approver,ഷിഫ്റ്റ് അഭ്യർത്ഥന അംഗീകാരം, -PAN Number,പാൻ നമ്പർ, Provident Fund Account,പ്രൊവിഡന്റ് ഫണ്ട് അക്കൗണ്ട്, MICR Code,MICR കോഡ്, Repay unclaimed amount from salary,ക്ലെയിം ചെയ്യാത്ത തുക ശമ്പളത്തിൽ നിന്ന് തിരിച്ചടയ്ക്കുക, Deduction from salary,ശമ്പളത്തിൽ നിന്ന് കിഴിവ്, -Expired Leaves,കാലഹരണപ്പെട്ട ഇലകൾ, If this is not checked the loan by default will be considered as a Demand Loan,ഇത് പരിശോധിച്ചില്ലെങ്കിൽ സ്ഥിരസ്ഥിതിയായി വായ്പ ഒരു ഡിമാൻഡ് വായ്പയായി കണക്കാക്കും, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,വായ്പക്കാരനിൽ നിന്ന് വായ്പ തിരിച്ചടവ് ബുക്ക് ചെയ്യുന്നതിനും വായ്പക്കാരന് വായ്പ വിതരണം ചെയ്യുന്നതിനും ഈ അക്കൗണ്ട് ഉപയോഗിക്കുന്നു, This account is capital account which is used to allocate capital for loan disbursal account ,വായ്പാ വിതരണ അക്കൗണ്ടിനായി മൂലധനം അനുവദിക്കുന്നതിന് ഉപയോഗിക്കുന്ന മൂലധന അക്കൗണ്ടാണ് ഈ അക്കൗണ്ട്, diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv index 4206335dd0..28fab20973 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -286,7 +286,6 @@ Auto Repeat,ऑटो पुनरावृत्ती, Auto repeat document updated,स्वयं पुनरावृत्ती कागदजत्र अद्यतनित केले, Automotive,ऑटोमोटिव्ह, Available,उपलब्ध, -Available Leaves,उपलब्ध पाने, Available Qty,उपलब्ध Qty, Available Selling,उपलब्ध विक्री, Available for use date is required,वापरण्याच्या तारखेसाठी उपलब्ध असणे आवश्यक आहे, @@ -1122,7 +1121,6 @@ Hub Category,हब श्रेणी, Hub Sync ID,हब सिंक ID, Human Resource,मानव संसाधन, Human Resources,मानव संसाधन, -IFSC Code,आयएफएससी कोड, IGST Amount,आयजीएसटी रक्कम, IP Address,IP पत्ता, ITC Available (whether in full op part),आयटीसी उपलब्ध (पूर्ण ऑप भागातील आहे की नाही), @@ -1666,7 +1664,6 @@ Organization Name,संस्थेचे नाव, Other,इतर, Other Reports,इतर अहवाल, "Other outward supplies(Nil rated,Exempted)","इतर बाह्य पुरवठा (शून्य रेट नाही, सूट दिलेला)", -Others,इतर, Out Qty,आउट Qty, Out Value,मूल्य Qty, Out of Order,नियमबाह्य, @@ -2812,7 +2809,6 @@ Total (Credit),एकूण (क्रेडिट), Total (Without Tax),एकूण (कर न करता), Total Achieved,एकूण गाठले, Total Actual,वास्तविक एकूण, -Total Allocated Leaves,एकूण वाटप पाने, Total Amount,एकूण रक्कम, Total Amount Credited,एकूण रक्कम श्रेय, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,खरेदी पावती आयटम टेबल एकूण लागू शुल्क एकूण कर आणि शुल्क म्हणून समान असणे आवश्यक आहे, @@ -2922,7 +2918,6 @@ Updating Variants...,रूपे अद्यतनित करीत आह Upload your letter head and logo. (you can edit them later).,आपले पत्र डोके आणि लोगो अपलोड करा. (आपण नंतर संपादित करू शकता)., Upper Income,उच्च उत्पन्न, Use Sandbox,Sandbox वापर, -Used Leaves,वापरले पाने, User,वापरकर्ता, User ID,वापरकर्ता आयडी, User ID not set for Employee {0},वापरकर्ता आयडी कर्मचारी {0}साठी सेट नाही, @@ -7949,12 +7944,10 @@ Payroll Cost Center,पेरोल खर्च केंद्र, Approvers,वाद, The first Approver in the list will be set as the default Approver.,सूचीतील प्रथम मंजूर डीफॉल्ट मंजूर म्हणून सेट केला जाईल., Shift Request Approver,शिफ्ट विनंती मंजूर, -PAN Number,पॅन क्रमांक, Provident Fund Account,भविष्य निर्वाह निधी, MICR Code,एमआयसीआर कोड, Repay unclaimed amount from salary,वेतनातून दावा न केलेली रक्कम परत द्या, Deduction from salary,वेतनातून वजावट, -Expired Leaves,कालबाह्य झालेले पाने, If this is not checked the loan by default will be considered as a Demand Loan,हे तपासले नाही तर डीफॉल्टनुसार कर्ज डिमांड लोन मानले जाईल, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,हे खाते कर्जदाराकडून कर्ज परतफेड बुक करण्यासाठी आणि कर्जदाराला कर्ज वितरणासाठी वापरले जाते., This account is capital account which is used to allocate capital for loan disbursal account ,हे खाते भांडवल खाते आहे जे कर्ज वितरण खात्यासाठी भांडवल वाटप करण्यासाठी वापरले जाते, diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv index ebfc36cc04..f3c53f6122 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Ulang, Auto repeat document updated,Dokumen pengulang automatik dikemas kini, Automotive,Automotif, Available,Tersedia, -Available Leaves,Daun yang disediakan, Available Qty,Terdapat Qty, Available Selling,Jualan Sedia Ada, Available for use date is required,Tersedia untuk tarikh penggunaan, @@ -1122,7 +1121,6 @@ Hub Category,Kategori Hab, Hub Sync ID,ID Sync Hub, Human Resource,Sumber Manusia, Human Resources,Sumber Manusia, -IFSC Code,Kod IFSC, IGST Amount,Jumlah IGST, IP Address,Alamat IP, ITC Available (whether in full op part),ITC Tersedia (sama ada dalam bahagian penuh), @@ -1666,7 +1664,6 @@ Organization Name,Nama Pertubuhan, Other,Lain-lain, Other Reports,Laporan Lain, "Other outward supplies(Nil rated,Exempted)","Bekalan luaran lain (Tiada nilai, Dikecualikan)", -Others,Lain, Out Qty,Keluar Qty, Out Value,Nilai Keluar, Out of Order,Telah habis, @@ -2812,7 +2809,6 @@ Total (Credit),Jumlah: (Kredit), Total (Without Tax),Jumlah (Tanpa Cukai), Total Achieved,Jumlah Pencapaian, Total Actual,Jumlah Sebenar, -Total Allocated Leaves,Jumlah Dikelilingi Daun, Total Amount,Jumlah, Total Amount Credited,Jumlah Jumlah Dikreditkan, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Jumlah Caj Terpakai di Purchase meja Resit Item mesti sama dengan Jumlah Cukai dan Caj, @@ -2922,7 +2918,6 @@ Updating Variants...,Mengemas kini Variasi ..., Upload your letter head and logo. (you can edit them later).,Memuat naik kepala surat dan logo. (Anda boleh mengeditnya kemudian)., Upper Income,Pendapatan Atas, Use Sandbox,Penggunaan Sandbox, -Used Leaves,Daun yang digunakan, User,Pengguna, User ID,ID Pengguna, User ID not set for Employee {0},ID Pengguna tidak ditetapkan untuk Pekerja {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Pusat Kos Gaji, Approvers,Meluluskan, The first Approver in the list will be set as the default Approver.,Pelulus pertama dalam senarai akan ditetapkan sebagai Pelulus lalai., Shift Request Approver,Kelulusan Permintaan Shift, -PAN Number,Nombor PAN, Provident Fund Account,Akaun Kumpulan Wang Simpanan, MICR Code,Kod MICR, Repay unclaimed amount from salary,Bayar balik jumlah yang tidak dituntut dari gaji, Deduction from salary,Potongan gaji, -Expired Leaves,Daun Tamat Tempoh, If this is not checked the loan by default will be considered as a Demand Loan,"Sekiranya ini tidak diperiksa, pinjaman secara lalai akan dianggap sebagai Permintaan Pinjaman", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Akaun ini digunakan untuk membuat pembayaran pinjaman dari peminjam dan juga mengeluarkan pinjaman kepada peminjam, This account is capital account which is used to allocate capital for loan disbursal account ,Akaun ini adalah akaun modal yang digunakan untuk memperuntukkan modal untuk akaun pengeluaran pinjaman, diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv index 82015c82de..ab56fee61d 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -286,7 +286,6 @@ Auto Repeat,အော်တိုထပ်ခါတလဲလဲ, Auto repeat document updated,အော်တိုထပ်စာရွက်စာတမ်း updated, Automotive,မော်တော်ယာဉ်, Available,ရရှိနိုင်, -Available Leaves,ရရှိနိုင်အရွက်, Available Qty,ရရှိနိုင်သည့် Qty, Available Selling,ရောင်းချခြင်းရရှိနိုင်, Available for use date is required,အသုံးပြုမှုနေ့စွဲများအတွက်ရရှိနိုင်လိုအပ်ပါသည်, @@ -1122,7 +1121,6 @@ Hub Category,hub Category:, Hub Sync ID,hub Sync ကို ID ကို, Human Resource,လူ့စွမ်းအားအရင်းအမြစ်, Human Resources,လူ့အင်အားအရင်းအမြစ်, -IFSC Code,IFSC Code ကို, IGST Amount,IGST ငွေပမာဏ, IP Address,IP Address ကို, ITC Available (whether in full op part),(အပြည့်အဝ op အစိတ်အပိုင်းအတွက်ရှိမရှိ) ရရှိနိုင် ITC, @@ -1666,7 +1664,6 @@ Organization Name,အစည်းအရုံးအမည်, Other,အခြား, Other Reports,အခြားအစီရင်ခံစာများ, "Other outward supplies(Nil rated,Exempted)","သည်အခြားအပြင်ထောက်ပံ့ရေးပစ္စည်းများ (nil rated, ကင်းလွတ်ခွင့်)", -Others,အခြားသူများ, Out Qty,Qty out, Out Value,Value တစ်ခုအထဲက, Out of Order,အမိန့်များထဲက, @@ -2812,7 +2809,6 @@ Total (Credit),စုစုပေါင်း (Credit), Total (Without Tax),(အခွန်မပါ) စုစုပေါင်း, Total Achieved,အကောင်အထည်ဖော်ခဲ့သောစုစုပေါင်း, Total Actual,အမှန်တကယ်စုစုပေါင်း, -Total Allocated Leaves,စုစုပေါင်းခွဲဝေအရွက်, Total Amount,စုစုပေါင်းတန်ဘိုး, Total Amount Credited,စုစုပေါင်းငွေပမာဏအသိအမှတ်ပြု, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,အရစ်ကျငွေလက်ခံပြေစာပစ္စည်းများ table ထဲမှာစုစုပေါင်းသက်ဆိုင်သောစွပ်စွဲချက်စုစုပေါင်းအခွန်နှင့်စွပ်စွဲချက်အဖြစ်အတူတူပင်ဖြစ်ရပါမည်, @@ -2922,7 +2918,6 @@ Updating Variants...,အသစ်ပြောင်းခြင်းမူက Upload your letter head and logo. (you can edit them later).,သင့်ရဲ့စာကိုဦးခေါင်းနှင့်လိုဂို upload ။ (သင်နောက်ပိုင်းမှာသူတို့ကိုတည်းဖြတ်နိုင်သည်) ။, Upper Income,အထက်ဝင်ငွေခွန်, Use Sandbox,Sandbox ကိုသုံးပါ, -Used Leaves,တပတ်ရစ်အရွက်, User,အသုံးပြုသူကို, User ID,သုံးစွဲသူအိုင်ဒီ, User ID not set for Employee {0},အသုံးပြုသူ ID န်ထမ်း {0} သည်စွဲလမ်းခြင်းမ, @@ -7949,12 +7944,10 @@ Payroll Cost Center,လုပ်ခလစာကုန်ကျစရိတ်စ Approvers,ခွင့်ပြုချက်, The first Approver in the list will be set as the default Approver.,စာရင်းထဲတွင်ပထမအတည်ပြုချက်ကိုမူလအတည်ပြုချက်အဖြစ်သတ်မှတ်မည်။, Shift Request Approver,Shift Request Approver, -PAN Number,PAN နံပါတ်, Provident Fund Account,Provident ရန်ပုံငွေအကောင့်, MICR Code,MICR ကုဒ်, Repay unclaimed amount from salary,မတောင်းဆိုသောငွေကိုလစာမှပြန်ပေးပါ, Deduction from salary,လစာမှနှုတ်ယူခြင်း, -Expired Leaves,သက်တမ်းကုန်ဆုံးသောအရွက်, If this is not checked the loan by default will be considered as a Demand Loan,အကယ်၍ ၎င်းကိုမစစ်ဆေးပါကပုံမှန်အားဖြင့်ချေးငွေကို Demand Loan အဖြစ်သတ်မှတ်မည်, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,ဒီအကောင့်ကိုငွေချေးသူထံမှချေးငွေပြန်ဆပ်မှုကိုကြိုတင်မှာကြားပြီးငွေချေးသူမှချေးငွေများထုတ်ပေးသည်, This account is capital account which is used to allocate capital for loan disbursal account ,ဤအကောင့်သည်ငွေရင်းငွေစာရင်းဖြစ်သည်, diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index 862d585246..839459ce10 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto herhalen, Auto repeat document updated,Automatisch herhaalde document bijgewerkt, Automotive,Automotive, Available,Beschikbaar, -Available Leaves,Beschikbare bladeren, Available Qty,Beschikbaar aantal, Available Selling,Beschikbare verkoop, Available for use date is required,Beschikbaar voor gebruik datum is vereist, @@ -1122,7 +1121,6 @@ Hub Category,Hubcategorie, Hub Sync ID,Hub Sync ID, Human Resource,Human Resource, Human Resources,Human Resources, -IFSC Code,IFSC-code, IGST Amount,IGST Bedrag, IP Address,IP adres, ITC Available (whether in full op part),ITC beschikbaar (al dan niet volledig), @@ -1666,7 +1664,6 @@ Organization Name,Naam van de Organisatie, Other,Ander, Other Reports,Andere rapporten, "Other outward supplies(Nil rated,Exempted)","Andere uitgaande leveringen (nul, vrijgesteld)", -Others,anderen, Out Qty,out Aantal, Out Value,out Value, Out of Order,Out of Order, @@ -2812,7 +2809,6 @@ Total (Credit),Totaal (Credit), Total (Without Tax),Totaal (zonder btw), Total Achieved,Totaal Bereikt, Total Actual,Totaal Werkelijke, -Total Allocated Leaves,Totaal toegewezen bladeren, Total Amount,Totaal bedrag, Total Amount Credited,Totaal gecrediteerd bedrag, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Totaal van toepassing zijnde kosten in Kwitantie Items tabel moet hetzelfde zijn als de totale belastingen en heffingen, @@ -2922,7 +2918,6 @@ Updating Variants...,Varianten bijwerken ..., Upload your letter head and logo. (you can edit them later).,Upload uw brief hoofd en logo. (Je kunt ze later bewerken)., Upper Income,Bovenste Inkomen, Use Sandbox,Gebruik Sandbox, -Used Leaves,Gebruikte bladeren, User,Gebruiker, User ID,Gebruikers-ID, User ID not set for Employee {0},Gebruikers-ID niet ingesteld voor werknemer {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Loonkostencentrum, Approvers,Goedkeurders, The first Approver in the list will be set as the default Approver.,De eerste goedkeurder in de lijst wordt ingesteld als de standaard goedkeurder., Shift Request Approver,Shift Request Approver, -PAN Number,PAN-nummer, Provident Fund Account,Provident Fund-account, MICR Code,MICR-code, Repay unclaimed amount from salary,Betaal niet-opgeëiste bedrag terug van salaris, Deduction from salary,Inhouding op salaris, -Expired Leaves,Verlopen bladeren, If this is not checked the loan by default will be considered as a Demand Loan,"Als dit niet is aangevinkt, wordt de lening standaard beschouwd als een opeisbare lening", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Deze rekening wordt gebruikt voor het boeken van aflossingen van leningen van de lener en ook voor het uitbetalen van leningen aan de lener, This account is capital account which is used to allocate capital for loan disbursal account ,Deze rekening is een kapitaalrekening die wordt gebruikt om kapitaal toe te wijzen voor het uitbetalen van leningen, diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv index 0c1cfac9b6..7491ab34cf 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Repeat, Auto repeat document updated,Automatisk gjentatt dokument oppdatert, Automotive,Automotive, Available,Tilgjengelig, -Available Leaves,Tilgjengelige blader, Available Qty,Tilgjengelig antall, Available Selling,Tilgjengelig salg, Available for use date is required,Tilgjengelig for bruk er nødvendig, @@ -1122,7 +1121,6 @@ Hub Category,Hub Kategori, Hub Sync ID,Hub Sync ID, Human Resource,Menneskelig Resurs, Human Resources,Menneskelige ressurser, -IFSC Code,IFSC-kode, IGST Amount,IGST Beløp, IP Address,IP adresse, ITC Available (whether in full op part),ITC tilgjengelig (om fullstendig del), @@ -1666,7 +1664,6 @@ Organization Name,Organization Name, Other,Andre, Other Reports,Andre rapporter, "Other outward supplies(Nil rated,Exempted)","Andre forsyninger (ikke vurdert, unntatt)", -Others,Annet, Out Qty,Ut Antall, Out Value,ut Verdi, Out of Order,I ustand, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Credit), Total (Without Tax),Totalt (uten skatt), Total Achieved,Oppnådd Total, Total Actual,Total Actual, -Total Allocated Leaves,Totalt tildelte blad, Total Amount,Totalbeløp, Total Amount Credited,Totalt beløp krevet, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Totalt gjeldende avgifter i kvitteringen Elementer tabellen må være det samme som total skatter og avgifter, @@ -2922,7 +2918,6 @@ Updating Variants...,Oppdaterer varianter ..., Upload your letter head and logo. (you can edit them later).,Last opp din brevhode og logo. (Du kan redigere dem senere)., Upper Income,Øvre inntekt, Use Sandbox,bruk Sandbox, -Used Leaves,Brukte blad, User,Bruker, User ID,bruker-ID, User ID not set for Employee {0},Bruker-ID ikke satt for Employee {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Lønns kostnadssenter, Approvers,Tilstridende, The first Approver in the list will be set as the default Approver.,Den første godkjenneren i listen blir angitt som standard godkjenner., Shift Request Approver,Godkjenning av skiftforespørsel, -PAN Number,PAN-nummer, Provident Fund Account,Forsikringskonto, MICR Code,MICR-kode, Repay unclaimed amount from salary,Tilbakebetalt uavhentet beløp fra lønn, Deduction from salary,Trekk fra lønn, -Expired Leaves,Utløpte blader, If this is not checked the loan by default will be considered as a Demand Loan,"Hvis dette ikke er sjekket, vil lånet som standard bli betraktet som et etterspørsel", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Denne kontoen brukes til å bestille tilbakebetaling av lån fra låntaker og også utbetale lån til låner, This account is capital account which is used to allocate capital for loan disbursal account ,Denne kontoen er en kapitalkonto som brukes til å fordele kapital til utbetaling av lån, diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv index 272e2fcda6..cfb1831198 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -285,7 +285,6 @@ Auto Material Requests Generated,Wnioski Auto Materiał Generated, Auto Repeat,Auto Repeat, Auto repeat document updated,Automatycznie powtórzony dokument został zaktualizowany, Available,Dostępny, -Available Leaves,Dostępne Nieobecności, Available Qty,Dostępne szt, Available Selling,Dostępne sprzedawanie, Available for use date is required,Dostępna jest data przydatności do użycia, @@ -1121,7 +1120,6 @@ Hub Category,Kategoria koncentratora, Hub Sync ID,Identyfikator Hub Sync, Human Resource,Zasoby ludzkie, Human Resources,Kadry, -IFSC Code,Kod IFSC, IGST Amount,Wielkość IGST, IP Address,Adres IP, ITC Available (whether in full op part),Dostępne ITC (czy w pełnej wersji), @@ -1659,7 +1657,6 @@ Organization Name,Nazwa organizacji, Other,Inne, Other Reports,Inne raporty, "Other outward supplies(Nil rated,Exempted)","Inne dostawy zewnętrzne (bez oceny, zwolnione)", -Others,Inni, Out Qty,Brak Ilości, Out Value,Brak Wartości, Out of Order,Nieczynny, @@ -2788,7 +2785,6 @@ Total (Credit),Razem (Credit), Total (Without Tax),Razem (bez podatku), Total Achieved,Razem Osiągnięte, Total Actual,Razem Rzeczywisty, -Total Allocated Leaves,Całkowicie Przydzielone Nieobecności, Total Amount,Wartość całkowita, Total Amount Credited,Całkowita kwota kredytu, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Wszystkich obowiązujących opłat w ZAKUPU Elementy tabeli muszą być takie same jak Wszystkich podatkach i opłatach, @@ -2898,7 +2894,6 @@ Updating Variants...,Aktualizowanie wariantów ..., Upload your letter head and logo. (you can edit them later).,Prześlij nagłówek firmowy i logo. (Można je edytować później)., Upper Income,Wzrost Wpływów, Use Sandbox,Korzystanie Sandbox, -Used Leaves,Wykorzystane Nieobecności, User,Użytkownik, User ID,ID Użytkownika, User ID not set for Employee {0},ID Użytkownika nie ustawiony dla Pracownika {0}, @@ -7860,12 +7855,10 @@ Payroll Cost Center,Centrum kosztów listy płac, Approvers,Osoby zatwierdzające, The first Approver in the list will be set as the default Approver.,Pierwsza osoba zatwierdzająca na liście zostanie ustawiona jako domyślna osoba zatwierdzająca., Shift Request Approver,Zatwierdzający prośbę o zmianę, -PAN Number,Numer PAN, Provident Fund Account,Konto funduszu rezerwowego, MICR Code,Kod MICR, Repay unclaimed amount from salary,Zwróć nieodebraną kwotę z wynagrodzenia, Deduction from salary,Odliczenie od wynagrodzenia, -Expired Leaves,Wygasłe liście, If this is not checked the loan by default will be considered as a Demand Loan,"Jeśli opcja ta nie zostanie zaznaczona, pożyczka domyślnie zostanie uznana za pożyczkę na żądanie", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"To konto służy do księgowania spłat pożyczki od pożyczkobiorcy, a także do wypłaty pożyczki pożyczkobiorcy", This account is capital account which is used to allocate capital for loan disbursal account ,"Rachunek ten jest rachunkiem kapitałowym, który służy do alokacji kapitału na rachunek wypłat pożyczki", diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv index 8d4e4673b2..8740441d9b 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -286,7 +286,6 @@ Auto Repeat,د اتوماتیک تکرار, Auto repeat document updated,د اتوم بیاکتنه سند تازه شوی, Automotive,مشين, Available,شته, -Available Leaves,شتون لري, Available Qty,موجود Qty, Available Selling,موجود پلورل, Available for use date is required,د کارولو نیټه اړینه ده, @@ -1122,7 +1121,6 @@ Hub Category,حب کټګوري, Hub Sync ID,د کور همکاري ID, Human Resource,د بشري منابعو, Human Resources,بشري منابع, -IFSC Code,د IFSC کود, IGST Amount,د IGST مقدار, IP Address,IP پته, ITC Available (whether in full op part),ITC شتون لري (ایا په بشپړ انتخابي برخه کې), @@ -1666,7 +1664,6 @@ Organization Name,د ادارې نوم, Other,نور, Other Reports,نور راپورونه, "Other outward supplies(Nil rated,Exempted)",نور خارجی اکمالات (نیل ارزول شوی ، معاف شوی), -Others,نور, Out Qty,له جملې څخه Qty, Out Value,له جملې څخه د ارزښت, Out of Order,له کاره وتلی, @@ -2812,7 +2809,6 @@ Total (Credit),Total (اعتبار), Total (Without Tax),ټول (د مالیې پرته), Total Achieved,Total السته, Total Actual,Total واقعي, -Total Allocated Leaves,ټول ټاکل شوي پاڼي, Total Amount,جمله پیسی, Total Amount Credited,ټولې پیسې اعتبار شوي, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,په رانيول رسيد توکي جدول ټولې د تطبیق په تور باید په توګه ټول ماليات او په تور ورته وي, @@ -2922,7 +2918,6 @@ Updating Variants...,د تغیراتو تازه کول ..., Upload your letter head and logo. (you can edit them later).,پورته ستاسو لیک مشر او لوګو. (کولی شئ چې وروسته د سمولو لپاره)., Upper Income,د مشرانو پر عايداتو, Use Sandbox,sandbox استعمال, -Used Leaves,استعمال شوي پاڼي, User,کارن, User ID,کارن نوم, User ID not set for Employee {0},کارن تذکرو لپاره د کارکونکو نه {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,د تادیاتو لګښت مرکز, Approvers,مخالفت, The first Approver in the list will be set as the default Approver.,په لیست کې لومړی تصویبونکی به د ډیفالټ تصویب کونکي په توګه وټاکل شي., Shift Request Approver,د شفټ غوښتنه تصویب, -PAN Number,د پان شمیره, Provident Fund Account,د راتلونکي فنډ حساب, MICR Code,د MICR کوډ, Repay unclaimed amount from salary,له تنخوا څخه نامعلومه اندازه پیسې بیرته ورکړئ, Deduction from salary,له معاش څخه تخفیف, -Expired Leaves,ختم شوې پاvesې, If this is not checked the loan by default will be considered as a Demand Loan,که چیرې دا چک نه شي نو په ډیفالټ ډول به د غوښتنې پور په توګه وګ .ل شي, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,دا حساب د پور ورکونکي څخه د پور بیرته تادیات کولو لپاره او هم پور اخیستونکي ته د پورونو توزیع لپاره کارول کیږي, This account is capital account which is used to allocate capital for loan disbursal account ,دا حساب د پانګوونې حساب دی چې د پور توزیع شوي حساب لپاره د پانګو ځانګړي کولو لپاره کارول کیږي, diff --git a/erpnext/translations/pt-BR.csv b/erpnext/translations/pt-BR.csv index 0b7fa7e2cd..fee6b5e7b1 100644 --- a/erpnext/translations/pt-BR.csv +++ b/erpnext/translations/pt-BR.csv @@ -286,7 +286,6 @@ Auto Repeat,Repetição Automática, Auto repeat document updated,Auto repetir documento atualizado, Automotive,Automotivo, Available,Disponível, -Available Leaves,Folhas Disponíveis, Available Qty,Qtde Disponível, Available Selling,Venda Disponível, Available for use date is required,Disponível para data de uso é obrigatório, @@ -1122,7 +1121,6 @@ Hub Category,Categoria Hub, Hub Sync ID,Identificação da Sincronização do Hub, Human Resource,Recursos Humanos, Human Resources,Recursos Humanos, -IFSC Code,Código IFSC, IGST Amount,Valor IGST, IP Address,Endereço de Ip, ITC Available (whether in full op part),ITC disponível (seja na parte operacional completa), @@ -1666,7 +1664,6 @@ Organization Name,Nome da Organização, Other,Outro, Other Reports,Relatórios Adicionais, "Other outward supplies(Nil rated,Exempted)","Outras fontes externas (valor nominal nominal, isentos)", -Others,Outros, Out Qty,Qtde Saída, Out Value,Valor Saída, Out of Order,Fora de Serviço, @@ -2812,7 +2809,6 @@ Total (Credit),Total (crédito), Total (Without Tax),Total (sem Imposto), Total Achieved,Total de Alcançados, Total Actual,Total Atual, -Total Allocated Leaves,Total de Folhas Alocadas, Total Amount,Valor Total, Total Amount Credited,Quantidade Total Creditada, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total de encargos aplicáveis em Purchase mesa Itens recibo deve ser o mesmo que o total Tributos e Encargos, @@ -2922,7 +2918,6 @@ Updating Variants...,Atualizando Variantes..., Upload your letter head and logo. (you can edit them later).,Publique seu timbre e logotipo. (Você pode editá-las mais tarde)., Upper Income,Alta Renda, Use Sandbox,Use Sandbox, -Used Leaves,Folhas Usadas, User,Usuário, User ID,ID de Usuário, User ID not set for Employee {0},ID de Usuário Não Definida Para Colaborador {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centro de Custo de Folha de Pagamento, Approvers,Aprovadores, The first Approver in the list will be set as the default Approver.,O primeiro aprovador da lista será definido como o aprovador padrão., Shift Request Approver,Aprovador de Solicitação de Turno, -PAN Number,Número PAN, Provident Fund Account,Conta do Fundo de Previdência, MICR Code,Código MICR, Repay unclaimed amount from salary,Reembolsar quantia não reclamada do salário, Deduction from salary,Dedução do salário, -Expired Leaves,Folhas Vencidas, If this is not checked the loan by default will be considered as a Demand Loan,Se esta opção não for marcada o empréstimo por padrão será considerado um empréstimo à vista, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Esta conta é usada para registrar reembolsos de empréstimos do mutuário e também desembolsar empréstimos para o mutuário, This account is capital account which is used to allocate capital for loan disbursal account ,Esta conta é a conta de capital que é usada para alocar capital para a conta de desembolso do empréstimo, diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv index 10f1b719b1..1d6b3fe182 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -286,7 +286,6 @@ Auto Repeat,Repetição Automática, Auto repeat document updated,Auto repetir documento atualizado, Automotive,Automóvel, Available,Disponível, -Available Leaves,Folhas Disponíveis, Available Qty,Qtd disponível, Available Selling,Venda disponível, Available for use date is required,Disponível para data de uso é obrigatório, @@ -1122,7 +1121,6 @@ Hub Category,Categoria Hub, Hub Sync ID,Identificação da Sincronização do Hub, Human Resource,Recursos humanos, Human Resources,Recursos humanos, -IFSC Code,Código IFSC, IGST Amount,Valor IGST, IP Address,Endereço de IP, ITC Available (whether in full op part),ITC disponível (seja na parte operacional completa), @@ -1666,7 +1664,6 @@ Organization Name,Nome da organização, Other,Outro, Other Reports,Outros relatórios, "Other outward supplies(Nil rated,Exempted)","Outras fontes externas (valor nominal nominal, isentos)", -Others,Outros, Out Qty,Qtd de Saída, Out Value,Valor de Saída, Out of Order,Fora de serviço, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Crédito), Total (Without Tax),Total (sem imposto), Total Achieved,Total Alcançado, Total Actual,Total real, -Total Allocated Leaves,Total de Folhas Alocadas, Total Amount,Valor total, Total Amount Credited,Quantidade Total Creditada, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total de encargos aplicáveis em Purchase mesa Itens recibo deve ser o mesmo que o total Tributos e Encargos, @@ -2922,7 +2918,6 @@ Updating Variants...,Atualizando variantes ..., Upload your letter head and logo. (you can edit them later).,Carregue o cabeçalho e logótipo da carta. (Pode editá-los mais tarde.), Upper Income,Rendimento Superior, Use Sandbox,Use Sandbox, -Used Leaves,Folhas Usadas, User,Do utilizador, User ID,ID de Utiliz., User ID not set for Employee {0},Não está definido a ID do utilizador para o Funcionário {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centro de custo de folha de pagamento, Approvers,Aprovadores, The first Approver in the list will be set as the default Approver.,O primeiro aprovador da lista será definido como o aprovador padrão., Shift Request Approver,Aprovador de solicitação de turno, -PAN Number,Número PAN, Provident Fund Account,Conta do fundo de previdência, MICR Code,Código MICR, Repay unclaimed amount from salary,Reembolsar quantia não reclamada do salário, Deduction from salary,Dedução do salário, -Expired Leaves,Folhas Vencidas, If this is not checked the loan by default will be considered as a Demand Loan,"Se esta opção não for marcada, o empréstimo, por padrão, será considerado um empréstimo à vista", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Esta conta é usada para registrar reembolsos de empréstimos do mutuário e também desembolsar empréstimos para o mutuário, This account is capital account which is used to allocate capital for loan disbursal account ,Esta conta é a conta de capital que é usada para alocar capital para a conta de desembolso do empréstimo, diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv index 80c32aa451..e6ce315809 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Repetare, Auto repeat document updated,Documentul repetat automat a fost actualizat, Automotive,Autopropulsat, Available,Disponibil, -Available Leaves,Frunzele disponibile, Available Qty,Cantitate disponibilă, Available Selling,Vânzări disponibile, Available for use date is required,Data de utilizare disponibilă pentru utilizare este necesară, @@ -1122,7 +1121,6 @@ Hub Category,Categorie Hub, Hub Sync ID,Hub ID de sincronizare, Human Resource,Resurse umane, Human Resources,Resurse umane, -IFSC Code,Codul IFSC, IGST Amount,Suma IGST, IP Address,Adresa IP, ITC Available (whether in full op part),ITC Disponibil (fie în opțiune integrală), @@ -1666,7 +1664,6 @@ Organization Name,Numele Organizatiei, Other,Altul, Other Reports,Alte rapoarte, "Other outward supplies(Nil rated,Exempted)","Alte consumabile exterioare (Nil evaluat, scutit)", -Others,Altel, Out Qty,Out Cantitate, Out Value,Valoarea afară, Out of Order,Scos din uz, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Credit), Total (Without Tax),Total (fără taxe), Total Achieved,Raport Realizat, Total Actual,Raport real, -Total Allocated Leaves,Frunzele totale alocate, Total Amount,Suma totală, Total Amount Credited,Suma totală creditată, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Taxe totale aplicabile în tabelul de achiziție Chitanță Elementele trebuie să fie la fel ca total impozite și taxe, @@ -2922,7 +2918,6 @@ Updating Variants...,Actualizarea variantelor ..., Upload your letter head and logo. (you can edit them later).,Încărcați capul scrisoare și logo-ul. (Le puteți edita mai târziu)., Upper Income,Venituri de sus, Use Sandbox,utilizare Sandbox, -Used Leaves,Frunze utilizate, User,Utilizator, User ID,ID-ul de utilizator, User ID not set for Employee {0},ID-ul de utilizator nu este setat pentru Angajat {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Centru de salarizare, Approvers,Aprobatori, The first Approver in the list will be set as the default Approver.,Primul aprobator din listă va fi setat ca aprobator implicit., Shift Request Approver,Aprobarea cererii de schimbare, -PAN Number,Număr PAN, Provident Fund Account,Contul Fondului Provident, MICR Code,Cod MICR, Repay unclaimed amount from salary,Rambursați suma nepreluată din salariu, Deduction from salary,Deducerea din salariu, -Expired Leaves,Frunze expirate, If this is not checked the loan by default will be considered as a Demand Loan,"Dacă acest lucru nu este verificat, împrumutul implicit va fi considerat un împrumut la cerere", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Acest cont este utilizat pentru rezervarea rambursărilor de împrumut de la împrumutat și, de asemenea, pentru a plăti împrumuturi către împrumutat", This account is capital account which is used to allocate capital for loan disbursal account ,Acest cont este un cont de capital care este utilizat pentru alocarea de capital pentru contul de debursare a împrumutului, diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 6a7e79fa49..5437c673f6 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -286,7 +286,6 @@ Auto Repeat,Автоматическое повторение, Auto repeat document updated,Автоматический повторный документ обновлен, Automotive,Автомобилестроение, Available,Доступно, -Available Leaves,Доступные листья, Available Qty,Доступное количество, Available Selling,Доступные продажи, Available for use date is required,Доступна дата использования., @@ -1120,7 +1119,6 @@ Hub Category,Категория концентратора, Hub Sync ID,Идентификатор синхронизации концентратора, Human Resource,Кадры, Human Resources,Персонал, -IFSC Code,Код IFSC, IGST Amount,Сумма IGST, IP Address,IP адрес, ITC Available (whether in full op part),ITC Доступен (будь то в полной части оп), @@ -1664,7 +1662,6 @@ Organization Name,Название организации, Other,Другое, Other Reports,Другие отчеты, "Other outward supplies(Nil rated,Exempted)","Другие внешние поставки (Ноль оценили, освобождены)", -Others,Другое, Out Qty,Из кол-ва, Out Value,Выходное значение, Out of Order,Вышел из строя, @@ -2810,7 +2807,6 @@ Total (Credit),Итого (кредит), Total (Without Tax),Всего (без налога), Total Achieved,Всего выполнено, Total Actual,Общий фактический, -Total Allocated Leaves,Всего выделенных листов, Total Amount,Общая сумма, Total Amount Credited,Общая сумма кредита, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Всего Применимые сборы в таблице Purchase квитанций Элементов должны быть такими же, как все налоги и сборы", @@ -2920,7 +2916,6 @@ Updating Variants...,Обновление вариантов..., Upload your letter head and logo. (you can edit them later).,Загрузить шапку фирменного бланка и логотип. (Вы можете отредактировать их позднее)., Upper Income,Высокий уровень дохода, Use Sandbox,Использовать «песочницу», -Used Leaves,Используемые листы, User,Пользователь, User ID,ID пользователя, User ID not set for Employee {0},ID пользователя не установлен для сотрудника {0}, @@ -7861,12 +7856,10 @@ Payroll Cost Center,Расчетный центр, Approvers,Утверждающие, The first Approver in the list will be set as the default Approver.,Первый утверждающий в списке будет установлен как утверждающий по умолчанию., Shift Request Approver,Утверждающий запрос смены, -PAN Number,Номер PAN, Provident Fund Account,Счет фонда обеспечения персонала, MICR Code,Код MICR, Repay unclaimed amount from salary,Вернуть невостребованную сумму из заработной платы, Deduction from salary,Удержание из заработной платы, -Expired Leaves,Просроченные листья, If this is not checked the loan by default will be considered as a Demand Loan,"Если этот флажок не установлен, ссуда по умолчанию будет считаться ссудой до востребования.", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Этот счет используется для регистрации платежей по ссуде от заемщика, а также для выдачи ссуд заемщику.", This account is capital account which is used to allocate capital for loan disbursal account ,"Этот счет является счетом движения капитала, который используется для распределения капитала на счет выдачи кредита.", diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv index f487f19728..4d883dcb6b 100644 --- a/erpnext/translations/rw.csv +++ b/erpnext/translations/rw.csv @@ -286,7 +286,6 @@ Auto Repeat,Gusubiramo Imodoka, Auto repeat document updated,Ongera usubiremo inyandiko ivugururwa, Automotive,Imodoka, Available,Birashoboka, -Available Leaves,Amababi aboneka, Available Qty,Kuboneka Qty, Available Selling,Kugurisha Kuboneka, Available for use date is required,Kuboneka kumatariki yo gukoresha birakenewe, @@ -1122,7 +1121,6 @@ Hub Category,Icyiciro cya Hub, Hub Sync ID,Indangamuntu ya Hub, Human Resource,Abakozi, Human Resources,Abakozi, -IFSC Code,Kode ya IFSC, IGST Amount,Amafaranga IGST, IP Address,Aderesi ya IP, ITC Available (whether in full op part),ITC Iraboneka (haba mubice byuzuye op), @@ -1666,7 +1664,6 @@ Organization Name,Izina ryumuryango, Other,Ibindi, Other Reports,Izindi Raporo, "Other outward supplies(Nil rated,Exempted)","Ibindi bikoresho byo hanze (Nil rated, Usonewe)", -Others,Abandi, Out Qty,Hanze Qty, Out Value,Agaciro, Out of Order,Bitemewe, @@ -2812,7 +2809,6 @@ Total (Credit),Igiteranyo (Inguzanyo), Total (Without Tax),Igiteranyo (Nta musoro), Total Achieved,Byose Byagezweho, Total Actual,Igiteranyo Cyuzuye, -Total Allocated Leaves,Amababi yose yatanzwe, Total Amount,Umubare wose, Total Amount Credited,Amafaranga yose yatanzwe, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Amafaranga yose akoreshwa muburyo bwo kugura ibicuruzwa byakiriwe bigomba kumera nkimisoro yose hamwe, @@ -2922,7 +2918,6 @@ Updating Variants...,Kuvugurura Ibihinduka ..., Upload your letter head and logo. (you can edit them later).,Kuramo ibaruwa yawe umutwe hamwe nikirangantego. (urashobora kubihindura nyuma)., Upper Income,Amafaranga yinjira, Use Sandbox,Koresha Sandbox, -Used Leaves,Amababi yakoreshejwe, User,Umukoresha, User ID,Indangamuntu, User ID not set for Employee {0},Indangamuntu yumukoresha ntabwo yashyizweho kubakozi {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Ikigo Cy'imishahara, Approvers,Abashinzwe, The first Approver in the list will be set as the default Approver.,Icyemezo cya mbere murutonde kizashyirwaho nkibisanzwe byemewe., Shift Request Approver,Guhindura Gusaba, -PAN Number,Numero ya PAN, Provident Fund Account,Konti y'Ikigega cy'Imari, MICR Code,Kode ya MICR, Repay unclaimed amount from salary,Subiza amafaranga atasabwe kuva kumushahara, Deduction from salary,Gukurwa ku mushahara, -Expired Leaves,Amababi yarangiye, If this is not checked the loan by default will be considered as a Demand Loan,Niba ibi bitagenzuwe inguzanyo byanze bikunze bizafatwa nkinguzanyo isabwa, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Iyi konti ikoreshwa mugutanga inguzanyo zishyuwe nuwagurijwe kandi ikanatanga inguzanyo kubagurijwe, This account is capital account which is used to allocate capital for loan disbursal account ,Konti ni konti shingiro ikoreshwa mugutanga igishoro kuri konti yo gutanga inguzanyo, diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv index fff78172ed..2c0390f1ab 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -286,7 +286,6 @@ Auto Repeat,මෝටර් රථය නැවත නැවතත්, Auto repeat document updated,ස්වයං යාවත්කාලීන ලියවිල්ල යාවත්කාලීන කරන ලදි, Automotive,රථ, Available,ඇත, -Available Leaves,පවතින ලීස්, Available Qty,ලබා ගත හැකි යවන ලද, Available Selling,විකුණා තිබේ, Available for use date is required,භාවිතයට ගත හැකි දිනය සඳහා අවශ්ය වේ, @@ -1122,7 +1121,6 @@ Hub Category,ප්රවර්ග කේන්ද්රස්ථානය, Hub Sync ID,Hub Sync ID, Human Resource,මානව සම්පත්, Human Resources,මානව සම්පත්, -IFSC Code,IFSC සංග්රහය, IGST Amount,IGST මුදල, IP Address,IP ලිපිනය, ITC Available (whether in full op part),ITC ලබා ගත හැකිය (පූර්ණ වශයෙන් වුවද), @@ -1666,7 +1664,6 @@ Organization Name,සංවිධානයේ නම, Other,වෙනත්, Other Reports,වෙනත් වාර්තා, "Other outward supplies(Nil rated,Exempted)","වෙනත් බාහිර සැපයුම් (නිල් ශ්‍රේණිගත, නිදහස්)", -Others,අන් අය, Out Qty,යවන ලද අතරින්, Out Value,අගය පෙන්වා, Out of Order,ක්රියාවිරහිත වී ඇත, @@ -2812,7 +2809,6 @@ Total (Credit),එකතුව (ක්රෙඩිට්), Total (Without Tax),මුළු (බදු රහිත), Total Achieved,මුළු ලබාගත්, Total Actual,මුළු තත, -Total Allocated Leaves,සමස්ථ වෙන් කළ කොළ, Total Amount,මුලු වටිනාකම, Total Amount Credited,මුළු මුදල අයකෙරේ, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,මිලදී ගැනීම රිසිට්පත අයිතම වගුවේ මුළු අදාළ ගාස්තු මුළු බදු හා ගාස්තු ලෙස එම විය යුතුය, @@ -2922,7 +2918,6 @@ Updating Variants...,ප්‍රභේද යාවත්කාලීන කි Upload your letter head and logo. (you can edit them later).,ඔබේ ලිපිය හිස සහ ලාංඡනය උඩුගත කරන්න. (ඔබ ඔවුන්ට පසුව සංස්කරණය කළ හැකි)., Upper Income,ඉහළ ආදායම්, Use Sandbox,වැලිපිල්ල භාවිතා, -Used Leaves,පාවිච්චි කළ කොළ, User,පරිශීලක, User ID,පරිශීලක ID, User ID not set for Employee {0},සේවක {0} සඳහා පරිශීලක අනන්යාංකය පිහිටුවා නැත, @@ -7949,12 +7944,10 @@ Payroll Cost Center,වැටුප් පිරිවැය මධ්‍යස Approvers,අනුමත කරන්නන්, The first Approver in the list will be set as the default Approver.,ලැයිස්තුවේ පළමු අනුමත කරන්නා පෙරනිමි අනුමත කරන්නා ලෙස සකසනු ඇත., Shift Request Approver,මාරුව ඉල්ලීම් අනුමත කරන්නා, -PAN Number,පෑන් අංකය, Provident Fund Account,අර්ථසාධක අරමුදල් ගිණුම, MICR Code,MICR කේතය, Repay unclaimed amount from salary,ඉල්ලුම් නොකළ මුදල වැටුපෙන් ආපසු ගෙවන්න, Deduction from salary,වැටුපෙන් අඩු කිරීම, -Expired Leaves,කල් ඉකුත් වූ කොළ, If this is not checked the loan by default will be considered as a Demand Loan,මෙය පරීක්‍ෂා නොකළ හොත් ණය පෙරනිමියෙන් ඉල්ලුම් ණය ලෙස සැලකේ, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,මෙම ගිණුම ණය ගැනුම්කරුගෙන් ණය ආපසු ගෙවීම් වෙන්කරවා ගැනීම සඳහා සහ ණය ගැනුම්කරුට ණය ලබා දීම සඳහා යොදා ගනී, This account is capital account which is used to allocate capital for loan disbursal account ,මෙම ගිණුම ණය බෙදා හැරීමේ ගිණුම සඳහා ප්‍රාග්ධනය වෙන් කිරීම සඳහා භාවිතා කරන ප්‍රාග්ධන ගිණුමකි, diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv index 05d3496dc2..9f24a6589a 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -286,7 +286,6 @@ Auto Repeat,Automatické opakovanie, Auto repeat document updated,Dokument bol aktualizovaný automaticky, Automotive,Automobilový, Available,K dispozici, -Available Leaves,Dostupné listy, Available Qty,Množství k dispozici, Available Selling,Dostupný predaj, Available for use date is required,Je potrebný dátum použiteľného na použitie, @@ -1122,7 +1121,6 @@ Hub Category,Kategória Hubu, Hub Sync ID,ID synchronizácie Hubu, Human Resource,Ľudské Zdroje, Human Resources,Lidské zdroje, -IFSC Code,Kód IFSC, IGST Amount,Suma IGST, IP Address,IP adresa, ITC Available (whether in full op part),ITC k dispozícii (či už v plnej op časti), @@ -1666,7 +1664,6 @@ Organization Name,Názov organizácie, Other,Ostatní, Other Reports,Ostatné správy, "Other outward supplies(Nil rated,Exempted)","Ostatné pasívne výrobky (bez hodnotenia, oslobodené)", -Others,Ostatní, Out Qty,Out Množství, Out Value,limitu, Out of Order,Mimo prevádzky, @@ -2812,7 +2809,6 @@ Total (Credit),Celkový (Credit), Total (Without Tax),Spolu (bez dane), Total Achieved,Celkem Dosažená, Total Actual,Celkem Aktuální, -Total Allocated Leaves,Celkové pridelené listy, Total Amount,Celková částka, Total Amount Credited,Celková čiastka bola pripísaná, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Celkom Použiteľné Poplatky v doklade o kúpe tovaru, ktorý tabuľky musí byť rovnaká ako celkom daní a poplatkov", @@ -2922,7 +2918,6 @@ Updating Variants...,Aktualizácia variantov ..., Upload your letter head and logo. (you can edit them later).,Nahrajte svoju hlavičku a logo pre dokumenty. (Môžete ich upravovať neskôr.), Upper Income,Horní příjmů, Use Sandbox,použitie Sandbox, -Used Leaves,Použité listy, User,užívateľ, User ID,User ID, User ID not set for Employee {0},User ID není nastavena pro zaměstnance {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Mzdové náklady, Approvers,Schvaľovatelia, The first Approver in the list will be set as the default Approver.,Prvý schvaľovateľ v zozname bude nastavený ako predvolený schvaľovateľ., Shift Request Approver,Schvaľovateľ žiadosti o zmenu, -PAN Number,Číslo PAN, Provident Fund Account,Účet podielového fondu, MICR Code,Kód MICR, Repay unclaimed amount from salary,Vrátiť nevyzdvihnutú sumu z platu, Deduction from salary,Odpočet z platu, -Expired Leaves,Vypršala platnosť, If this is not checked the loan by default will be considered as a Demand Loan,"Pokiaľ to nie je zaškrtnuté, pôžička sa štandardne bude považovať za pôžičku na požiadanie", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Tento účet sa používa na rezerváciu splátok pôžičky od dlžníka a tiež na vyplácanie pôžičiek dlžníkovi, This account is capital account which is used to allocate capital for loan disbursal account ,"Tento účet je kapitálovým účtom, ktorý sa používa na pridelenie kapitálu pre účet vyplácania pôžičiek", diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv index 8eafe41329..fe43400501 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -286,7 +286,6 @@ Auto Repeat,Samodejno ponavljanje, Auto repeat document updated,Posodobljen samodejno ponavljanje dokumenta, Automotive,Avtomobilizem, Available,Na voljo, -Available Leaves,Na voljo listi, Available Qty,Na voljo Količina, Available Selling,Razpoložljiva prodaja, Available for use date is required,Potreben je datum uporabe, @@ -1122,7 +1121,6 @@ Hub Category,Kategorija vozlišča, Hub Sync ID,Sync ID vozlišča, Human Resource,Človeški vir, Human Resources,Človeški viri, -IFSC Code,Kodeks IFSC, IGST Amount,Znesek IGST, IP Address,IP naslov, ITC Available (whether in full op part),ITC Na voljo (v celoti v delu), @@ -1666,7 +1664,6 @@ Organization Name,Organization Name, Other,Drugi, Other Reports,Druga poročila, "Other outward supplies(Nil rated,Exempted)","Druge zunanje dobave (ničelno, oproščeno)", -Others,Drugi, Out Qty,Out Kol, Out Value,iz Vrednost, Out of Order,Ne deluje, @@ -2812,7 +2809,6 @@ Total (Credit),Skupaj (Credit), Total (Without Tax),Skupaj (brez davka), Total Achieved,Skupaj Doseženi, Total Actual,Skupaj Actual, -Total Allocated Leaves,Skupna dodeljena lista, Total Amount,Skupni znesek, Total Amount Credited,Skupni znesek kredita, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Skupaj veljavnih cenah na Potrdilo o nakupu postavke tabele mora biti enaka kot Skupaj davkov in dajatev, @@ -2922,7 +2918,6 @@ Updating Variants...,Posodobitev različic ..., Upload your letter head and logo. (you can edit them later).,Naložite svoje pismo glavo in logotip. (lahko jih uredite kasneje)., Upper Income,Zgornja Prihodki, Use Sandbox,uporaba Sandbox, -Used Leaves,Uporabljeni listi, User,Uporabnik, User ID,Uporabniško ime, User ID not set for Employee {0},ID uporabnika ni nastavljena za Employee {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Stroškovno središče za plače, Approvers,Odobritelji, The first Approver in the list will be set as the default Approver.,Prvi odobritelj na seznamu bo nastavljen kot privzeti odobritelj., Shift Request Approver,Odobritelj zahteve za premik, -PAN Number,Številka PAN, Provident Fund Account,Račun rezervnega sklada, MICR Code,Koda MICR, Repay unclaimed amount from salary,Vrnite neizterjani znesek iz plače, Deduction from salary,Odbitek od plače, -Expired Leaves,Potekli listi, If this is not checked the loan by default will be considered as a Demand Loan,"Če to ni potrjeno, bo posojilo privzeto obravnavano kot posojilo na zahtevo", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ta račun se uporablja za rezervacijo odplačil posojilojemalca in tudi za izplačilo posojil posojilojemalcu, This account is capital account which is used to allocate capital for loan disbursal account ,"Ta račun je račun kapitala, ki se uporablja za dodelitev kapitala za račun izplačila posojila", diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv index 8c9a2ed0f4..214cc66b5f 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -286,7 +286,6 @@ Auto Repeat,Repeat automatikisht, Auto repeat document updated,Dokumenti i përsëritjes automatike përditësohej, Automotive,automobilistik, Available,në dispozicion, -Available Leaves,Lejet e disponueshme, Available Qty,Qty në dispozicion, Available Selling,Shitja në dispozicion, Available for use date is required,Kërkohet data e përdorimit, @@ -1122,7 +1121,6 @@ Hub Category,Kategoria Hub, Hub Sync ID,ID e sinkronizimit të Hub, Human Resource,Burimeve Njerëzore, Human Resources,Burimeve Njerëzore, -IFSC Code,Kodi IFSC, IGST Amount,Shuma IGST, IP Address,Adresa IP, ITC Available (whether in full op part),ITC në dispozicion (qoftë në pjesën e plotë të op), @@ -1666,7 +1664,6 @@ Organization Name,Emri i Organizatës, Other,Tjetër, Other Reports,Raportet tjera, "Other outward supplies(Nil rated,Exempted)","Furnizime të tjera të jashtme (vlerësuar me Nil, Përjashtuar)", -Others,Të tjerët, Out Qty,Nga Qty, Out Value,Vlera out, Out of Order,Jashtë përdorimit, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Credit), Total (Without Tax),Totali (Pa Tatimore), Total Achieved,Gjithsej Arritur, Total Actual,Gjithsej aktuale, -Total Allocated Leaves,Totali i lëkundjeve të alokuara, Total Amount,Shuma totale, Total Amount Credited,Shuma totale e kredituar, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Akuzat totale të zbatueshme në Blerje tryezë Receipt artikujt duhet të jetë i njëjtë si Total taksat dhe tarifat, @@ -2922,7 +2918,6 @@ Updating Variants...,Përditësimi i varianteve ..., Upload your letter head and logo. (you can edit them later).,Ngarko kokën tuaj letër dhe logo. (Ju mund të modifikoni ato më vonë)., Upper Income,Të ardhurat e sipërme, Use Sandbox,Përdorimi Sandbox, -Used Leaves,Lë të përdorura, User,përdorues, User ID,User ID, User ID not set for Employee {0},Përdoruesi ID nuk është caktuar për punonjësit {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Qendra e Kostos së Pagave, Approvers,Përgjegjës, The first Approver in the list will be set as the default Approver.,Miratuesi i parë në listë do të caktohet si Miratuesi i paracaktuar., Shift Request Approver,Miratuesi i kërkesës për ndërrim, -PAN Number,Numri PAN, Provident Fund Account,Llogaria e Fondit të Providencës, MICR Code,Kodi MICR, Repay unclaimed amount from salary,Shlyeni shumën e pakërkuar nga paga, Deduction from salary,Zbritja nga paga, -Expired Leaves,Gjethet e skaduara, If this is not checked the loan by default will be considered as a Demand Loan,"Nëse kjo nuk kontrollohet, kredia si parazgjedhje do të konsiderohet si një Kredi e Kërkesës", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Kjo llogari përdoret për rezervimin e ripagimeve të huasë nga huamarrësi dhe gjithashtu disbursimin e huamarrësve, This account is capital account which is used to allocate capital for loan disbursal account ,Kjo llogari është llogari kapitale e cila përdoret për të alokuar kapitalin për llogarinë e disbursimit të kredisë, diff --git a/erpnext/translations/sr-SP.csv b/erpnext/translations/sr-SP.csv index c70ae81bfe..ac5711f351 100644 --- a/erpnext/translations/sr-SP.csv +++ b/erpnext/translations/sr-SP.csv @@ -7,10 +7,8 @@ Is Purchase Item,Artikal je za poručivanje, Warehouse {0} does not exist,Skladište {0} ne postoji, Confirmed orders from Customers.,Potvrđene porudžbine od strane kupaca, Healthcare (beta),Klinika (beta) -Salary Structure,Структура плата Item Reorder,Dopuna artikla, Report an Issue,Prijavi grešku, -Net Pay,Neto plaćanje, Internal Transfer,Interni prenos, Item Tax Rate,Poreska stopa, Create a new Customer,Kreirajte novog kupca, @@ -52,7 +50,6 @@ Closing Fiscal Year,Zatvaranje fiskalne godine, You can only select a maximum of one option from the list of check boxes.,Из листе поља за потврду можете изабрати највише једну опцију. Group by Voucher,Grupiši po knjiženjima, Total Outstanding,Ukupno preostalo, -Email Salary Slip to Employee,Pošalji platnu listu Zaposlenom, Customer Code,Šifra kupca, Allow multiple Sales Orders against a Customer's Purchase Order,Dozvolite više prodajnih naloga koji su vezani sa porudžbenicom kupca, Total Outgoing,Ukupno isporučeno, @@ -61,7 +58,6 @@ Sales Register,Pregled Prodaje, % Delivered,% Isporučeno, Appraisal {0} created for Employee {1} in the given date range,Procjena {0} kreirana za Zaposlenog {1} za dati period, Party Balance,Stanje kupca, -{0} already allocated for Employee {1} for period {2} to {3},{0} već dodijeljeno zaposlenom {1} za period {2} {3} Customers,Kupci, Please set User ID field in an Employee record to set Employee Role,Molimo podesite polje Korisnički ID u evidenciji Zaposlenih radi podešavanja zaduženja Zaposlenih, Task Completion,Završenost zadataka, @@ -89,7 +85,6 @@ Amount {0} {1} transferred from {2} to {3},Iznos {0} {1} prebačen iz {2} u {3} Consultations,Pregledi, Classification of Customers by region,Klasifikacija kupaca po regiji, Quotation To,Ponuda za, -Payroll Employee Detail,Detalji o platnom spisku Zaposlenih, Timesheet for tasks.,Potrošeno vrijeme za zadatke, Remark,Napomena, Tree of Item Groups.,Stablo Vrste artikala, @@ -122,13 +117,11 @@ Sales Analytics,Prodajna analitika, `Freeze Stocks Older Than` should be smaller than %d days.,"""Zamrzni akcije starije"" trebalo bi da budu manje %d dana." Patient Age,Starost pacijenta, Employee cannot report to himself.,Zaposleni ne može izvještavati sebi, -You can only submit Leave Encashment for a valid encashment amount,Можете поднети Исплату одсуства само са валидном количином за исплату. Customer Address,Adresa kupca, Total Amount {0},Ukupan iznos {0} Total (Credit),Ukupno bez PDV-a (duguje) In Words (Export) will be visible once you save the Delivery Note.,Slovima (izvoz) će biti vidljiv onda kad sačuvate otpremnicu, Opportunity Date,Datum prilike, -Marked Attendance HTML,Označeno prisustvo HTML, Delivery Note Item,Pozicija otpremnice, Customer's Purchase Order,Porudžbenica kupca, Get items from,Dodaj stavke iz, @@ -150,14 +143,12 @@ Default Employee Advance Account,Podrazumjevani račun zaposlenog, Account: {0} can only be updated via Stock Transactions,Računovodstvo: {0} može samo da se ažurira u dijelu Promjene na zalihama, Leave Approver,Odobrava izlaske s posla, Customer or Item,Kupac ili proizvod, -Attendance To Date,Prisustvo do danas, Requested Qty,Tražena kol, Attendance Freeze Date,Datum zamrzavanja prisustva, No Item with Serial No {0},Ne postoji artikal sa serijskim brojem {0} Taxes and Charges,Porezi i naknade, Serial Number Series,Serijski broj serije, Delivered,Isporučeno, -No employee found,Zaposleni nije pronađen, Default Territory,Podrazumijevana država, Asset Category,Grupe osnovnih sredstava, Customer Warehouse (Optional),Skladište kupca (opciono) @@ -190,10 +181,8 @@ Payment,Plaćanje, Sales Partners Commission,Provizija za prodajne partnere, Territory,Teritorija, Sales Order Message,Poruka prodajnog naloga, -Employees HTML,HTML Zaposlenih, Employee Leave Balance,Pregled odsustva Zaposlenih, Minute,Minut, -Emails salary slip to employee based on preferred email selected in Employee,Pošalji platnu listu Zaposlenom na željenu adresu označenu u tab-u ZAPOSLENI, Litre,Litar, Opening (Cr),Početno stanje (Po) Academics User,Akademski korisnik, @@ -222,11 +211,9 @@ Taxable Amount,Oporezivi iznos, Product Bundle Item,Sastavljeni proizvodi, Select Employees,Odaberite Zaposlene, "Create Employee records to manage leaves, expense claims and payroll","Kreiraj evidenciju o Zaposlenom kako bi upravljali odsustvom, potraživanjima za troškove i platnim spiskovima" -This is based on the attendance of this Employee,Ovo se zasniva na pohađanju ovog zaposlenog, Address & Contact,Adresa i kontakt, Reserved Qty for Production,Rezervisana kol. za proizvodnju, This is based on stock movement. See {0} for details,Ovo praćenje je zasnovano na kretanje zaliha. Pogledajte {0} za više detalja, -Training Event Employee,Obuke Zaposlenih, All Contacts.,Svi kontakti, Sales Person,Prodajni agent, Default Warehouse,Podrazumijevano skladište, @@ -237,7 +224,6 @@ Year start date or end date is overlapping with {0}. To avoid please set company Credit,Potražuje, Grand Total,Za plaćanje, Human Resource,Ljudski resursi, -Employees,Zaposleni, Delivery Note Required,Otpremnica je obavezna, Type of Payment,Vrsta plaćanja, UOM,JM, @@ -284,15 +270,12 @@ Supplier Warehouse,Skladište dobavljača, Customer is required,Kupac je obavezan podatak, Manufacturer,Proizvođač Selling Amount,Prodajni iznos, -Please set the Date Of Joining for employee {0},Molimo podesite datum zasnivanja radnog odnosa {0} Allow over delivery or receipt upto this percent,Dozvolite isporukuili prijem robe ukoliko ne premaši ovaj procenat, Orders,Porudžbine, Stock Transactions,Promjene na zalihama, -You are not authorized to approve leaves on Block Dates,Немате дозволу да одобравате одсуства на Блок Датумима. Daily Timesheet Summary,Pregled dnevnog potrošenog vremena, View Timesheet,Pogledaj potrošeno vrijeme, Rounded Total (Company Currency),Zaokruženi ukupan iznos (valuta preduzeća) -Salary Slip of employee {0} already created for this period,Isplatna lista Zaposlenog {0} kreirana je već za ovaj period, "If this item has variants, then it cannot be selected in sales orders etc.","Ako ovaj artikal ima varijante, onda ne može biti biran u prodajnom nalogu." You can only redeem max {0} points in this order.,Можете унети највише {0} поена у овој наруџбини. No leave record found for employee {0} for {1},Nije nađena evidancija o odsustvu Zaposlenog {0} za {1} @@ -312,7 +295,6 @@ Rest Of The World,Ostatak svijeta, Additional Operating Cost,Dodatni operativni troškovi, Rejected Warehouse,Odbijeno skladište, Manufacturing Manager,Menadžer proizvodnje, -You are not present all day(s) between compensatory leave request days,Нисте присутни свих дана између захтева за компензацијски одмор. Is Fixed Asset,Artikal je osnovno sredstvo, POS,POS, Timesheet {0} is already completed or cancelled,Potrošeno vrijeme {0} je već potvrđeno ili otkazano, @@ -327,7 +309,6 @@ Make Sales Orders to help you plan your work and deliver on-time,Kreiranje proda Sync Offline Invoices,Sinhronizuj offline fakture, Manufacturing,Proizvodnja, {0}% Delivered,{0}% Isporučeno, -Attendance,Prisustvo, Customer's Purchase Order No,Broj porudžbenice kupca, Please enter Sales Orders in the above table,U tabelu iznad unesite prodajni nalog, Report Date,Datum izvještaja, @@ -338,7 +319,6 @@ Gross Profit %,Bruto dobit% Payment Request,Upit za plaćanje, Purchase Analytics,Analiza nabavke, Tree Details,Detalji stabla, -Upload Attendance,Priloži evidenciju, Against,Povezano sa, Requested Amount,Traženi iznos, "Record of all communications of type email, phone, chat, visit, etc.","Snimanje svih komunikacija tipa email, telefon, poruke, posjete, itd." @@ -381,7 +361,6 @@ Repack,Prepakovati, Please select a warehouse,Izaberite skladište, Received and Accepted,Primio i prihvatio, Project will be accessible on the website to these users,Projekat će biti dostupan na sajtu sledećim korisnicima, -Upload HTML,Priloži HTML, Services,Usluge, Item Cart,Korpa sa artiklima, Total Paid Amt,Ukupno plaćeno, @@ -390,7 +369,6 @@ Quotation Item,Stavka sa ponude, Employee Advance,Napredak Zaposlenog, Warehouse and Reference,Skladište i veza, {0} {1}: Account {2} is inactive,{0} {1}: Nalog {2} je neaktivan, -Fiscal Year {0} not found,Fiskalna godina {0} nije pronađena, No Remarks,Nema napomene, Purchase Receipt Message,Poruka u Prijemu robe, Taxes and Charges Deducted,Umanjeni porezi i naknade, @@ -427,7 +405,6 @@ Write Off Difference Amount,Otpis razlike u iznosu, Closing Date,Datum zatvaranja, Cheque/Reference Date,Datum izvoda, Planned Qty,Planirana količina, -Payment Date,Datum plaćanja, Additional Details,Dodatni detalji, Create Chart Of Accounts Based On,Kreiraj kontni plan prema, You can not change rate if BOM mentioned agianst any item,Не можете променити цену ако постоји Саставница за било коју ставку. @@ -440,8 +417,6 @@ Not allowed to update stock transactions older than {0},Nije dozvoljeno mijenjat Add Employees,Dodaj Zaposlenog, Setting up Employees,Podešavanja Zaposlenih, Warehouse not found in the system,Skladište nije pronađeno u sistemu, -Attendance for employee {0} is already marked for this day,Prisustvo zaposlenog {0} је već označeno za ovaj dan, -Employee relieved on {0} must be set as 'Left',"Zaposleni smijenjen na {0} mora biti označen kao ""Napustio""" Shipping Bill Number,Broj isporuke, Lab Test Report,Izvještaj labaratorijskog testa, You cannot credit and debit same account at the same time,Не можете кредитирати и дебитовати исти налог у исто време. @@ -475,17 +450,14 @@ Shopping Cart,Korpa sa sajta, Reserved for manufacturing,Rezervisana za proizvodnju, Pricing Rule Help,Pravilnik za cijene pomoć Ageing Range 2,Opseg dospijeća 2, -Employee Benefits,Primanja Zaposlenih, POS Item Group,POS Vrsta artikala, Lead,Lead, -Employee Settings,Podešavanja zaposlenih, View All Products,Pogledajte sve proizvode, Patient Medical Record,Medicinski karton pacijenta, Batch,Serija, Purchase Receipt,Prijem robe, Warranty Period (in days),Garantni rok (u danima) Customer database.,Korisnička baza podataka, -Attendance Date,Datum prisustva, Notify Employee,Obavijestiti Zaposlenog, User ID not set for Employee {0},Korisnički ID nije podešen za Zaposlenog {0} Stock Projected Qty,Projektovana količina na zalihama, @@ -507,7 +479,6 @@ Tax Breakup,Porez po pozicijama, Task,Zadatak, Add / Edit Prices,Dodaj / Izmijeni cijene, Item Prices,Cijene artikala, -Salary Component,Компонента плате Customer's Purchase Order Date,Datum porudžbenice kupca, Country of Origin,Zemlja porijekla, Please select Employee Record first.,Molimo izaberite registar Zaposlenih prvo, @@ -520,7 +491,6 @@ Itemwise Recommended Reorder Level,Pregled preporučenih nivoa dopune, {0} against Bill {1} dated {2},{0} veza sa računom {1} na datum {2} You are not authorized to set Frozen value,Немате дозволу да постављате замрзнуту вредност Requested Items To Be Ordered,Tražene stavke za isporuku, -Unmarked Attendance,Neobilježeno prisustvo, Sales Order {0} is not submitted,Prodajni nalog {0} nije potvrđen, Default Material Request Type,Podrazumijevani zahtjev za tip materijala, Sales Pipeline,Prodajna linija, @@ -580,7 +550,6 @@ A Customer Group exists with same name please change the Customer name or rename Warning: Another {0} # {1} exists against stock entry {2},Upozorenje: Još jedan {0} # {1} postoji u vezanom Unosu zaliha {2} Suplier,Dobavljač Has Serial No,Ima serijski broj, -Employee {0} on Half day on {1},Zaposleni {0} na pola radnog vremena {1} Difference Amount (Company Currency),Razlika u iznosu (Valuta) Add Serial No,Dodaj serijski broj, Company and Accounts,Preduzeće i računi, @@ -614,7 +583,6 @@ Warehouse Name,Naziv skladišta, Customer / Item Name,Kupac / Naziv proizvoda, Total Billed Amount,Ukupno fakturisano, In Value,Prijem vrije. -Employees Email Id,ID email Zaposlenih, Tree Type,Tip stabla, Update Rate and Availability,Izmijenite cijenu i dostupnost, Supplier Quotation,Ponuda dobavljača, @@ -643,11 +611,8 @@ Invoice No,Broj fakture, Purchase Receipt Item,Stavka Prijema robe, Invoices,Fakture, Task Progress,% završenosti zadataka, -Employee Attendance Tool,Alat za prisustvo Zaposlenih, -Payment Days,Dana za plaćanje, Recruitment,Zapošljavanje, Taxes and Charges Calculation,Izračun Poreza, -For Employee,Za Zaposlenog, Terms and Conditions Template,Uslovi i odredbe šablon, Change,Kusur, Stock Entry {0} created,Unos zaliha {0} je kreiran, @@ -672,7 +637,6 @@ Accepted Warehouse,Prihvaćeno skladište, Income Account,Račun prihoda, Account Balance,Knjigovodstveno stanje, 'Expected Start Date' can not be greater than 'Expected End Date',Očekivani datum početka ne može biti veći od očekivanog datuma završetka, -Employee Emails,Elektronska pošta Zaposlenog, Opening Qty,Početna količina, Reorder level based on Warehouse,Nivo dopune u zavisnosti od skladišta, To Warehouse,U skladište, @@ -683,7 +647,6 @@ Return / Debit Note,Povraćaj / knjižno zaduženje, Request for Quotation Supplier,Zahtjev za ponudu dobavljača, LeaderBoard,Tabla, Lab Test Groups,Labaratorijske grupe, -Training Result Employee,Rezultati obuke Zaposlenih, Invoice Details,Detalji fakture, Banking and Payments,Bakarstvo i plaćanja, Employee Name,Ime Zaposlenog, @@ -702,10 +665,8 @@ Item Shortage Report,Izvještaj o negativnim zalihama, Transaction reference no {0} dated {1},Broj izvoda {0} na datum {1} Make Sales Order,Kreiraj prodajni nalog, Items,Artikli, -Employees working on a holiday,Zaposleni koji rade za vrijeme praznika, Allocate Payment Amount,Poveži uplaćeni iznos, Patient ID,ID pacijenta, -Printed On,Datum i vrijeme štampe, Debit To,Zaduženje za, Global Settings,Globalna podešavanja, Make Employee,Keriraj Zaposlenog, @@ -732,7 +693,6 @@ Update Stock,Ažuriraj zalihu, Target Warehouse,Ciljno skladište, Delivery Note Trends,Trendovi Otpremnica, Default Source Warehouse,Izdajno skladište, -"{0}: Employee email not found, hence email not sent","{0}: Email zaposlenog nije pronađena, stoga email nije poslat" All Warehouses,Sva skladišta, Difference Amount,Razlika u iznosu, User Remark,Korisnička napomena, @@ -775,22 +735,18 @@ Return Against Delivery Note,Povraćaj u vezi sa otpremnicom, Ageing Range 1,Opseg dospijeća 1, Incoming Rate,Nabavna cijena, Timesheets,Potrošnja vremena, -Attendance From Date,Datum početka prisustva, Stock Items,Artikli na zalihama, New Cart,Nova korpa, Opening Value,Početna vrijednost, "Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}","Podešavanje stanja na {0}, pošto Zaposleni koji se priključio Prodavcima nema koririsnički ID {1}" -Import Attendance,Uvoz prisustva, Bank Balance,Stanje na računu, Employee Number,Broj Zaposlenog, Rate and Amount,Cijena i vrijednost sa popustom, 'Total','Ukupno bez PDV-a' Total Taxes and Charges,Porez, -No active or default Salary Structure found for employee {0} for the given dates,Nisu pronađene aktivne ili podrazumjevane strukture plate za Zaposlenog {0} za dati period, Supplier Part Number,Dobavljačeva šifra, Project Task,Projektni zadatak, Parent Item Group,Nadređena Vrsta artikala, -Mark Attendance,Označi prisustvo, {0} created,Kreirao je korisnik {0} Advance Paid,Avansno plačanje, Projected,Projektovana količina na zalihama, @@ -824,7 +780,6 @@ For Warehouse,Za skladište, Purchase Price List,Nabavni cjenovnik, Accounts Payable Summary,Pregled obaveze prema dobavljačima, Delivery Notes {0} must be cancelled before cancelling this Sales Order,Otpremnice {0} moraju biti otkazane prije otkazivanja prodajnog naloga, -Total Payment,Ukupno plaćeno, POS Settings,POS podešavanja, Buying Amount,Iznos nabavke, Valuation Rate,Prosječna nab. cijena, @@ -906,7 +861,6 @@ Please Delivery Note first,Otpremite robu prvo, From Customer,Od kupca, Maintain Stock,Vođenje zalihe, Sales Order Item,Pozicija prodajnog naloga, -Attendance From Date and Attendance To Date is mandatory,Datum početka prisustva i prisustvo do danas su obavezni, Reserved Qty,Rezervisana kol. Not items found,Ništa nije pronađeno, Copy From Item Group,Kopiraj iz vrste artikala, @@ -932,7 +886,6 @@ Price List Country,Zemlja cjenovnika, Due Date is mandatory,Datum dospijeća je obavezan, Cart,Korpa, Stock transactions before {0} are frozen,Promjene na zalihama prije {0} su zamrznute, -Employee {0} is not active or does not exist,Zaposleni {0} nije aktivan ili ne postoji, You have entered duplicate items. Please rectify and try again.,Унели сте дупликате. Молимо проверите и покушајте поново. Closing (Dr),Saldo (Du) Product Bundle Help,Sastavnica Pomoć @@ -947,7 +900,6 @@ To Bill,Za fakturisanje, Gantt Chart,Gant dijagram, Requested Quantity,Tražena količina, Chart Of Accounts Template,Templejt za kontni plan, -Marked Attendance,Označeno prisustvo, Please set a default Holiday List for Employee {0} or Company {1},Molimo podesite podrazumjevanu listu praznika za Zaposlenog {0} ili Preduzeće {1} Pending Amount,Iznos na čekanju, Supplier Invoice No,Broj fakture dobavljača, @@ -973,18 +925,15 @@ Purchasing,Kupovina, You cannot delete Project Type 'External',"Не можете обрисати ""Спољни"" тип пројекта." Delivery Note,Otpremnice, In Words will be visible once you save the Sales Order.,U riječima će biti vidljivo tek kada sačuvate prodajni nalog. -Show Salary Slip,Прикажи одсечак плате Activity Cost per Employee,Troškovi aktivnosti po zaposlenom, Sales Order,Prodajni nalog, Customer or Supplier Details,Detalji kupca ili dobavljača, Sell,Prodaja, -Salary Slip of employee {0} already created for time sheet {1},Isplatna lista Zaposlenog {0} kreirana je već za raspored {1} Additional Discount Percentage,Dodatni procenat popusta, HR User,Korisnik za ljudske resure, Stock Reports,Izvještaji zaliha robe, Return Against Sales Invoice,Povraćaj u vezi sa Fakturom prodaje, Naming Series,Vrste dokumenta, -Monthly Attendance Sheet,Mjesečni list prisustva, Stock Ledger,Skladišni karton, Sales Invoice {0} must be cancelled before cancelling this Sales Order,Faktura {0} mora biti otkazana prije otkazivanja ovog prodajnog naloga, New Quotations,Nove ponude, diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv index cf32d3654a..c5b2702cb4 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -286,7 +286,6 @@ Auto Repeat,Ауто Репеат, Auto repeat document updated,Аутоматско понављање документа је ажурирано, Automotive,аутомобилски, Available,Доступно, -Available Leaves,Расположиве листе, Available Qty,Доступно ком, Available Selling,Доступна продаја, Available for use date is required,Потребан је датум употребе, @@ -1122,7 +1121,6 @@ Hub Category,Главна категорија, Hub Sync ID,Хуб Синц ИД, Human Resource,Људски ресурси, Human Resources,Человеческие ресурсы, -IFSC Code,ИФСЦ код, IGST Amount,ИГСТ Износ, IP Address,ИП адреса, ITC Available (whether in full op part),Доступан ИТЦ (било у целокупном делу), @@ -1666,7 +1664,6 @@ Organization Name,Име организације, Other,Други, Other Reports,Остали извештаји, "Other outward supplies(Nil rated,Exempted)","Остале спољне залихе (Нил, Ослобођено)", -Others,другие, Out Qty,Од Кол, Out Value,od Вредност, Out of Order,Неисправно, @@ -2812,7 +2809,6 @@ Total (Credit),Укупно (кредит), Total (Without Tax),Укупно (без пореза), Total Achieved,Укупно Постигнута, Total Actual,Укупно Стварна, -Total Allocated Leaves,Укупно издвојене листе, Total Amount,Укупан износ, Total Amount Credited,Укупан износ кредита, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Укупно Важећи Оптужбе у куповини потврда за ставке табели мора бити исти као и укупних пореза и накнада, @@ -2922,7 +2918,6 @@ Updating Variants...,Ажурирање варијанти ..., Upload your letter head and logo. (you can edit them later).,Уплоад главу писмо и лого. (Можете их уредити касније)., Upper Income,Горња прихода, Use Sandbox,Употреба Песак, -Used Leaves,Користи Леавес, User,Усер, User ID,Кориснички ИД, User ID not set for Employee {0},ID пользователя не установлен Требуются {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Место трошкова зарада, Approvers,Одобривачи, The first Approver in the list will be set as the default Approver.,Први одобравач на листи биће постављен као подразумевани одобраватељ., Shift Request Approver,Одобривач захтева за смену, -PAN Number,ПАН број, Provident Fund Account,Рачун осигуравајућег фонда, MICR Code,МИЦР код, Repay unclaimed amount from salary,Отплати неискоришћени износ из плате, Deduction from salary,Одбитак од зараде, -Expired Leaves,Истекло лишће, If this is not checked the loan by default will be considered as a Demand Loan,"Ако ово није потврђено, зајам ће се подразумевано сматрати зајмом на захтев", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Овај рачун се користи за резервирање отплате зајма од зајмопримца и за исплату зајмова зајмопримцу, This account is capital account which is used to allocate capital for loan disbursal account ,Овај рачун је рачун капитала који се користи за алокацију капитала за рачун за издвајање кредита, diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv index c07fda2568..141b5718b2 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -286,7 +286,6 @@ Auto Repeat,Auto Repeat, Auto repeat document updated,Automatisk upprepa dokument uppdaterad, Automotive,Fordon, Available,Tillgängligt, -Available Leaves,Tillgängliga löv, Available Qty,Tillgång Antal, Available Selling,Tillgänglig försäljning, Available for use date is required,Tillgängligt för användning datum krävs, @@ -1122,7 +1121,6 @@ Hub Category,Navkategori, Hub Sync ID,Hub Sync ID, Human Resource,Personal administration, Human Resources,Personal Resurser, -IFSC Code,IFSC-kod, IGST Amount,IGST-belopp, IP Address,IP-adress, ITC Available (whether in full op part),ITC tillgängligt (oavsett om det är fullständigt), @@ -1666,7 +1664,6 @@ Organization Name,Organisationsnamn, Other,Annat, Other Reports,Andra rapporter, "Other outward supplies(Nil rated,Exempted)","Andra leveranser utifrån (noll betyg, undantagna)", -Others,Annat, Out Qty,Ut antal, Out Value,ut Värde, Out of Order,Trasig, @@ -2812,7 +2809,6 @@ Total (Credit),Total (Credit), Total (Without Tax),Totalt (utan skatt), Total Achieved,Totalt Uppnått, Total Actual,Totalt Faktisk, -Total Allocated Leaves,Totalt tilldelade blad, Total Amount,Totala summan, Total Amount Credited,Summa belopp Credited, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Totalt tillämpliga avgifter i inköpskvittot Items tabellen måste vara densamma som den totala skatter och avgifter, @@ -2922,7 +2918,6 @@ Updating Variants...,Uppdaterar varianter ..., Upload your letter head and logo. (you can edit them later).,Ladda upp din brevhuvud och logotyp. (Du kan redigera dem senare)., Upper Income,Övre inkomst, Use Sandbox,användning Sandbox, -Used Leaves,Använda löv, User,Användare, User ID,användar ID, User ID not set for Employee {0},Användar-ID inte satt för anställd {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Lönekostnadsställe, Approvers,Omstridda, The first Approver in the list will be set as the default Approver.,Den första godkännaren i listan kommer att ställas in som standard godkännare., Shift Request Approver,Skiftbegäran godkännare, -PAN Number,PAN-nummer, Provident Fund Account,Försäkringskassakonto, MICR Code,MICR-kod, Repay unclaimed amount from salary,Återbetala obefogat belopp från lönen, Deduction from salary,Avdrag från lön, -Expired Leaves,Utgångna löv, If this is not checked the loan by default will be considered as a Demand Loan,Om detta inte är markerat kommer lånet som standard att betraktas som ett efterfrågelån, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Detta konto används för bokning av återbetalningar av lån från låntagaren och för utbetalning av lån till låntagaren, This account is capital account which is used to allocate capital for loan disbursal account ,Det här kontot är ett kapitalkonto som används för att fördela kapital för lånekonto, diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv index 243994fd80..cead023dbd 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -286,7 +286,6 @@ Auto Repeat,Rudia tena, Auto repeat document updated,Rudia tena hati iliyosasishwa, Automotive,Magari, Available,Inapatikana, -Available Leaves,Majani Inapatikana, Available Qty,Uchina unaopatikana, Available Selling,Inapatikana Kuuza, Available for use date is required,Inapatikana kwa tarehe ya matumizi inahitajika, @@ -1122,7 +1121,6 @@ Hub Category,Jamii ya Hub, Hub Sync ID,ID ya Usawazishaji wa Hub, Human Resource,Rasilimali watu, Human Resources,Rasilimali, -IFSC Code,IFSC Kanuni, IGST Amount,IGST Kiasi, IP Address,Anwani ya IP, ITC Available (whether in full op part),ITC Inapatikana (iwe katika sehemu kamili), @@ -1666,7 +1664,6 @@ Organization Name,Jina la Shirika, Other,Nyingine, Other Reports,Taarifa nyingine, "Other outward supplies(Nil rated,Exempted)","Vifaa vingine vya nje (Nilikadiriwa, Imesamehewa)", -Others,Wengine, Out Qty,Nje ya Uchina, Out Value,Thamani ya nje, Out of Order,Nje ya Utaratibu, @@ -2812,7 +2809,6 @@ Total (Credit),Jumla (Mikopo), Total (Without Tax),Jumla (bila ya Kodi), Total Achieved,Jumla imefikia, Total Actual,Jumla halisi, -Total Allocated Leaves,Jumla ya Majani yaliyowekwa, Total Amount,Jumla, Total Amount Credited,Jumla ya Kizuizi, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Malipo Yote ya Kuhitajika katika Jedwali la Vipokezi vya Ununuzi lazima lifanane na Jumla ya Kodi na Malipo, @@ -2922,7 +2918,6 @@ Updating Variants...,Inasasisha anuwai ..., Upload your letter head and logo. (you can edit them later).,Pakia kichwa chako na alama. (unaweza kuwahariri baadaye)., Upper Income,Mapato ya Juu, Use Sandbox,Tumia Sandbox, -Used Leaves,Majani yaliyotumika, User,Mtumiaji, User ID,Kitambulisho cha mtumiaji, User ID not set for Employee {0},Kitambulisho cha mtumiaji hakiwekwa kwa Waajiriwa {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Kituo cha Gharama za Mishahara, Approvers,Waunga mkono, The first Approver in the list will be set as the default Approver.,Mtangazaji wa kwanza kwenye orodha atawekwa kama Mtoaji Mbadala., Shift Request Approver,Ombi la Shift Liidhinishe, -PAN Number,Nambari ya PAN, Provident Fund Account,Akaunti ya Mfuko wa Utoaji, MICR Code,Nambari ya MICR, Repay unclaimed amount from salary,Lipa kiasi ambacho hakijadai kutoka kwa mshahara, Deduction from salary,Utoaji kutoka kwa mshahara, -Expired Leaves,Majani yaliyomalizika, If this is not checked the loan by default will be considered as a Demand Loan,Ikiwa hii haijakaguliwa mkopo kwa chaguo-msingi utazingatiwa kama Mkopo wa Mahitaji, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Akaunti hii inatumika kwa uhifadhi wa mikopo kutoka kwa akopaye na pia kutoa mikopo kwa akopaye, This account is capital account which is used to allocate capital for loan disbursal account ,Akaunti hii ni akaunti kuu ambayo hutumiwa kutenga mtaji kwa akaunti ya utoaji wa mkopo, diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv index d2c3dfb8d1..61417c7aeb 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -286,7 +286,6 @@ Auto Repeat,ஆட்டோ மீண்டும், Auto repeat document updated,தானியங்கு திரும்ப ஆவணம் புதுப்பிக்கப்பட்டது, Automotive,வாகன, Available,கிடைக்கக்கூடிய, -Available Leaves,கிடைக்கும் இலைகள், Available Qty,கிடைக்கும் அளவு, Available Selling,கிடைக்கும் விற்பனை, Available for use date is required,உபயோகத்திற்கான தேதி கிடைக்க வேண்டும், @@ -1122,7 +1121,6 @@ Hub Category,ஹப் பகுப்பு, Hub Sync ID,மைய ஒருங்கிணைப்பு ஐடி, Human Resource,மையம் வள, Human Resources,மனித வளங்கள், -IFSC Code,IFSC கோட், IGST Amount,IGST தொகை, IP Address,ஐபி முகவரி, ITC Available (whether in full op part),ஐ.டி.சி கிடைக்கிறது (முழு ஒப் பகுதியாக இருந்தாலும்), @@ -1666,7 +1664,6 @@ Organization Name,நிறுவன பெயர், Other,வேறு, Other Reports,பிற அறிக்கைகள், "Other outward supplies(Nil rated,Exempted)","பிற வெளிப்புற பொருட்கள் (இல்லை மதிப்பிடப்பட்டது, விலக்கு)", -Others,மற்றவை, Out Qty,அளவு அவுட், Out Value,அவுட் மதிப்பு, Out of Order,ஆர்டர் அவுட், @@ -2812,7 +2809,6 @@ Total (Credit),மொத்த (கடன்), Total (Without Tax),மொத்தம் (வரி இல்லாமல்), Total Achieved,மொத்த Achieved, Total Actual,உண்மையான மொத்த, -Total Allocated Leaves,மொத்த ஒதுக்கப்பட்ட இலைகள், Total Amount,மொத்த தொகை, Total Amount Credited,மொத்த தொகை பாராட்டப்பட்டது, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,கொள்முதல் ரசீது பொருட்கள் அட்டவணையில் மொத்த பொருந்தும் கட்டணங்கள் மொத்த வரி மற்றும் கட்டணங்கள் அதே இருக்க வேண்டும், @@ -2922,7 +2918,6 @@ Updating Variants...,மாறுபாடுகளைப் புதுப் Upload your letter head and logo. (you can edit them later).,உங்கள் கடிதம் தலை மற்றும் சின்னம் பதிவேற்ற. (நீங்கள் பின்னர் அவர்களை திருத்த முடியும்)., Upper Income,உயர் வருமானம், Use Sandbox,சாண்ட்பாக்ஸினைப் பயன்படுத்தவும், -Used Leaves,பயன்படுத்திய இலைகள், User,பயனர், User ID,பயனர் ஐடி, User ID not set for Employee {0},பயனர் ஐடி பணியாளர் அமைக்க{0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,ஊதிய செலவு மையம், Approvers,ஒப்புதல்கள், The first Approver in the list will be set as the default Approver.,பட்டியலில் முதல் ஒப்புதல் இயல்புநிலை ஒப்புதலாக அமைக்கப்படும்., Shift Request Approver,ஷிப்ட் கோரிக்கை ஒப்புதல், -PAN Number,பான் எண், Provident Fund Account,வருங்கால வைப்பு நிதி கணக்கு, MICR Code,MICR குறியீடு, Repay unclaimed amount from salary,கோரப்படாத தொகையை சம்பளத்திலிருந்து திருப்பிச் செலுத்துங்கள், Deduction from salary,சம்பளத்திலிருந்து கழித்தல், -Expired Leaves,காலாவதியான இலைகள், If this is not checked the loan by default will be considered as a Demand Loan,"இது சரிபார்க்கப்படாவிட்டால், இயல்புநிலையாக கடன் தேவை கடனாக கருதப்படும்", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,கடன் வாங்கியவரிடமிருந்து கடன் திருப்பிச் செலுத்துவதற்கு முன்பதிவு செய்வதற்கும் கடன் வாங்கியவருக்கு கடன்களை வழங்குவதற்கும் இந்த கணக்கு பயன்படுத்தப்படுகிறது, This account is capital account which is used to allocate capital for loan disbursal account ,"இந்த கணக்கு மூலதன கணக்கு ஆகும், இது கடன் வழங்கல் கணக்கிற்கு மூலதனத்தை ஒதுக்க பயன்படுகிறது", diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv index 228f293e8f..43b5bde593 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -286,7 +286,6 @@ Auto Repeat,ఆటో రిపీట్, Auto repeat document updated,ఆటో రిపీట్ పత్రం నవీకరించబడింది, Automotive,ఆటోమోటివ్, Available,అందుబాటులో, -Available Leaves,అందుబాటులో ఉన్న ఆకులు, Available Qty,అందుబాటులో ప్యాక్ చేసిన అంశాల, Available Selling,అందుబాటులో సెల్లింగ్, Available for use date is required,ఉపయోగ తేదీ కోసం అందుబాటులో ఉంది, @@ -1122,7 +1121,6 @@ Hub Category,హబ్ వర్గం, Hub Sync ID,హబ్ సమకాలీకరణ ID, Human Resource,మానవ వనరుల, Human Resources,మానవ వనరులు, -IFSC Code,IFSC కోడ్, IGST Amount,IGST మొత్తం, IP Address,IP చిరునామా, ITC Available (whether in full op part),ఐటిసి అందుబాటులో ఉంది (పూర్తిస్థాయిలో ఉన్నా), @@ -1666,7 +1664,6 @@ Organization Name,సంస్థ పేరు, Other,ఇతర, Other Reports,ఇతర నివేదికలు, "Other outward supplies(Nil rated,Exempted)","ఇతర బాహ్య సామాగ్రి (నిల్ రేట్, మినహాయింపు)", -Others,ఇతరత్రా, Out Qty,ప్యాక్ చేసిన అంశాల అవుట్, Out Value,అవుట్ విలువ, Out of Order,పనిచేయటంలేదు, @@ -2812,7 +2809,6 @@ Total (Credit),మొత్తం (క్రెడిట్), Total (Without Tax),మొత్తం (పన్ను లేకుండా), Total Achieved,మొత్తం ఆర్జిత, Total Actual,యదార్థమైన మొత్తం, -Total Allocated Leaves,మొత్తం కేటాయించిన ఆకులు, Total Amount,మొత్తం డబ్బు, Total Amount Credited,మొత్తం మొత్తంలో పొందింది, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,కొనుగోలు స్వీకరణపై అంశాలు పట్టికలో మొత్తం వర్తించే ఛార్జీలు మొత్తం పన్నులు మరియు ఆరోపణలు అదే ఉండాలి, @@ -2922,7 +2918,6 @@ Updating Variants...,వైవిధ్యాలను నవీకరిస్ Upload your letter head and logo. (you can edit them later).,మీ లేఖ తల మరియు లోగో అప్లోడ్. (మీరు తర్వాత వాటిని సవరించవచ్చు)., Upper Income,ఉన్నత ఆదాయపు, Use Sandbox,శాండ్బాక్స్ ఉపయోగించండి, -Used Leaves,వాడిన ఆకులు, User,వాడుకరి, User ID,వినియోగదారుని గుర్తింపు, User ID not set for Employee {0},వాడుకరి ID ఉద్యోగి సెట్ {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,పేరోల్ ఖర్చు కేంద్రం, Approvers,అప్రూవర్స్, The first Approver in the list will be set as the default Approver.,జాబితాలోని మొదటి ఆమోదం డిఫాల్ట్ ఆమోదంగా సెట్ చేయబడుతుంది., Shift Request Approver,షిఫ్ట్ అభ్యర్థన ఆమోదం, -PAN Number,పాన్ సంఖ్య, Provident Fund Account,ప్రావిడెంట్ ఫండ్ ఖాతా, MICR Code,MICR కోడ్, Repay unclaimed amount from salary,క్లెయిమ్ చేయని మొత్తాన్ని జీతం నుండి తిరిగి చెల్లించండి, Deduction from salary,జీతం నుండి మినహాయింపు, -Expired Leaves,గడువు ముగిసిన ఆకులు, If this is not checked the loan by default will be considered as a Demand Loan,ఇది తనిఖీ చేయకపోతే అప్పును అప్పుగా డిమాండ్ లోన్‌గా పరిగణిస్తారు, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,ఈ ఖాతా రుణగ్రహీత నుండి రుణ తిరిగి చెల్లించటానికి మరియు రుణగ్రహీతకు రుణాలు పంపిణీ చేయడానికి ఉపయోగించబడుతుంది, This account is capital account which is used to allocate capital for loan disbursal account ,"ఈ ఖాతా మూలధన ఖాతా, ఇది రుణ పంపిణీ ఖాతాకు మూలధనాన్ని కేటాయించడానికి ఉపయోగించబడుతుంది", diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv index 86b8d1581f..4930853759 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -286,7 +286,6 @@ Auto Repeat,ทำซ้ำอัตโนมัติ, Auto repeat document updated,อัปเดตเอกสารซ้ำอัตโนมัติแล้ว, Automotive,ยานยนต์, Available,ที่มีจำหน่าย, -Available Leaves,มีใบ, Available Qty,จำนวนที่มีจำหน่าย, Available Selling,ขายได้, Available for use date is required,ต้องมีวันที่ใช้งาน, @@ -1122,7 +1121,6 @@ Hub Category,หมวดหมู่ฮับ, Hub Sync ID,รหัสการซิงค์ของ Hub, Human Resource,ทรัพยากรมนุษย์, Human Resources,ทรัพยากรบุคคล, -IFSC Code,รหัส IFSC, IGST Amount,IGST Amount, IP Address,ที่อยู่ IP, ITC Available (whether in full op part),ITC ว่าง (ไม่ว่าจะอยู่ในส่วน op แบบเต็ม), @@ -1666,7 +1664,6 @@ Organization Name,ชื่อองค์กร, Other,อื่น ๆ, Other Reports,รายงานอื่น ๆ, "Other outward supplies(Nil rated,Exempted)","วัสดุภายนอกอื่น ๆ (ไม่มีการจัดอันดับ, ยกเว้น)", -Others,คนอื่น ๆ, Out Qty,ออก จำนวน, Out Value,ราคาออกมา, Out of Order,ชำรุด, @@ -2812,7 +2809,6 @@ Total (Credit),รวม (เครดิต), Total (Without Tax),รวม (ไม่มีภาษี), Total Achieved,รวมประสบความสำเร็จ, Total Actual,ทั้งหมดที่เกิดขึ้นจริง, -Total Allocated Leaves,ยอดรวมใบ, Total Amount,รวมเป็นเงิน, Total Amount Credited,ยอดรวมเครดิต, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,ค่าใช้จ่ายรวมในการซื้อโต๊ะใบเสร็จรับเงินรายการที่จะต้องเป็นเช่นเดียวกับภาษีและค่าใช้จ่ายรวม, @@ -2922,7 +2918,6 @@ Updating Variants...,กำลังอัปเดตชุดตัวเล Upload your letter head and logo. (you can edit them later).,อัปโหลดหัวจดหมายของคุณและโลโก้ (คุณสามารถแก้ไขได้ในภายหลัง), Upper Income,รายได้บน, Use Sandbox,ใช้ Sandbox, -Used Leaves,ใบที่ใช้แล้ว, User,ผู้ใช้งาน, User ID,รหัสผู้ใช้, User ID not set for Employee {0},รหัสผู้ใช้ ไม่ได้ ตั้งไว้สำหรับ พนักงาน {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,ศูนย์ต้นทุนบัญชีเงิน Approvers,ผู้อนุมัติ, The first Approver in the list will be set as the default Approver.,ผู้อนุมัติคนแรกในรายการจะถูกตั้งเป็นผู้อนุมัติเริ่มต้น, Shift Request Approver,ผู้อนุมัติคำขอกะ, -PAN Number,หมายเลข PAN, Provident Fund Account,บัญชีกองทุนสำรองเลี้ยงชีพ, MICR Code,รหัส MICR, Repay unclaimed amount from salary,ชำระคืนจำนวนเงินที่ไม่มีการเรียกร้องจากเงินเดือน, Deduction from salary,หักจากเงินเดือน, -Expired Leaves,ใบหมดอายุ, If this is not checked the loan by default will be considered as a Demand Loan,หากไม่ได้ตรวจสอบเงินกู้โดยค่าเริ่มต้นจะถือว่าเป็น Demand Loan, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,บัญชีนี้ใช้สำหรับจองการชำระคืนเงินกู้จากผู้กู้และการเบิกจ่ายเงินกู้ให้กับผู้กู้, This account is capital account which is used to allocate capital for loan disbursal account ,บัญชีนี้เป็นบัญชีทุนที่ใช้ในการจัดสรรเงินทุนสำหรับบัญชีการเบิกจ่ายเงินกู้, diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index 78d6a7998a..6d05deba16 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -286,7 +286,6 @@ Auto Repeat,Otomatik Tekrarla, Auto repeat document updated,Otomatik tekrar dokümanı güncellendi, Automotive,Otomotiv, Available,Mevcut, -Available Leaves,Mevcut İzinler, Available Qty,Mevcut Miktar, Available Selling,Mevcut Satış, Available for use date is required,Kullanılabilir olacağı tarih gereklidir, @@ -1122,7 +1121,6 @@ Hub Category,Hub Kategorisi, Hub Sync ID,Hub Senkronizasyon Kimliği, Human Resource,İnsan Kaynağı, Human Resources,İnsan Kaynakları, -IFSC Code,IFSC Kodu, IGST Amount,IGST Tutarı, IP Address,IP adresi, ITC Available (whether in full op part),ITC Müsait (tam hüküm olsun), @@ -1666,7 +1664,6 @@ Organization Name,Kuruluş adı, Other,Diğer, Other Reports,Diğer Raporlar, "Other outward supplies(Nil rated,Exempted)","Diğer dış sarf malzemeleri (Nil puan, Muaf)", -Others,Diğer, Out Qty,Çıkış Miktarı, Out Value,Çıkış Değeri, Out of Order,bozuk, @@ -2812,7 +2809,6 @@ Total (Credit),Toplam (Alacak), Total (Without Tax),Toplam (Vergi hariç), Total Achieved,Toplam Eldeki, Total Actual,Gerçek Toplam, -Total Allocated Leaves,Toplam Tahsis Edilen İzin, Total Amount,Toplam Tutar, Total Amount Credited,Kredili Toplam Tutar, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Satınalma Makbuzu Vergi Öğeleri tablosundaki toplam uygulanabilir Masraflar Toplam ve Masraflar aynı olmalıdır, @@ -2922,7 +2918,6 @@ Updating Variants...,Varyantlar Güncelleniyor..., Upload your letter head and logo. (you can edit them later).,Mektup baş ve logo yükleyin. (Daha sonra bunları düzenleyebilirsiniz)., Upper Income,üst gelir, Use Sandbox,Kullanım Sandbox, -Used Leaves,Kullanılan İzin, User,Kullanıcı, User ID,Kullanıcı kimliği, User ID not set for Employee {0},Çalışan {0} için kullanıcı sıfatı ayarlanmamış, @@ -7948,12 +7943,10 @@ Payroll Cost Center,Bordro Maliyet Merkezi, Approvers,Onaylayanlar, The first Approver in the list will be set as the default Approver.,"Listedeki ilk Onaylayan, varsayılan Onaylayan olarak ayarlanacaktır.", Shift Request Approver,Vardiya İsteği Onaylayıcısı, -PAN Number,PAN Numarası, Provident Fund Account,İhtiyat Fonu Hesabı, MICR Code,MICR Kodu, Repay unclaimed amount from salary,Talep edilmeyen maaştan geri ödeyin, Deduction from salary,Maaştan kesinti, -Expired Leaves,Süresi biten İzinler, If this is not checked the loan by default will be considered as a Demand Loan,"Bu kontrol reddedilirse, varsayılan olarak kredi bir Talep Kredisi olarak kabul edilecektir.", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Bu hesap, borçludan kredi geri ödemelerini yapmak ve ayrıca borçluya kredi vermek için kullanılır.", This account is capital account which is used to allocate capital for loan disbursal account ,"Bu hesap, kredi ödeme hesabına sermaye tahsis etmek için kullanılan sermaye hesabıdır.", diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv index 3ad588cda7..eafd38b3fb 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -286,7 +286,6 @@ Auto Repeat,Авто Повторіть, Auto repeat document updated,Автоматичний повторення документа оновлено, Automotive,Автомобільний, Available,наявний, -Available Leaves,Доступні листи, Available Qty,Доступна к-сть, Available Selling,Доступні продажі, Available for use date is required,Необхідна дата для використання, @@ -1122,7 +1121,6 @@ Hub Category,Категорія концентратора, Hub Sync ID,Ідентифікатор концентратора синхронізації, Human Resource,Людський ресурс, Human Resources,Кадри, -IFSC Code,IFSC Code, IGST Amount,Сума IGST, IP Address,IP-адреса, ITC Available (whether in full op part),Доступний ITC (будь то в повній частині, @@ -1666,7 +1664,6 @@ Organization Name,Назва організації, Other,Інший, Other Reports,Інші звіти, "Other outward supplies(Nil rated,Exempted)","Інші зовнішні поставки (з нульовою оцінкою, звільнено)", -Others,Інші, Out Qty,Розхід у к-сті, Out Value,Розхід у Сумі, Out of Order,Вийшов з ладу, @@ -2812,7 +2809,6 @@ Total (Credit),Разом (кредит), Total (Without Tax),Усього (без податку), Total Achieved,Всього Виконано, Total Actual,Загальний фактичний, -Total Allocated Leaves,Усього виділених листів, Total Amount,Загалом, Total Amount Credited,Загальна сума кредитується, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,"Всього Застосовуються збори в таблиці Purchase квитанцій Елементів повинні бути такими ж, як всі податки і збори", @@ -2922,7 +2918,6 @@ Updating Variants...,Оновлення варіантів ..., Upload your letter head and logo. (you can edit them later).,Відвантажити ваш фірмовий заголовок та логотип. (Ви зможете відредагувати їх пізніше)., Upper Income,Верхня прибуток, Use Sandbox,Використання Пісочниця, -Used Leaves,Використовувані листи, User,Користувач, User ID,ідентифікатор користувача, User ID not set for Employee {0},Ідентифікатор користувача не встановлений Employee {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Центр витрат на оплату праці, Approvers,Схвалювачі, The first Approver in the list will be set as the default Approver.,Перший затверджувач у списку буде встановлений як затверджувач за замовчуванням., Shift Request Approver,Затверджувач запиту на зміну, -PAN Number,Номер PAN, Provident Fund Account,Рахунок забезпеченого фонду, MICR Code,Кодекс MICR, Repay unclaimed amount from salary,Повернути незатребувану суму із заробітної плати, Deduction from salary,Відрахування із зарплати, -Expired Leaves,Листя закінчилися, If this is not checked the loan by default will be considered as a Demand Loan,"Якщо це не позначено, позика за замовчуванням буде розглядатися як позика на вимогу", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,"Цей рахунок використовується для бронювання виплат позики у позичальника, а також для видачі позик позичальнику", This account is capital account which is used to allocate capital for loan disbursal account ,"Цей рахунок є рахунком капіталу, який використовується для розподілу капіталу на рахунок виплати позики", diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv index c5610f010e..b661163388 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -286,7 +286,6 @@ Auto Repeat,آٹو دوبارہ دہرائیں, Auto repeat document updated,خود بخود دستاویز کو اپ ڈیٹ کیا, Automotive,آٹوموٹو, Available,دستیاب, -Available Leaves,دستیاب پتیوں, Available Qty,دستیاب مقدار, Available Selling,دستیاب فروخت, Available for use date is required,استعمال کی تاریخ کے لئے دستیاب ہے, @@ -1122,7 +1121,6 @@ Hub Category,حب زمرہ, Hub Sync ID,حب ہم آہنگی کی شناخت, Human Resource,انسانی وسائل, Human Resources,انسانی وسائل, -IFSC Code,IFSC کوڈ, IGST Amount,IGST رقم, IP Address,IP پتہ, ITC Available (whether in full op part),آئی ٹی سی دستیاب ہے (چاہے وہ پوری طرح سے ہو), @@ -1666,7 +1664,6 @@ Organization Name,تنظیم کا نام, Other,دیگر, Other Reports,دیگر رپورٹوں, "Other outward supplies(Nil rated,Exempted)",دیگر ظاہری رسد (نیل ریٹیڈ ، مستثنیٰ), -Others,دیگر, Out Qty,مقدار باہر, Out Value,آؤٹ ویلیو, Out of Order,حکم سے باہر, @@ -2812,7 +2809,6 @@ Total (Credit),کل (کریڈٹ), Total (Without Tax),کل (ٹیکس کے بغیر), Total Achieved,کل حاصل کیا, Total Actual,اصل کل, -Total Allocated Leaves,کل مختص شدہ پتیوں, Total Amount,کل رقم, Total Amount Credited,کریڈٹ کل رقم, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,خریداری کی رسید اشیا ٹیبل میں تمام قابل اطلاق چارجز کل ٹیکس اور الزامات طور پر ایک ہی ہونا چاہیے, @@ -2922,7 +2918,6 @@ Updating Variants...,مختلف حالتوں کو اپ ڈیٹ کیا جا رہا Upload your letter head and logo. (you can edit them later).,اپنے خط سر اور علامت (لوگو). (آپ کو بعد ان میں ترمیم کر سکتے ہیں)., Upper Income,بالائی آمدنی, Use Sandbox,سینڈباکس استعمال, -Used Leaves,استعمال شدہ پتیوں, User,صارف, User ID,صارف کی شناخت, User ID not set for Employee {0},صارف ID ملازم کے لئے مقرر نہیں {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,پےرول لاگت سنٹر, Approvers,تنازعہ, The first Approver in the list will be set as the default Approver.,فہرست میں پہلا منظور نامہ پہلے سے طے شدہ منظوری کے طور پر مقرر کیا جائے گا۔, Shift Request Approver,شفٹ کی درخواست منظور کریں, -PAN Number,پین نمبر, Provident Fund Account,پروویڈنٹ فنڈ اکاؤنٹ, MICR Code,ایم آئی سی آر کوڈ, Repay unclaimed amount from salary,تنخواہ سے غیر دعویدار رقم واپس کریں, Deduction from salary,تنخواہ سے کٹوتی, -Expired Leaves,ختم شدہ پتے, If this is not checked the loan by default will be considered as a Demand Loan,اگر اس کی جانچ نہیں کی گئی تو قرض کو بطور ڈیفانٹ لون سمجھا جائے گا, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,یہ اکاؤنٹ قرض لینے والے سے قرض کی واپسیوں کی بکنگ اور قرض لینے والے کو قرضوں کی تقسیم کے لئے استعمال ہوتا ہے, This account is capital account which is used to allocate capital for loan disbursal account ,یہ کھاتہ دارالحکومت کا کھاتہ ہے جو قرض تقسیم کے اکاؤنٹ کے لئے سرمایہ مختص کرنے کے لئے استعمال ہوتا ہے, diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv index a3fc3f41cd..8762284319 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -286,7 +286,6 @@ Auto Repeat,Avto-takrorlash, Auto repeat document updated,Avtomatik qayta-qayta hujjat yangilandi, Automotive,Avto, Available,Mavjud, -Available Leaves,Mavjud barglar, Available Qty,Mavjud Miqdor, Available Selling,Sotuvga tayyor, Available for use date is required,Foydalanish uchun mavjud bo'lgan sana talab qilinadi, @@ -1122,7 +1121,6 @@ Hub Category,Hub-toifa, Hub Sync ID,Uyadagi sinxronlash identifikatori, Human Resource,Inson resursi, Human Resources,Kadrlar bo'limi, -IFSC Code,IFSC kodi, IGST Amount,IGST miqdori, IP Address,IP manzili, ITC Available (whether in full op part),ITC mavjud (to'liq holatda bo'lsin), @@ -1666,7 +1664,6 @@ Organization Name,tashkilot nomi, Other,Boshqa, Other Reports,Boshqa hisobotlar, "Other outward supplies(Nil rated,Exempted)","Boshqa tashqi etkazib berish (Nil baholanmagan, ozod qilingan)", -Others,Boshqalar, Out Qty,Miqdori, Out Value,Chiqish qiymati, Out of Order,Tartibsiz, @@ -2812,7 +2809,6 @@ Total (Credit),Jami (kredit), Total (Without Tax),Hammasi bo'lib (soliqsiz), Total Achieved,Jami erishildi, Total Actual,Jami haqiqiy, -Total Allocated Leaves,Jami ajratilgan barglar, Total Amount,Umumiy hisob, Total Amount Credited,Jami kredit miqdori, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Buyurtma olish bo'yicha ma'lumotlar jamlanmasi jami soliqlar va yig'imlar bilan bir xil bo'lishi kerak, @@ -2922,7 +2918,6 @@ Updating Variants...,Variantlar yangilanmoqda ..., Upload your letter head and logo. (you can edit them later).,Sizning xat boshingizni va logotipingizni yuklang. (keyinchalik ularni tahrirlashingiz mumkin)., Upper Income,Yuqori daromad, Use Sandbox,Sandboxdan foydalaning, -Used Leaves,Ishlatilgan barglar, User,Foydalanuvchi, User ID,foydalanuvchi IDsi, User ID not set for Employee {0},Foydalanuvchining identifikatori {0} xizmatdoshiga o'rnatilmagan, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Ish haqi bo'yicha xarajatlar markazi, Approvers,Qarama-qarshilik, The first Approver in the list will be set as the default Approver.,Ro'yxatdagi birinchi Approver standart Approver sifatida o'rnatiladi., Shift Request Approver,Shift so'rovini tasdiqlovchi, -PAN Number,PAN raqami, Provident Fund Account,Provayder fondining hisobvarag'i, MICR Code,MICR kodi, Repay unclaimed amount from salary,Talab qilinmagan miqdorni ish haqidan qaytaring, Deduction from salary,Ish haqidan ushlab qolish, -Expired Leaves,Muddati o'tgan barglar, If this is not checked the loan by default will be considered as a Demand Loan,"Agar bu tekshirilmasa, sukut bo'yicha qarz talabga javob beradigan kredit sifatida qabul qilinadi", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Ushbu hisob qarz oluvchidan kreditni to'lashni bron qilish va qarz oluvchiga qarz berish uchun ishlatiladi, This account is capital account which is used to allocate capital for loan disbursal account ,"Ushbu hisob kapital hisobvarag'i bo'lib, u ssudani to'lash hisobiga kapital ajratish uchun ishlatiladi", diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv index 10bc92ae77..d5a932c9e3 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -286,7 +286,6 @@ Auto Repeat,Tự động lặp lại, Auto repeat document updated,Tự động cập nhật tài liệu được cập nhật, Automotive,Ô tô, Available,Khả dụng, -Available Leaves,Lá có sẵn, Available Qty,Số lượng có sẵn, Available Selling,Bán có sẵn, Available for use date is required,Có sẵn cho ngày sử dụng là bắt buộc, @@ -1122,7 +1121,6 @@ Hub Category,Danh mục trung tâm, Hub Sync ID,ID đồng bộ hóa của Hub, Human Resource,Nguồn nhân lực, Human Resources,Nhân sự, -IFSC Code,Mã IFSC, IGST Amount,Lượng IGST, IP Address,Địa chỉ IP, ITC Available (whether in full op part),ITC Có sẵn (cho dù trong phần op đầy đủ), @@ -1666,7 +1664,6 @@ Organization Name,tên tổ chức, Other,Khác, Other Reports,Báo cáo khác, "Other outward supplies(Nil rated,Exempted)","Các nguồn cung bên ngoài khác (Không được xếp hạng, Được miễn)", -Others,Các thông tin khác, Out Qty,Số lượng ra, Out Value,Giá trị hiện, Out of Order,Out of Order, @@ -2812,7 +2809,6 @@ Total (Credit),Tổng số (nợ), Total (Without Tax),Tổng (Không Thuế), Total Achieved,Tổng số đã đạt được, Total Actual,Tổng số thực tế, -Total Allocated Leaves,Tổng số lá được phân bổ, Total Amount,Tổng số, Total Amount Credited,Tổng số tiền được ghi có, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Tổng phí tại biên lai mua các mẫu hàng phải giống như tổng các loại thuế và phí, @@ -2922,7 +2918,6 @@ Updating Variants...,Cập nhật các biến thể ..., Upload your letter head and logo. (you can edit them later).,Tải lên tiêu đề trang và logo. (Bạn có thể chỉnh sửa chúng sau này)., Upper Income,Thu nhập trên, Use Sandbox,sử dụng Sandbox, -Used Leaves,Lá đã qua sử dụng, User,Người dùng, User ID,ID người dùng, User ID not set for Employee {0},ID người dùng không thiết lập cho nhân viên {0}, @@ -7949,12 +7944,10 @@ Payroll Cost Center,Trung tâm chi phí tính lương, Approvers,Người phê duyệt, The first Approver in the list will be set as the default Approver.,Người phê duyệt đầu tiên trong danh sách sẽ được đặt làm Người phê duyệt mặc định., Shift Request Approver,Người phê duyệt yêu cầu thay đổi, -PAN Number,Số PAN, Provident Fund Account,Tài khoản Quỹ cấp, MICR Code,Mã MICR, Repay unclaimed amount from salary,Hoàn trả số tiền chưa nhận được từ tiền lương, Deduction from salary,Khấu trừ lương, -Expired Leaves,Lá hết hạn, If this is not checked the loan by default will be considered as a Demand Loan,"Nếu điều này không được kiểm tra, khoản vay mặc định sẽ được coi là Khoản vay không kỳ hạn", This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,Tài khoản này được sử dụng để hoàn trả khoản vay từ người đi vay và cũng để giải ngân các khoản vay cho người vay, This account is capital account which is used to allocate capital for loan disbursal account ,Tài khoản này là tài khoản vốn dùng để cấp vốn cho tài khoản giải ngân cho vay, diff --git a/erpnext/translations/zh-TW.csv b/erpnext/translations/zh-TW.csv index 752c1f5d5e..b410c416a8 100644 --- a/erpnext/translations/zh-TW.csv +++ b/erpnext/translations/zh-TW.csv @@ -712,7 +712,6 @@ Rate at which Price list currency is converted to company's base currency,價目 Account {0} does not belong to company: {1},科目{0}不屬於公司:{1} Abbreviation already used for another company,另一家公司已使用此縮寫 Default Customer Group,預設客戶群組 -IFSC Code,IFSC代碼 "If disable, 'Rounded Total' field will not be visible in any transaction",如果禁用,“圓角總計”字段將不可見的任何交易 Operating Cost,營業成本 Produced Items,生產物品 @@ -4144,7 +4143,6 @@ Student Group: ,學生組: Finance Book Id,金融書籍ID, Safety Stock,安全庫存 Healthcare Settings,醫療設置 -Total Allocated Leaves,總分配的葉子 Progress % for a task cannot be more than 100.,為任務進度百分比不能超過100個。 Before reconciliation,調整前 Taxes and Charges Added (Company Currency),稅收和收費上架(公司貨幣) @@ -4523,7 +4521,6 @@ Test Code,測試代碼 Settings for website homepage,對網站的主頁設置 {0} is on hold till {1},{0}一直保持到{1} RFQs are not allowed for {0} due to a scorecard standing of {1},由於{1}的記分卡,{0}不允許使用RFQ, -Used Leaves,使用的葉子 Link Options,鏈接選項 Total Amount {0},總金額{0} Invalid attribute {0} {1},無效的屬性{0} {1} @@ -4654,7 +4651,6 @@ Academic Year Name,學年名稱 Contact Desc,聯絡倒序 Send regular summary reports via Email.,使用電子郵件發送定期匯總報告。 Please set default account in Expense Claim Type {0},請報銷類型設置默認科目{0} -Available Leaves,可用的葉子 Student Name,學生姓名 Item Manager,項目經理 Payroll Payable,應付職工薪酬 diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index bb30bd4a83..8887913066 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -286,7 +286,6 @@ Auto Repeat,自动重复, Auto repeat document updated,自动重复文件更新, Automotive,汽车, Available,可用的, -Available Leaves,可用休假天数, Available Qty,可用数量, Available Selling,可用销售, Available for use date is required,需要使用可用日期, @@ -1122,7 +1121,6 @@ Hub Category,集线器类别, Hub Sync ID,集线器同步ID, Human Resource,人力资源, Human Resources,人力资源, -IFSC Code,IFSC代码, IGST Amount,IGST金额, IP Address,IP地址, ITC Available (whether in full op part),ITC可用(无论是全部操作部分), @@ -1666,7 +1664,6 @@ Organization Name,组织名称, Other,其他, Other Reports,其他报表, "Other outward supplies(Nil rated,Exempted)",其他外向供应(未评级,豁免), -Others,他人, Out Qty,发出数量, Out Value,输出值, Out of Order,乱序, @@ -2812,7 +2809,6 @@ Total (Credit),总(信用), Total (Without Tax),总计(不含税), Total Achieved,总体上实现的, Total Actual,实际总和, -Total Allocated Leaves,合计已分配休假天数, Total Amount,总金额, Total Amount Credited,记入贷方的总金额, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,基于采购收货单信息计算的总税费必须与采购单(单头)的总税费一致, @@ -2922,7 +2918,6 @@ Updating Variants...,更新变体......, Upload your letter head and logo. (you can edit them later).,上传你的信头和logo。(您可以在以后对其进行编辑)。, Upper Income,高收入, Use Sandbox,使用沙盒, -Used Leaves,已休假(天数), User,用户, User ID,用户ID, User ID not set for Employee {0},员工设置{0}为设置用户ID, @@ -7949,12 +7944,10 @@ Payroll Cost Center,工资成本中心, Approvers,批准人, The first Approver in the list will be set as the default Approver.,列表中的第一个批准人将被设置为默认批准人。, Shift Request Approver,轮班请求批准人, -PAN Number,PAN号码, Provident Fund Account,公积金帐户, MICR Code,MICR代码, Repay unclaimed amount from salary,从工资中偿还无人认领的金额, Deduction from salary,从工资中扣除, -Expired Leaves,过期的叶子, If this is not checked the loan by default will be considered as a Demand Loan,如果未选中此选项,则默认情况下该贷款将被视为需求贷款, This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower,此帐户用于预订借款人的还款,也用于向借款人发放贷款, This account is capital account which is used to allocate capital for loan disbursal account ,该帐户是资本帐户,用于为贷款支付帐户分配资本, From 4b8dc8a35c2bc6e612fb4c5ded4bacdf9a2f90b2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:58:26 +0200 Subject: [PATCH 092/280] refactor: migrate translations from HRMS --- erpnext/translations/af.csv | 2 ++ erpnext/translations/am.csv | 2 ++ erpnext/translations/ar.csv | 2 ++ erpnext/translations/bg.csv | 2 ++ erpnext/translations/bn.csv | 2 ++ erpnext/translations/bs.csv | 2 ++ erpnext/translations/ca.csv | 2 ++ erpnext/translations/cs.csv | 2 ++ erpnext/translations/da.csv | 2 ++ erpnext/translations/de.csv | 2 ++ erpnext/translations/el.csv | 2 ++ erpnext/translations/es.csv | 2 ++ erpnext/translations/et.csv | 2 ++ erpnext/translations/fa.csv | 2 ++ erpnext/translations/fi.csv | 2 ++ erpnext/translations/fr.csv | 2 ++ erpnext/translations/gu.csv | 2 ++ erpnext/translations/he.csv | 2 ++ erpnext/translations/hi.csv | 2 ++ erpnext/translations/hr.csv | 2 ++ erpnext/translations/hu.csv | 2 ++ erpnext/translations/id.csv | 2 ++ erpnext/translations/is.csv | 2 ++ erpnext/translations/it.csv | 2 ++ erpnext/translations/ja.csv | 2 ++ erpnext/translations/km.csv | 2 ++ erpnext/translations/kn.csv | 2 ++ erpnext/translations/ko.csv | 2 ++ erpnext/translations/ku.csv | 2 ++ erpnext/translations/lo.csv | 2 ++ erpnext/translations/lt.csv | 2 ++ erpnext/translations/lv.csv | 2 ++ erpnext/translations/mk.csv | 2 ++ erpnext/translations/ml.csv | 2 ++ erpnext/translations/mr.csv | 2 ++ erpnext/translations/ms.csv | 2 ++ erpnext/translations/my.csv | 2 ++ erpnext/translations/nl.csv | 2 ++ erpnext/translations/no.csv | 2 ++ erpnext/translations/pl.csv | 2 ++ erpnext/translations/ps.csv | 2 ++ erpnext/translations/pt-BR.csv | 2 ++ erpnext/translations/pt.csv | 2 ++ erpnext/translations/ro.csv | 2 ++ erpnext/translations/ru.csv | 2 ++ erpnext/translations/rw.csv | 2 ++ erpnext/translations/si.csv | 2 ++ erpnext/translations/sk.csv | 2 ++ erpnext/translations/sl.csv | 2 ++ erpnext/translations/sq.csv | 2 ++ erpnext/translations/sr.csv | 2 ++ erpnext/translations/sv.csv | 2 ++ erpnext/translations/sw.csv | 2 ++ erpnext/translations/ta.csv | 2 ++ erpnext/translations/te.csv | 2 ++ erpnext/translations/th.csv | 2 ++ erpnext/translations/tr.csv | 2 ++ erpnext/translations/uk.csv | 2 ++ erpnext/translations/ur.csv | 2 ++ erpnext/translations/uz.csv | 2 ++ erpnext/translations/vi.csv | 2 ++ erpnext/translations/zh-TW.csv | 1 + erpnext/translations/zh.csv | 2 ++ 63 files changed, 125 insertions(+) diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index 3a34a4887a..d4b823d995 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globale verstek, Is Mandatory,Is verpligtend, WhatsApp,WhatsApp, Make a call,Maak 'n oproep, +Approve,goed te keur, +Reject,verwerp, diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv index 958ebe12c5..764868d8dc 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -8741,3 +8741,5 @@ Global Defaults,ዓለም አቀፍ ነባሪዎች, Is Mandatory,አስገዳጅ ነው, WhatsApp,ዋትስአፕ, Make a call,ደውል, +Approve,ማጽደቅ, +Reject,አይቀበሉ, diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index 1d62b8a4ed..4e03a18a5c 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -8741,3 +8741,5 @@ Global Defaults,افتراضيات العالمية, Is Mandatory,إلزامي, WhatsApp,ال WhatsApp, Make a call,إجراء مكالمة, +Approve,وافق, +Reject,رفض, diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv index e2b8b13aeb..8dff755941 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -8741,3 +8741,5 @@ Global Defaults,Глобални настройки по подразбиран Is Mandatory,Задължително, WhatsApp,WhatsApp, Make a call,Обадете се, +Approve,одобрявам, +Reject,Отхвърляне, diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv index d7b3744832..8a698dfca4 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -8741,3 +8741,5 @@ Global Defaults,আন্তর্জাতিক ডিফল্ট, Is Mandatory,আবশ্যক, WhatsApp,হোয়াটসঅ্যাপ, Make a call,ফোন করুন, +Approve,অনুমোদন করা, +Reject,প্রত্যাখ্যান, diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv index 5040f83ef5..7ba4a883cb 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globalne zadane postavke, Is Mandatory,Je obavezna, WhatsApp,WhatsApp, Make a call,Pozovite, +Approve,odobriti, +Reject,odbiti, diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv index 9789b0b69f..cce1f3a13b 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -8741,3 +8741,5 @@ Global Defaults,Valors per defecte globals, Is Mandatory,És obligatori, WhatsApp,Què tal, Make a call,Fer una trucada, +Approve,aprovar, +Reject,Rebutjar, diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv index c619b853b6..1072ed31fc 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globální Výchozí, Is Mandatory,Je povinná, WhatsApp,WhatsApp, Make a call,Zavolat, +Approve,Schvalovat, +Reject,Odmítnout, diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv index 6d43829fa1..138da5d1f2 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globale indstillinger, Is Mandatory,Er obligatorisk, WhatsApp,WhatsApp, Make a call,Ringe, +Approve,Godkende, +Reject,Afvise, diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 28749908ef..79777f2338 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -8826,3 +8826,5 @@ Global Defaults,Allgemeine Voreinstellungen, Is Mandatory,Ist obligatorisch, WhatsApp,WhatsApp, Make a call,Einen Anruf tätigen, +Approve,Genehmigen, +Reject,Ablehnen, diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv index 95977c621d..7a83cc5dcd 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -8741,3 +8741,5 @@ Global Defaults,Καθολικές προεπιλογές, Is Mandatory,Ειναι υποχρεωτικό, WhatsApp,WhatsApp, Make a call,Τηλεφώνησε, +Approve,Εγκρίνω, +Reject,Απορρίπτω, diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv index 5ea5a8b668..fadf7a78e2 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -8741,3 +8741,5 @@ Global Defaults,Predeterminados globales, Is Mandatory,Es obligatorio, WhatsApp,WhatsApp, Make a call,Haz una llamada, +Approve,Aprobar, +Reject,Rechazar, diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv index 4e047c9be1..4e26a82f85 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global Vaikeväärtused, Is Mandatory,On kohustuslik, WhatsApp,WhatsApp, Make a call,Helistage, +Approve,kinnitama, +Reject,tagasi lükkama, diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv index 545198fbd5..530965d8cf 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -8741,3 +8741,5 @@ Global Defaults,به طور پیش فرض جهانی, Is Mandatory,اجباری است, WhatsApp,واتس اپ, Make a call,تماس بگیر, +Approve,تایید, +Reject,رد کردن, diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv index e9ea261d88..6e9380cf74 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -8741,3 +8741,5 @@ Global Defaults,Yleiset oletusasetukset, Is Mandatory,On pakollinen, WhatsApp,WhatsApp, Make a call,Soita, +Approve,Hyväksyä, +Reject,Hylätä, diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 9a67835705..32fe9fffa0 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -8851,3 +8851,5 @@ Stage,Etape Probability,Probabilité Closing,Clôture Allow Sales,Autoriser à la vente +Approve,Approuver, +Reject,Rejeter, diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv index 6cfceef5e7..e2de8ce96d 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -8741,3 +8741,5 @@ Global Defaults,વૈશ્વિક ડિફૉલ્ટ્સ, Is Mandatory,ફરજિયાત છે, WhatsApp,વોટ્સેપ, Make a call,કોલ કરો, +Approve,મંજૂર, +Reject,નકારો, diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv index fb11d6b774..6cd900a7cf 100644 --- a/erpnext/translations/he.csv +++ b/erpnext/translations/he.csv @@ -8741,3 +8741,5 @@ Global Defaults,ברירות מחדל גלובליות, Is Mandatory,זה חובה, WhatsApp,ווטסאפ, Make a call,להתקשר, +Approve,לְאַשֵׁר, +Reject,לִדחוֹת, diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv index 4090a458de..388b502c11 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -8741,3 +8741,5 @@ Global Defaults,वैश्विक मूलभूत, Is Mandatory,अनिवार्य है, WhatsApp,WhatsApp, Make a call,कॉल करें, +Approve,मंजूर, +Reject,अस्वीकार, diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv index bafdcc1eca..b44babc7eb 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globalne zadane postavke, Is Mandatory,Je obavezno, WhatsApp,Što ima, Make a call,Uputi poziv, +Approve,Odobriti, +Reject,Odbiti, diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv index 9fc3a0382d..4ea5b9a714 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -8741,3 +8741,5 @@ Global Defaults,Általános beállítások, Is Mandatory,Kötelező, WhatsApp,WhatsApp, Make a call,Hívást kezdeményezni, +Approve,Jóváhagy, +Reject,Elutasít, diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv index e30951296d..d84c3fdcd6 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -8741,3 +8741,5 @@ Global Defaults,Standar Global, Is Mandatory,Apakah Wajib, WhatsApp,Ada apa, Make a call,Menelpon, +Approve,Menyetujui, +Reject,Menolak, diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv index 89c0de679d..7687e4a680 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global Vanskil, Is Mandatory,Er skylda, WhatsApp,WhatsApp, Make a call,Hringja, +Approve,Samþykkja, +Reject,Hafna, diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv index 9ae43e3d4c..b88cadad36 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -8741,3 +8741,5 @@ Global Defaults,Predefiniti Globali, Is Mandatory,È obbligatorio, WhatsApp,WhatsApp, Make a call,Effettuare una chiamata, +Approve,Approva, +Reject,Rifiutare, diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv index c61a70e888..11455bdf7d 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -8741,3 +8741,5 @@ Global Defaults,共通デフォルト設定, Is Mandatory,必須です, WhatsApp,WhatsApp, Make a call,電話を掛ける, +Approve,承認, +Reject,拒否, diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv index 90f9013b9c..46dcabaa86 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -8741,3 +8741,5 @@ Global Defaults,លំនាំដើមជាសកល, Is Mandatory,គឺចាំបាច់, WhatsApp,WhatsApp, Make a call,ធ្វើការហៅ, +Approve,អនុម័ត, +Reject,ច្រានចោល, diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv index 02eab1d831..18e44a1bb2 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -8741,3 +8741,5 @@ Global Defaults,ಜಾಗತಿಕ ಪೂರ್ವನಿಯೋಜಿತಗ Is Mandatory,ಕಡ್ಡಾಯವಾಗಿದೆ, WhatsApp,ವಾಟ್ಸಾಪ್, Make a call,ಕರೆ ಮಾಡಿ, +Approve,ಅನುಮೋದಿಸಿ, +Reject,ರಿಜೆಕ್ಟ್, diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv index ae99764268..788655c044 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -8741,3 +8741,5 @@ Global Defaults,글로벌 기본값, Is Mandatory,필수, WhatsApp,WhatsApp, Make a call,전화하다, +Approve,승인, +Reject,받지 않다, diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv index d94a37e453..a7fcf4e1ed 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -8741,3 +8741,5 @@ Global Defaults,Têrbûn Global, Is Mandatory,Erkdar e, WhatsApp,WhatsApp, Make a call,Bang bikin, +Approve,Destûrdan, +Reject,Refzkirin, diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv index 3213a1aa59..9b74f655b2 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -8741,3 +8741,5 @@ Global Defaults,ຄ່າເລີ່ມຕົ້ນຂອງໂລກ, Is Mandatory,ແມ່ນບັງຄັບ, WhatsApp,WhatsApp, Make a call,ໂທອອກ, +Approve,ອະນຸມັດ, +Reject,ປະຕິເສດ, diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv index 72294ee527..2c87256d2a 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global Numatytasis, Is Mandatory,Yra privaloma, WhatsApp,„WhatsApp“, Make a call,Paskambinti, +Approve,Patvirtinti, +Reject,Atmesti, diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv index c95189c29a..2cfa1306f1 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globālie Noklusējumi, Is Mandatory,Ir obligāta, WhatsApp,WhatsApp, Make a call,Piezvanīt, +Approve,Apstiprināt, +Reject,Noraidīt, diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv index a322861413..f01b1b0cf2 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -8741,3 +8741,5 @@ Global Defaults,Глобална Стандардни, Is Mandatory,Задолжително е, WhatsApp,WhatsApp, Make a call,Направи повик, +Approve,Одобри, +Reject,Reject, diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv index c74d4a0dc5..59d3160d39 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -8741,3 +8741,5 @@ Global Defaults,ആഗോള സ്ഥിരസ്ഥിതികൾ, Is Mandatory,നിർബന്ധമാണ്, WhatsApp,വാട്ട്‌സ്ആപ്പ്, Make a call,ഒരു കാൾ ചെയ്യുക, +Approve,അംഗീകരിക്കുക, +Reject,നിരസിക്കുക, diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv index 28fab20973..ff339b6d9f 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -8741,3 +8741,5 @@ Global Defaults,ग्लोबल डीफॉल्ट, Is Mandatory,अनिवार्य आहे, WhatsApp,व्हॉट्सअ‍ॅप, Make a call,कॉल करा, +Approve,मंजूर, +Reject,नकार, diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv index f3c53f6122..2258a1804f 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -8741,3 +8741,5 @@ Global Defaults,Lalai Global, Is Mandatory,Adakah Wajib, WhatsApp,WhatsApp, Make a call,Buat panggilan, +Approve,Terima, +Reject,Tolak, diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv index ab56fee61d..dc5ab12f43 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -8741,3 +8741,5 @@ Global Defaults,ဂလိုဘယ် Defaults ကို, Is Mandatory,မသင်မနေရဖြစ်ပါတယ်, WhatsApp,နင်, Make a call,ခေါ်ဆိုပါ, +Approve,အတည်ပြု, +Reject,ပယ်ချ, diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index 839459ce10..ad11eae08b 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global Standaardwaarden, Is Mandatory,Is verplicht, WhatsApp,WhatsApp, Make a call,Bellen, +Approve,Goedkeuren, +Reject,afwijzen, diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv index 7491ab34cf..b136e97954 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global Defaults, Is Mandatory,Er obligatorisk, WhatsApp,Hva skjer, Make a call,Ta en telefon, +Approve,Vedta, +Reject,Avvis, diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv index cfb1831198..9be56f36f7 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -8652,3 +8652,5 @@ Global Defaults,Globalne wartości domyślne, Is Mandatory,Jest obowiązkowe, WhatsApp,WhatsApp, Make a call,Zadzwoń, +Approve,Zatwierdzać, +Reject,Odrzucać, diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv index 8740441d9b..8033752f78 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global افتراضیو, Is Mandatory,لازمي دی, WhatsApp,WhatsApp, Make a call,یو زنګ ووهه, +Approve,منظور کړل, +Reject,رد, diff --git a/erpnext/translations/pt-BR.csv b/erpnext/translations/pt-BR.csv index fee6b5e7b1..9823470afb 100644 --- a/erpnext/translations/pt-BR.csv +++ b/erpnext/translations/pt-BR.csv @@ -8741,3 +8741,5 @@ Global Defaults,Padrões Gerais, Is Mandatory,É mandatório, WhatsApp,Whatsapp, Make a call,Faça uma ligação, +Approve,Aprovar, +Reject,Rejeitar, diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv index 1d6b3fe182..c2afe3216d 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -8741,3 +8741,5 @@ Global Defaults,Padrões Gerais, Is Mandatory,É mandatório, WhatsApp,Whatsapp, Make a call,Faça uma ligação, +Approve,Aprovar, +Reject,Rejeitar, diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv index e6ce315809..0cb3f84278 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -8741,3 +8741,5 @@ Global Defaults,Valori Implicite Globale, Is Mandatory,Este obligatoriu, WhatsApp,WhatsApp, Make a call,Efectua un apel, +Approve,Aproba, +Reject,Respinge, diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 5437c673f6..da4e1bef82 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -8661,3 +8661,5 @@ Is Mandatory,Является обязательным, WhatsApp,WhatsApp, Make a call,Позвонить, Is Template,Является шаблоном, +Approve,Одобрить, +Reject,Отклонить, diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv index 4d883dcb6b..ae0cb3a908 100644 --- a/erpnext/translations/rw.csv +++ b/erpnext/translations/rw.csv @@ -8741,3 +8741,5 @@ Global Defaults,Mburabuzi, Is Mandatory,Ni itegeko, WhatsApp,WhatsApp, Make a call,Hamagara, +Approve,Emeza, +Reject,Wange, diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv index 2c0390f1ab..fa6fbf090a 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -8741,3 +8741,5 @@ Global Defaults,ගෝලීය Defaults, Is Mandatory,අනිවාර්යයි, WhatsApp,WhatsApp, Make a call,ඇමතුමක් ගන්න, +Approve,අනුමත, +Reject,ප්රතික්ෂේප, diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv index 9f24a6589a..0e51158b27 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globální Výchozí, Is Mandatory,Je povinné, WhatsApp,WhatsApp, Make a call,Zavolať, +Approve,schvaľovať, +Reject,Odmietnuť, diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv index fe43400501..7c4e11b63f 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globalni Privzeto, Is Mandatory,Je obvezno, WhatsApp,WhatsApp, Make a call,Opraviti klic, +Approve,Odobri, +Reject,Zavrni, diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv index 214cc66b5f..0f10795113 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -8741,3 +8741,5 @@ Global Defaults,Defaults Global, Is Mandatory,Shtë e Detyrueshme, WhatsApp,WhatsApp, Make a call,Bej nje telefonate, +Approve,miratoj, +Reject,hedh poshtë, diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv index c5b2702cb4..38116ecc9e 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -8741,3 +8741,5 @@ Global Defaults,Глобални Дефаултс, Is Mandatory,Је обавезно, WhatsApp,ВхатсАпп, Make a call,Позивање, +Approve,одобрити, +Reject,Одбити, diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv index 141b5718b2..c4d46ea130 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -8741,3 +8741,5 @@ Global Defaults,Globala standardinställningar, Is Mandatory,Är obligatorisk, WhatsApp,WhatsApp, Make a call,Ringa ett samtal, +Approve,Godkänna, +Reject,Avvisa, diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv index cead023dbd..ad144f04d6 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -8741,3 +8741,5 @@ Global Defaults,Ufafanuzi wa Global, Is Mandatory,Ni lazima, WhatsApp,WhatsApp, Make a call,Piga simu, +Approve,Thibitisha, +Reject,Kataa, diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv index 61417c7aeb..5ccabc044d 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -8741,3 +8741,5 @@ Global Defaults,உலக இயல்புநிலைகளுக்கு, Is Mandatory,அத்தியாவசியமானதாகும், WhatsApp,பகிரி, Make a call,அழைப்பு விடுங்கள், +Approve,ஒப்புதல், +Reject,நிராகரி, diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv index 43b5bde593..8472163af5 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -8741,3 +8741,5 @@ Global Defaults,గ్లోబల్ డిఫాల్ట్, Is Mandatory,తప్పనిసరి, WhatsApp,వాట్సాప్, Make a call,కాల్ చేయుము, +Approve,ఆమోదించడానికి, +Reject,తిరస్కరించు, diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv index 4930853759..dcd632bb3c 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -8741,3 +8741,5 @@ Global Defaults,เริ่มต้นทั่วโลก, Is Mandatory,เป็นข้อบังคับ, WhatsApp,WhatsApp, Make a call,ทำการโทร, +Approve,อนุมัติ, +Reject,ปฏิเสธ, diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index 6d05deba16..3708246a9d 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -8998,3 +8998,5 @@ Global Defaults,Genel Varsayılanlar, Is Mandatory,Zorunludur, WhatsApp,WhatsApp, Make a call,Arama yap, +Approve,Onayla, +Reject,Reddet, diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv index eafd38b3fb..1752330754 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -8741,3 +8741,5 @@ Global Defaults,Глобальні значення за замовчуванн Is Mandatory,Обов’язковий, WhatsApp,WhatsApp, Make a call,Зроби дзвінок, +Approve,Затвердити, +Reject,відхиляти, diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv index b661163388..504cc8de0c 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -8741,3 +8741,5 @@ Global Defaults,گلوبل ڈیفالٹس, Is Mandatory,لازمی ہے, WhatsApp,واٹس ایپ, Make a call,بنائیں ایک کال, +Approve,منظور کریں, +Reject,مسترد, diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv index 8762284319..6b25e7b152 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -8741,3 +8741,5 @@ Global Defaults,Global standartlar, Is Mandatory,Majburiy, WhatsApp,WhatsApp, Make a call,Qo'ng'iroq qiling, +Approve,Tasdiqlang, +Reject,Rad etish, diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv index d5a932c9e3..e576ef7119 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -8741,3 +8741,5 @@ Global Defaults,Mặc định toàn cầu, Is Mandatory,Bắt buộc, WhatsApp,WhatsApp, Make a call,Thực hiện cuộc gọi, +Approve,Tán thành, +Reject,Từ chối, diff --git a/erpnext/translations/zh-TW.csv b/erpnext/translations/zh-TW.csv index b410c416a8..699d802206 100644 --- a/erpnext/translations/zh-TW.csv +++ b/erpnext/translations/zh-TW.csv @@ -5971,3 +5971,4 @@ Looks like someone sent you to an incomplete URL. Please ask them to look into i Payment Gateway,支付網關 Address and Contacts,地址和聯絡方式 Plan Name,計劃名稱 +Reject,拒絕 diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index 8887913066..70ec81af26 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -8741,3 +8741,5 @@ Global Defaults,全局默认值, Is Mandatory,是强制性的, WhatsApp,WhatsApp的, Make a call,打个电话, +Approve,同意, +Reject,拒绝, From 10c666bf69ddf668e6828719f87ae5e2d992de1a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:43:13 +0530 Subject: [PATCH 093/280] fix: incorrect status of the returned purchase receipt (backport #37300) (#37380) fix: incorrect status of the returned purchase receipt (#37300) (cherry picked from commit 63f45739e05d728e8844320ff0c7dbafa8660acf) Co-authored-by: rohitwaghchaure --- erpnext/controllers/taxes_and_totals.py | 4 ++- .../public/js/controllers/taxes_and_totals.js | 10 ++++++- .../purchase_receipt/purchase_receipt.py | 4 +++ .../purchase_receipt/test_purchase_receipt.py | 26 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 62d4c53868..95bf0e4688 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -190,7 +190,9 @@ class calculate_taxes_and_totals(object): item.net_rate = item.rate - if not item.qty and self.doc.get("is_return"): + if ( + not item.qty and self.doc.get("is_return") and self.doc.get("doctype") != "Purchase Receipt" + ): item.amount = flt(-1 * item.rate, item.precision("amount")) elif not item.qty and self.doc.get("is_debit_note"): item.amount = flt(item.rate, item.precision("amount")) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index eeb09cb8b0..70b70c3d28 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -135,7 +135,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } else { // allow for '0' qty on Credit/Debit notes - let qty = item.qty || (me.frm.doc.is_debit_note ? 1 : -1); + let qty = flt(item.qty); + if (!qty) { + qty = (me.frm.doc.is_debit_note ? 1 : -1); + if (me.frm.doc.doctype !== "Purchase Receipt" && me.frm.doc.is_return === 1) { + // In case of Purchase Receipt, qty can be 0 if all items are rejected + qty = flt(item.qty); + } + } + item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item)); } diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 04eff54c43..6afa86e34e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -956,6 +956,10 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate total_amount += total_billable_amount total_billed_amount += flt(item.billed_amt) + + if pr_doc.get("is_return") and not total_amount and total_billed_amount: + total_amount = total_billed_amount + if adjust_incoming_rate: adjusted_amt = 0.0 if item.billed_amt and item.amount: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b7712ee5ce..a8ef5e8e48 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2060,6 +2060,32 @@ class TestPurchaseReceipt(FrappeTestCase): company.enable_provisional_accounting_for_non_stock_items = 0 company.save() + def test_purchase_return_status_with_debit_note(self): + pr = make_purchase_receipt(rejected_qty=10, received_qty=10, rate=100, do_not_save=1) + pr.items[0].qty = 0 + pr.items[0].stock_qty = 0 + pr.submit() + + return_pr = make_purchase_receipt( + is_return=1, + return_against=pr.name, + qty=0, + rejected_qty=10 * -1, + received_qty=10 * -1, + do_not_save=1, + ) + return_pr.items[0].qty = 0.0 + return_pr.items[0].stock_qty = 0.0 + return_pr.submit() + + self.assertEqual(return_pr.status, "To Bill") + + pi = make_purchase_invoice(return_pr.name) + pi.submit() + + return_pr.reload() + self.assertEqual(return_pr.status, "Completed") + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 40e7c43ce3970183b7effb53c8246af75d62b58c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 7 Oct 2023 18:24:46 +0530 Subject: [PATCH 094/280] refactor: import financial_statement in erpnext bundle --- .../report/balance_sheet/balance_sheet.js | 36 ++- .../accounts/report/cash_flow/cash_flow.js | 34 +-- .../consolidated_financial_statement.js | 286 +++++++++--------- .../dimension_wise_accounts_balance_report.js | 142 +++++---- .../gross_and_net_profit_report.js | 27 +- .../profit_and_loss_statement.js | 22 +- .../profitability_analysis.js | 232 +++++++------- .../report/trial_balance/trial_balance.js | 218 +++++++------ erpnext/public/js/erpnext.bundle.js | 1 + 9 files changed, 491 insertions(+), 507 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index ecc13d7dc8..c2b57f768f 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -1,25 +1,23 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function () { - frappe.query_reports["Balance Sheet"] = $.extend( - {}, - erpnext.financial_statements - ); +frappe.query_reports["Balance Sheet"] = $.extend( + {}, + erpnext.financial_statements +); - erpnext.utils.add_dimensions("Balance Sheet", 10); +erpnext.utils.add_dimensions("Balance Sheet", 10); - frappe.query_reports["Balance Sheet"]["filters"].push({ - fieldname: "accumulated_values", - label: __("Accumulated Values"), - fieldtype: "Check", - default: 1, - }); - - frappe.query_reports["Balance Sheet"]["filters"].push({ - fieldname: "include_default_book_entries", - label: __("Include Default Book Entries"), - fieldtype: "Check", - default: 1, - }); +frappe.query_reports["Balance Sheet"]["filters"].push({ + fieldname: "accumulated_values", + label: __("Accumulated Values"), + fieldtype: "Check", + default: 1, +}); + +frappe.query_reports["Balance Sheet"]["filters"].push({ + fieldname: "include_default_book_entries", + label: __("Include Default Book Entries"), + fieldtype: "Check", + default: 1, }); diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js index a2c34c6ee2..6b8ed27e64 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.js +++ b/erpnext/accounts/report/cash_flow/cash_flow.js @@ -1,24 +1,24 @@ // Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Cash Flow"] = $.extend({}, - erpnext.financial_statements); +frappe.query_reports["Cash Flow"] = $.extend( + {}, + erpnext.financial_statements +); - erpnext.utils.add_dimensions('Cash Flow', 10); +erpnext.utils.add_dimensions('Cash Flow', 10); - // The last item in the array is the definition for Presentation Currency - // filter. It won't be used in cash flow for now so we pop it. Please take - // of this if you are working here. +// The last item in the array is the definition for Presentation Currency +// filter. It won't be used in cash flow for now so we pop it. Please take +// of this if you are working here. - frappe.query_reports["Cash Flow"]["filters"].splice(8, 1); +frappe.query_reports["Cash Flow"]["filters"].splice(8, 1); - frappe.query_reports["Cash Flow"]["filters"].push( - { - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 - } - ); -}); +frappe.query_reports["Cash Flow"]["filters"].push( + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check", + "default": 1 + } +); diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 1afa8d5625..590408c6f8 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -2,152 +2,150 @@ // For license information, please see license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Consolidated Financial Statement"] = { - "filters": [ - { - "fieldname":"company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname":"filter_based_on", - "label": __("Filter Based On"), - "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": ["Fiscal Year"], - "reqd": 1, - on_change: function() { - let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); - frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); - - frappe.query_report.refresh(); - } - }, - { - "fieldname":"period_start_date", - "label": __("Start Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"period_end_date", - "label": __("End Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"from_fiscal_year", - "label": __("Start Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), - "reqd": 1, - on_change: () => { - frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) { - let year_start_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), "year_start_date"); - frappe.query_report.set_filter_value({ - period_start_date: year_start_date - }); - }); - } - }, - { - "fieldname":"to_fiscal_year", - "label": __("End Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), - "reqd": 1, - on_change: () => { - frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) { - let year_end_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), "year_end_date"); - frappe.query_report.set_filter_value({ - period_end_date: year_end_date - }); - }); - } - }, - { - "fieldname":"finance_book", - "label": __("Finance Book"), - "fieldtype": "Link", - "options": "Finance Book" - }, - { - "fieldname":"report", - "label": __("Report"), - "fieldtype": "Select", - "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], - "default": "Balance Sheet", - "reqd": 1 - }, - { - "fieldname": "presentation_currency", - "label": __("Currency"), - "fieldtype": "Select", - "options": erpnext.get_presentation_currency_list(), - "default": frappe.defaults.get_user_default("Currency") - }, - { - "fieldname":"accumulated_in_group_company", - "label": __("Accumulated Values in Group Company"), - "fieldtype": "Check", - "default": 0 - }, - { - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 - }, - { - "fieldname": "show_zero_values", - "label": __("Show zero values"), - "fieldtype": "Check" - } - ], - "formatter": function(value, row, column, data, default_formatter) { - if (data && column.fieldname=="account") { - value = data.account_name || value; - - column.link_onclick = - "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; - column.is_tree = true; - } - - if (data && data.account && column.apply_currency_formatter) { - data.currency = erpnext.get_currency(column.company_name); - } - - value = default_formatter(value, row, column, data); - if (!data.parent_account) { - value = $(`${value}`); - - var $value = $(value).css("font-weight", "bold"); - - value = $value.wrap("

").parent().html(); - } - return value; +frappe.query_reports["Consolidated Financial Statement"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 }, - onload: function() { - let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today()); + { + "fieldname":"filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1, + on_change: function() { + let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); + frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); + frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - period_start_date: fy.year_start_date, - period_end_date: fy.year_end_date + frappe.query_report.refresh(); + } + }, + { + "fieldname":"period_start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"period_end_date", + "label": __("End Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"from_fiscal_year", + "label": __("Start Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), + "reqd": 1, + on_change: () => { + frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) { + let year_start_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), "year_start_date"); + frappe.query_report.set_filter_value({ + period_start_date: year_start_date + }); }); - }); + } + }, + { + "fieldname":"to_fiscal_year", + "label": __("End Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), + "reqd": 1, + on_change: () => { + frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) { + let year_end_date = frappe.model.get_value("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), "year_end_date"); + frappe.query_report.set_filter_value({ + period_end_date: year_end_date + }); + }); + } + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book" + }, + { + "fieldname":"report", + "label": __("Report"), + "fieldtype": "Select", + "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], + "default": "Balance Sheet", + "reqd": 1 + }, + { + "fieldname": "presentation_currency", + "label": __("Currency"), + "fieldtype": "Select", + "options": erpnext.get_presentation_currency_list(), + "default": frappe.defaults.get_user_default("Currency") + }, + { + "fieldname":"accumulated_in_group_company", + "label": __("Accumulated Values in Group Company"), + "fieldtype": "Check", + "default": 0 + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check", + "default": 1 + }, + { + "fieldname": "show_zero_values", + "label": __("Show zero values"), + "fieldtype": "Check" } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data && column.fieldname=="account") { + value = data.account_name || value; + + column.link_onclick = + "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + column.is_tree = true; + } + + if (data && data.account && column.apply_currency_formatter) { + data.currency = erpnext.get_currency(column.company_name); + } + + value = default_formatter(value, row, column, data); + if (!data.parent_account) { + value = $(`${value}`); + + var $value = $(value).css("font-weight", "bold"); + + value = $value.wrap("

").parent().html(); + } + return value; + }, + onload: function() { + let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today()); + + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + period_start_date: fy.year_start_date, + period_end_date: fy.year_end_date + }); + }); } -}); +} diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js index 79e5a0997f..51fa8c83f3 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js @@ -2,83 +2,81 @@ // For license information, please see license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Dimension-wise Accounts Balance Report"] = { - "filters": [ - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), - "reqd": 1, - "on_change": function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - from_date: fy.year_start_date, - to_date: fy.year_end_date - }); - }); +frappe.query_reports["Dimension-wise Accounts Balance Report"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; } - }, - { - "fieldname": "from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], - "reqd": 1 - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], - "reqd": 1 - }, - { - "fieldname": "finance_book", - "label": __("Finance Book"), - "fieldtype": "Link", - "options": "Finance Book", - }, - { - "fieldname": "dimension", - "label": __("Select Dimension"), - "fieldtype": "Select", - "default": "Cost Center", - "options": get_accounting_dimension_options(), - "reqd": 1, - }, - ], - "formatter": erpnext.financial_statements.formatter, - "tree": true, - "name_field": "account", - "parent_field": "parent_account", - "initial_depth": 3 - } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date + }); + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], + "reqd": 1 + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], + "reqd": 1 + }, + { + "fieldname": "finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + }, + { + "fieldname": "dimension", + "label": __("Select Dimension"), + "fieldtype": "Select", + "default": "Cost Center", + "options": get_accounting_dimension_options(), + "reqd": 1, + }, + ], + "formatter": erpnext.financial_statements.formatter, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 +} -}); function get_accounting_dimension_options() { let options =["Cost Center", "Project"]; frappe.db.get_list('Accounting Dimension', - {fields:['document_type']}).then((res) => { - res.forEach((dimension) => { - options.push(dimension.document_type); - }); - }); + {fields:['document_type']}).then((res) => { + res.forEach((dimension) => { + options.push(dimension.document_type); + }); + }); return options } diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js index f6b0b8c3f7..40d4259da9 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js @@ -2,20 +2,15 @@ // For license information, please see license.txt -frappe.query_reports["Gross and Net Profit Report"] = { - "filters": [ +frappe.query_reports["Gross and Net Profit Report"] = $.extend( + {}, + erpnext.financial_statements +); - ] -} -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Gross and Net Profit Report"] = $.extend({}, - erpnext.financial_statements); - - frappe.query_reports["Gross and Net Profit Report"]["filters"].push( - { - "fieldname": "accumulated_values", - "label": __("Accumulated Values"), - "fieldtype": "Check" - } - ); -}); +frappe.query_reports["Gross and Net Profit Report"]["filters"].push( + { + "fieldname": "accumulated_values", + "label": __("Accumulated Values"), + "fieldtype": "Check" + } +); diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index 9fe93b9772..e5898bf69d 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -1,18 +1,16 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function () { - frappe.query_reports["Profit and Loss Statement"] = $.extend( - {}, - erpnext.financial_statements - ); +frappe.query_reports["Profit and Loss Statement"] = $.extend( + {}, + erpnext.financial_statements +); - erpnext.utils.add_dimensions("Profit and Loss Statement", 10); +erpnext.utils.add_dimensions("Profit and Loss Statement", 10); - frappe.query_reports["Profit and Loss Statement"]["filters"].push({ - fieldname: "accumulated_values", - label: __("Accumulated Values"), - fieldtype: "Check", - default: 1, - }); +frappe.query_reports["Profit and Loss Statement"]["filters"].push({ + fieldname: "accumulated_values", + label: __("Accumulated Values"), + fieldtype: "Check", + default: 1, }); diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index ebd0ec1a73..4a3d9bb479 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -1,133 +1,131 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Profitability Analysis"] = { - "filters": [ - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname": "based_on", - "label": __("Based On"), - "fieldtype": "Select", - "options": ["Cost Center", "Project", "Accounting Dimension"], - "default": "Cost Center", - "reqd": 1, - "on_change": function(query_report){ - let based_on = query_report.get_values().based_on; - if(based_on!='Accounting Dimension'){ - frappe.query_report.set_filter_value({ - accounting_dimension: '' - }); - } - } - }, - { - "fieldname": "accounting_dimension", - "label": __("Accounting Dimension"), - "fieldtype": "Link", - "options": "Accounting Dimension", - "get_query": () =>{ - return { - filters: { - "disabled": 0 - } - } - } - }, - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), - "reqd": 1, - "on_change": function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - from_date: fy.year_start_date, - to_date: fy.year_end_date - }); +frappe.query_reports["Profitability Analysis"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "based_on", + "label": __("Based On"), + "fieldtype": "Select", + "options": ["Cost Center", "Project", "Accounting Dimension"], + "default": "Cost Center", + "reqd": 1, + "on_change": function(query_report){ + let based_on = query_report.get_values().based_on; + if(based_on!='Accounting Dimension'){ + frappe.query_report.set_filter_value({ + accounting_dimension: '' }); } - }, - { - "fieldname": "from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], - }, - { - "fieldname": "show_zero_values", - "label": __("Show zero values"), - "fieldtype": "Check" } - ], - "formatter": function(value, row, column, data, default_formatter) { - if (column.fieldname=="account") { - value = data.account_name; - - column.link_onclick = - "frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(data) + ")"; - column.is_tree = true; - } - - value = default_formatter(value, row, column, data); - - if (!data.parent_account && data.based_on != 'project') { - value = $(`${value}`); - var $value = $(value).css("font-weight", "bold"); - if (data.warn_if_negative && data[column.fieldname] < 0) { - $value.addClass("text-danger"); + }, + { + "fieldname": "accounting_dimension", + "label": __("Accounting Dimension"), + "fieldtype": "Link", + "options": "Accounting Dimension", + "get_query": () =>{ + return { + filters: { + "disabled": 0 + } } + } + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date + }); + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], + }, + { + "fieldname": "show_zero_values", + "label": __("Show zero values"), + "fieldtype": "Check" + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (column.fieldname=="account") { + value = data.account_name; - value = $value.wrap("

").parent().html(); + column.link_onclick = + "frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(data) + ")"; + column.is_tree = true; + } + + value = default_formatter(value, row, column, data); + + if (!data.parent_account && data.based_on != 'project') { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + if (data.warn_if_negative && data[column.fieldname] < 0) { + $value.addClass("text-danger"); } - return value; - }, - "open_profit_and_loss_statement": function(data) { - if (!data.account) return; + value = $value.wrap("

").parent().html(); + } - frappe.route_options = { - "company": frappe.query_report.get_filter_value('company'), - "from_fiscal_year": data.fiscal_year, - "to_fiscal_year": data.fiscal_year - }; + return value; + }, + "open_profit_and_loss_statement": function(data) { + if (!data.account) return; - if(data.based_on == 'Cost Center'){ - frappe.route_options["cost_center"] = data.account - } else { - frappe.route_options["project"] = data.account - } + frappe.route_options = { + "company": frappe.query_report.get_filter_value('company'), + "from_fiscal_year": data.fiscal_year, + "to_fiscal_year": data.fiscal_year + }; - frappe.set_route("query-report", "Profit and Loss Statement"); - }, - "tree": true, - "name_field": "account", - "parent_field": "parent_account", - "initial_depth": 3 - } + if(data.based_on == 'Cost Center'){ + frappe.route_options["cost_center"] = data.account + } else { + frappe.route_options["project"] = data.account + } - erpnext.dimension_filters.forEach((dimension) => { - frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]); - }); + frappe.set_route("query-report", "Profit and Loss Statement"); + }, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 +} +erpnext.dimension_filters.forEach((dimension) => { + frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]); }); + diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index c12ab0ff91..edd40b68ef 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -1,118 +1,116 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Trial Balance"] = { - "filters": [ - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), - "reqd": 1, - "on_change": function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - from_date: fy.year_start_date, - to_date: fy.year_end_date - }); +frappe.query_reports["Trial Balance"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date }); - } - }, - { - "fieldname": "from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], - }, - { - "fieldname": "cost_center", - "label": __("Cost Center"), - "fieldtype": "Link", - "options": "Cost Center", - "get_query": function() { - var company = frappe.query_report.get_filter_value('company'); - return { - "doctype": "Cost Center", - "filters": { - "company": company, - } + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], + }, + { + "fieldname": "cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + "get_query": function() { + var company = frappe.query_report.get_filter_value('company'); + return { + "doctype": "Cost Center", + "filters": { + "company": company, } } - }, - { - "fieldname": "project", - "label": __("Project"), - "fieldtype": "Link", - "options": "Project" - }, - { - "fieldname": "finance_book", - "label": __("Finance Book"), - "fieldtype": "Link", - "options": "Finance Book", - }, - { - "fieldname": "presentation_currency", - "label": __("Currency"), - "fieldtype": "Select", - "options": erpnext.get_presentation_currency_list() - }, - { - "fieldname": "with_period_closing_entry", - "label": __("Period Closing Entry"), - "fieldtype": "Check", - "default": 1 - }, - { - "fieldname": "show_zero_values", - "label": __("Show zero values"), - "fieldtype": "Check" - }, - { - "fieldname": "show_unclosed_fy_pl_balances", - "label": __("Show unclosed fiscal year's P&L balances"), - "fieldtype": "Check" - }, - { - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 - }, - { - "fieldname": "show_net_values", - "label": __("Show net values in opening and closing columns"), - "fieldtype": "Check", - "default": 1 } - ], - "formatter": erpnext.financial_statements.formatter, - "tree": true, - "name_field": "account", - "parent_field": "parent_account", - "initial_depth": 3 - } + }, + { + "fieldname": "project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" + }, + { + "fieldname": "finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + }, + { + "fieldname": "presentation_currency", + "label": __("Currency"), + "fieldtype": "Select", + "options": erpnext.get_presentation_currency_list() + }, + { + "fieldname": "with_period_closing_entry", + "label": __("Period Closing Entry"), + "fieldtype": "Check", + "default": 1 + }, + { + "fieldname": "show_zero_values", + "label": __("Show zero values"), + "fieldtype": "Check" + }, + { + "fieldname": "show_unclosed_fy_pl_balances", + "label": __("Show unclosed fiscal year's P&L balances"), + "fieldtype": "Check" + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check", + "default": 1 + }, + { + "fieldname": "show_net_values", + "label": __("Show net values in opening and closing columns"), + "fieldtype": "Check", + "default": 1 + } + ], + "formatter": erpnext.financial_statements.formatter, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 +} - erpnext.utils.add_dimensions('Trial Balance', 6); -}); +erpnext.utils.add_dimensions('Trial Balance', 6); diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 0e1b23b0ea..dee9a06f05 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -30,5 +30,6 @@ import "./utils/landed_taxes_and_charges_common.js"; import "./utils/sales_common.js"; import "./controllers/buying.js"; import "./utils/demo.js"; +import "./financial_statements.js"; // import { sum } from 'frappe/public/utils/util.js' From d3c6000904387f64605fdb316412f1ee1948bd86 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 8 Oct 2023 13:16:28 +0530 Subject: [PATCH 095/280] fix: exception on exporting errored rows --- .../doctype/bank_statement_import/bank_statement_import.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 04af32346b..a70af7a90e 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -352,10 +352,11 @@ frappe.ui.form.on("Bank Statement Import", { export_errored_rows(frm) { open_url_post( - "/api/method/frappe.core.doctype.data_import.data_import.download_errored_template", + "/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template", { data_import_name: frm.doc.name, - } + }, + true ); }, From 2c899dd13aaee1c6e63f9e2512e0e75aa737fc5c Mon Sep 17 00:00:00 2001 From: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:21:25 +0530 Subject: [PATCH 096/280] fix: customer group and territory not mandatory (#37050) * fix: customer group and territory not mandatory * fix: supplier group not required * test: added test case for customer and supplier without group * test: Customer without customer group and territory * chore: remove unwanted changes * test: Supplier without supplier group * test: code cleanup --------- Co-authored-by: Deepesh Garg --- .../test_opening_invoice_creation_tool.py | 1 + .../purchase_invoice/test_purchase_invoice.py | 26 +++++++++++++++++++ .../sales_invoice/test_sales_invoice.py | 19 ++++++++++++++ erpnext/buying/doctype/supplier/supplier.json | 5 ++-- .../buying/doctype/supplier/test_supplier.py | 7 +++-- .../selling/doctype/customer/customer.json | 4 +-- erpnext/selling/doctype/customer/customer.py | 14 +--------- 7 files changed, 55 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 1e22c64c8f..02c2c6704a 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -218,6 +218,7 @@ def make_customer(customer=None): "territory": "All Territories", } ) + if not frappe.db.exists("Customer", customer_name): customer.insert(ignore_permissions=True) return customer.name diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0aaea060b5..aa3d1b3c15 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1916,6 +1916,32 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.load_from_db() self.assertFalse(pi.repost_required) + @change_settings("Buying Settings", {"supplier_group": None}) + def test_purchase_invoice_without_supplier_group(self): + # Create a Supplier + test_supplier_name = "_Test Supplier Without Supplier Group" + if not frappe.db.exists("Supplier", test_supplier_name): + supplier = frappe.get_doc( + { + "doctype": "Supplier", + "supplier_name": test_supplier_name, + } + ).insert(ignore_permissions=True) + + self.assertEqual(supplier.supplier_group, None) + + po = create_purchase_order( + supplier=test_supplier_name, + rate=3000, + item="_Test Non Stock Item", + posting_date="2021-09-15", + ) + + pi = make_purchase_invoice(supplier=test_supplier_name) + + self.assertEqual(po.docstatus, 1) + self.assertEqual(pi.docstatus, 1) + def set_advance_flag(company, flag, default_account): frappe.db.set_value( diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 84b0149942..8aa1f4c103 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -26,6 +26,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched from erpnext.controllers.accounts_controller import update_invoice_status from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency +from erpnext.selling.doctype.customer.test_customer import get_customer_dict from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt @@ -3400,6 +3401,24 @@ class TestSalesInvoice(unittest.TestCase): set_advance_flag(company="_Test Company", flag=0, default_account="") + @change_settings("Selling Settings", {"customer_group": None, "territory": None}) + def test_sales_invoice_without_customer_group_and_territory(self): + # create a customer + if not frappe.db.exists("Customer", "_Test Simple Customer"): + customer_dict = get_customer_dict("_Test Simple Customer") + customer_dict.pop("customer_group") + customer_dict.pop("territory") + customer = frappe.get_doc(customer_dict).insert(ignore_permissions=True) + + self.assertEqual(customer.customer_group, None) + self.assertEqual(customer.territory, None) + + # create a sales invoice + si = create_sales_invoice(customer="_Test Simple Customer") + self.assertEqual(si.docstatus, 1) + self.assertEqual(si.customer_group, None) + self.assertEqual(si.territory, None) + @change_settings("Selling Settings", {"allow_negative_rates_for_items": 0}) def test_sales_return_negative_rate(self): si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 19972ca1fa..f37db5f115 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -167,8 +167,7 @@ "label": "Supplier Group", "oldfieldname": "supplier_type", "oldfieldtype": "Link", - "options": "Supplier Group", - "reqd": 1 + "options": "Supplier Group" }, { "default": "Company", @@ -486,7 +485,7 @@ "link_fieldname": "party" } ], - "modified": "2023-09-21 12:24:20.398889", + "modified": "2023-09-25 12:48:21.869563", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index ee2ada3b65..350a25f26e 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -207,11 +207,14 @@ def create_supplier(**args): "doctype": "Supplier", "supplier_name": args.supplier_name, "default_currency": args.default_currency, - "supplier_group": args.supplier_group or "Services", "supplier_type": args.supplier_type or "Company", "tax_withholding_category": args.tax_withholding_category, } - ).insert() + ) + if not args.without_supplier_group: + doc.supplier_group = args.supplier_group or "Services" + + doc.insert() return doc diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 0f42def0bd..40cab9f330 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -181,7 +181,6 @@ "oldfieldname": "customer_group", "oldfieldtype": "Link", "options": "Customer Group", - "reqd": 1, "search_index": 1 }, { @@ -193,8 +192,7 @@ "oldfieldname": "territory", "oldfieldtype": "Link", "options": "Territory", - "print_hide": 1, - "reqd": 1 + "print_hide": 1 }, { "fieldname": "tax_id", diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d351c3cc5b..a7a1aa2659 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -15,14 +15,9 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from frappe.model.utils.rename_doc import update_linked_doctypes from frappe.utils import cint, cstr, flt, get_formatted_email, today -from frappe.utils.nestedset import get_root_of from frappe.utils.user import get_users_with_role -from erpnext.accounts.party import ( # noqa - get_dashboard_info, - get_timeline_data, - validate_party_accounts, -) +from erpnext.accounts.party import get_dashboard_info, validate_party_accounts # noqa from erpnext.controllers.website_list_for_contact import add_role_for_portal_user from erpnext.utilities.transaction_base import TransactionBase @@ -81,7 +76,6 @@ class Customer(TransactionBase): validate_party_accounts(self) self.validate_credit_limit_on_change() self.set_loyalty_program() - self.set_territory_and_group() self.check_customer_group_change() self.validate_default_bank_account() self.validate_internal_customer() @@ -140,12 +134,6 @@ class Customer(TransactionBase): _("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)) ) - def set_territory_and_group(self): - if not self.territory: - self.territory = get_root_of("Territory") - if not self.customer_group: - self.customer_group = get_root_of("Customer Group") - def validate_internal_customer(self): if not self.is_internal_customer: self.represents_company = "" From 6959c928c6d3ef3bbde70baf94dfe12aaad08de9 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 9 Oct 2023 20:58:56 +0530 Subject: [PATCH 097/280] test: add product_code in create_loan_product (#37416) --- erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py | 1 + .../accounts/doctype/bank_transaction/test_bank_transaction.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py index 2a18830746..ace751b4a2 100644 --- a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py @@ -42,6 +42,7 @@ class TestBankClearance(unittest.TestCase): def create_loan_masters(): create_loan_product( + "Clearance Loan", "Clearance Loan", 2000000, 13.5, diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index 2a504f6caf..4a6491d086 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -421,6 +421,7 @@ def create_loan_and_repayment(): from erpnext.setup.doctype.employee.test_employee import make_employee create_loan_product( + "Personal Loan", "Personal Loan", 500000, 8.4, From 38ca164662532a97469db4b2d0c1519a570120eb Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 9 Oct 2023 23:45:58 +0200 Subject: [PATCH 098/280] fix: german tranlations of "Is Return" --- erpnext/translations/de.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 79777f2338..79b9574239 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -4586,7 +4586,7 @@ ACC-PINV-.YYYY.-,ACC-PINV-.JJJJ.-, Tax Withholding Category,Steuereinbehalt Kategorie, Edit Posting Date and Time,Buchungsdatum und -uhrzeit bearbeiten, Is Paid,Ist bezahlt, -Is Return (Debit Note),ist Rücklieferung (Lastschrift), +Is Return (Debit Note),Ist Rechnungskorrektur (Retoure), Apply Tax Withholding Amount,Steuereinbehaltungsbetrag anwenden, Accounting Dimensions ,Buchhaltung Dimensionen, Supplier Invoice Details,Lieferant Rechnungsdetails, @@ -4710,7 +4710,7 @@ Item Wise Tax Detail ,Item Wise Tax Detail, ACC-SINV-.YYYY.-,ACC-SINV-.JJJJ.-, Include Payment (POS),(POS) Zahlung einschließen, Offline POS Name,Offline-Verkaufsstellen-Name, -Is Return (Credit Note),ist Rücklieferung (Gutschrift), +Is Return (Credit Note),Ist Rechnungskorrektur (Retoure), Update Billed Amount in Sales Order,Aktualisierung des Rechnungsbetrags im Auftrag, Customer PO Details,Auftragsdetails, Customer's Purchase Order,Bestellung des Kunden, @@ -6998,7 +6998,7 @@ Customs Tariff Number,Zolltarifnummer, Tariff Number,Tarifnummer, Delivery To,Lieferung an, MAT-DN-.YYYY.-,MAT-DN-.YYYY.-, -Is Return,Ist Rückgabe, +Is Return,Ist Retoure, Issue Credit Note,Gutschrift ausgeben, Return Against Delivery Note,Zurück zum Lieferschein, Customer's Purchase Order No,Bestellnummer des Kunden, From 2d6f1f8598b10e5e7414a0e4f4abb37879888502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Tue, 10 Oct 2023 03:56:37 +0200 Subject: [PATCH 099/280] fix(UX): Un-require description fields on items (#37354) fix(UX): Un-require description fields on items. --- .../doctype/sales_invoice_item/sales_invoice_item.json | 3 +-- .../doctype/purchase_order_item/purchase_order_item.json | 3 +-- .../request_for_quotation_item/request_for_quotation_item.json | 3 +-- .../supplier_quotation_item/supplier_quotation_item.json | 3 +-- erpnext/selling/doctype/quotation_item/quotation_item.json | 3 +-- erpnext/selling/doctype/sales_order_item/sales_order_item.json | 3 +-- .../stock/doctype/delivery_note_item/delivery_note_item.json | 3 +-- .../doctype/material_request_item/material_request_item.json | 3 +-- .../doctype/purchase_receipt_item/purchase_receipt_item.json | 3 +-- 9 files changed, 9 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index abeaab1d25..5d2764b669 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -157,7 +157,6 @@ "oldfieldname": "description", "oldfieldtype": "Text", "print_width": "200px", - "reqd": 1, "width": "200px" }, { @@ -912,4 +911,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index f79b6223bf..6b29984491 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -180,7 +180,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -916,4 +915,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json index e07f4626b8..82fcfa2713 100644 --- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json +++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json @@ -84,7 +84,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -270,4 +269,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index 638cde01be..8d491fbc84 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -126,7 +126,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -569,4 +568,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index dde2f9b76b..5016f1f1fd 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -132,7 +132,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -677,4 +676,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 07565c3928..e6f7456620 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -162,7 +162,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -906,4 +905,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index d3236ba0ba..612d674e01 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -165,7 +165,6 @@ "oldfieldname": "description", "oldfieldtype": "Small Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -903,4 +902,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 770dacdd19..c585d6c490 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -104,7 +104,6 @@ "oldfieldname": "description", "oldfieldtype": "Text", "print_width": "250px", - "reqd": 1, "width": "250px" }, { @@ -472,4 +471,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index d7419dcc37..d93d21c1f2 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -187,7 +187,6 @@ "oldfieldname": "description", "oldfieldtype": "Text", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -1079,4 +1078,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From fb7c5f6d91b59d1645ca4fc2401114ba8fad0b85 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 10 Oct 2023 04:26:54 +0200 Subject: [PATCH 100/280] fix(sales): add sales user to read stock settings for js refresh code path (#37317) --- erpnext/stock/doctype/stock_settings/stock_settings.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 4fbc0eb43a..2052daafed 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -428,7 +428,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-09-01 16:16:34.018947", + "modified": "2023-10-01 14:22:36.136111", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -442,6 +442,10 @@ "role": "Stock Manager", "share": 1, "write": 1 + }, + { + "read": 1, + "role": "Sales User" } ], "quick_entry": 1, @@ -449,4 +453,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 78eaf5d0356e00f4a07b84a3dc5ee7916871efaf Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 10 Oct 2023 11:05:24 +0530 Subject: [PATCH 101/280] fix: fetch dependent task subject and project (#37401) --- .../task_depends_on/task_depends_on.json | 192 ++++-------------- 1 file changed, 44 insertions(+), 148 deletions(-) diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.json b/erpnext/projects/doctype/task_depends_on/task_depends_on.json index dbbe9d3c7b..5102986f00 100644 --- a/erpnext/projects/doctype/task_depends_on/task_depends_on.json +++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.json @@ -1,156 +1,52 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2015-04-29 04:52:48.868079", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2015-04-29 04:52:48.868079", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "task", + "column_break_2", + "subject", + "project" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "task", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Task", - "length": 0, - "no_copy": 0, - "options": "Task", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "task", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Task", + "options": "Task" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subject", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Subject", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fetch_from": "task.subject", + "fieldname": "subject", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Subject", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fetch_from": "task.project", + "fieldname": "project", + "fieldtype": "Text", + "label": "Project", + "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-02-24 04:56:04.862502", - "modified_by": "Administrator", - "module": "Projects", - "name": "Task Depends On", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2023-10-09 11:34:14.335853", + "modified_by": "Administrator", + "module": "Projects", + "name": "Task Depends On", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] } \ No newline at end of file From bda82bf1e9622dda9f7fa42b27b31b3879de342b Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 10 Oct 2023 10:30:09 +0000 Subject: [PATCH 102/280] fix(gp): wrong `allocated_amount` on multi sales person invoice --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 3324a73e25..38060bb5b2 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -544,6 +544,8 @@ class GrossProfitGenerator(object): new_row.qty += flt(row.qty) new_row.buying_amount += flt(row.buying_amount, self.currency_precision) new_row.base_amount += flt(row.base_amount, self.currency_precision) + if self.filters.get("group_by") == "Sales Person": + new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision) new_row = self.set_average_rate(new_row) self.grouped_data.append(new_row) From 0d7a0f393deed1f3bec5d5925bd5f9bb4eab99c5 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 11 Oct 2023 10:51:40 +0530 Subject: [PATCH 103/280] fix(ux): allow MR to Stop until fully received --- .../doctype/material_request/material_request.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index bf3301f6d8..9673a70501 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -102,6 +102,12 @@ frappe.ui.form.on('Material Request', { if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') { let precision = frappe.defaults.get_default("float_precision"); + + if (flt(frm.doc.per_received, precision) < 100) { + frm.add_custom_button(__('Stop'), + () => frm.events.update_status(frm, 'Stopped')); + } + if (flt(frm.doc.per_ordered, precision) < 100) { let add_create_pick_list_button = () => { frm.add_custom_button(__('Pick List'), @@ -148,11 +154,6 @@ frappe.ui.form.on('Material Request', { } frm.page.set_inner_btn_group_as_primary(__('Create')); - - // stop - frm.add_custom_button(__('Stop'), - () => frm.events.update_status(frm, 'Stopped')); - } } From 4a6108e91251e4d284024b42ec48030493f87288 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 11 Oct 2023 11:17:47 +0530 Subject: [PATCH 104/280] fix: use mariadb instead of mysql Drop mysql-client in favour of mariadb-client Signed-off-by: Akhil Narang --- .github/helper/install.sh | 16 +++++++++------- .github/workflows/patch.yml | 2 +- .github/workflows/server-tests-mariadb.yml | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index d1a97f87ff..915a463799 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -4,7 +4,9 @@ set -e cd ~ || exit -sudo apt update && sudo apt install redis-server libcups2-dev +sudo apt update +sudo apt remove mysql-server mysql-client +sudo apt install libcups2-dev redis-server mariadb-client-10.6 pip install frappe-bench @@ -25,14 +27,14 @@ fi if [ "$DB" == "mariadb" ];then - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe" - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES" fi if [ "$DB" == "postgres" ];then diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 07b8de7a90..21dd3d4879 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -28,7 +28,7 @@ jobs: MARIADB_ROOT_PASSWORD: 'root' ports: - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: - name: Clone diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index 559be06993..ccdfc8c109 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -47,7 +47,7 @@ jobs: MARIADB_ROOT_PASSWORD: 'root' ports: - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: - name: Clone From cea0d65fbdc2b38689521cdb3aeadf8ce88d50a4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 11 Oct 2023 12:22:06 +0530 Subject: [PATCH 105/280] chore: disable beta release --- .github/workflows/initiate_release.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml index ee60bad104..70347738f2 100644 --- a/.github/workflows/initiate_release.yml +++ b/.github/workflows/initiate_release.yml @@ -30,23 +30,3 @@ jobs: head: version-${{ matrix.version }}-hotfix env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - - beta-release: - name: Release - runs-on: ubuntu-latest - strategy: - fail-fast: false - - steps: - - uses: octokit/request-action@v2.x - with: - route: POST /repos/{owner}/{repo}/pulls - owner: frappe - repo: erpnext - title: |- - "chore: release v15 beta" - body: "Automated beta release." - base: version-15-beta - head: develop - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} From c1782c50158e50e1e57e7eeda276ffd46203f86c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 10 Oct 2023 17:12:47 +0530 Subject: [PATCH 106/280] refactor: for non-repost fields, don't validate --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 5 +++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 85ed1260d3..2433268627 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -539,8 +539,9 @@ class PurchaseInvoice(BuyingController): ] child_tables = {"items": ("expense_account",), "taxes": ("account_head",)} self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_for_repost() - 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 make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f380825db7..f6d9c93261 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -536,8 +536,9 @@ class SalesInvoice(SellingController): "taxes": ("account_head",), } self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables) - self.validate_for_repost() - 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 set_paid_amount(self): paid_amount = 0.0 From f3238f910509813a24fbc7ebfb726f42f6addd6f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 11 Oct 2023 14:08:11 +0530 Subject: [PATCH 107/280] fix: production plan reserved qty incorrect calculation (#37400) --- .../production_plan/production_plan.py | 23 ++++++------------- .../production_plan/test_production_plan.py | 6 ++--- .../doctype/work_order/work_order.py | 19 ++++++++------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index deef020220..ddd9375211 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -8,7 +8,6 @@ import json import frappe from frappe import _, msgprint from frappe.model.document import Document -from frappe.query_builder import Case from frappe.query_builder.functions import IfNull, Sum from frappe.utils import ( add_days, @@ -1618,21 +1617,13 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): table = frappe.qb.DocType("Production Plan") child = frappe.qb.DocType("Material Request Plan Item") - completed_production_plans = get_completed_production_plans() + non_completed_production_plans = get_non_completed_production_plans() - case = Case() query = ( frappe.qb.from_(table) .inner_join(child) .on(table.name == child.parent) - .select( - Sum( - child.quantity - * IfNull( - case.when(child.material_request_type == "Purchase", child.conversion_factor).else_(1.0), 1.0 - ) - ) - ) + .select(Sum(child.required_bom_qty)) .where( (table.docstatus == 1) & (child.item_code == item_code) @@ -1641,8 +1632,8 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): ) ) - if completed_production_plans: - query = query.where(table.name.notin(completed_production_plans)) + if non_completed_production_plans: + query = query.where(table.name.isin(non_completed_production_plans)) query = query.run() @@ -1653,7 +1644,7 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): reserved_qty_for_production = flt( get_reserved_qty_for_production( - item_code, warehouse, completed_production_plans, check_production_plan=True + item_code, warehouse, non_completed_production_plans, check_production_plan=True ) ) @@ -1663,7 +1654,7 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): return reserved_qty_for_production_plan - reserved_qty_for_production -def get_completed_production_plans(): +def get_non_completed_production_plans(): table = frappe.qb.DocType("Production Plan") child = frappe.qb.DocType("Production Plan Item") @@ -1675,7 +1666,7 @@ def get_completed_production_plans(): .where( (table.docstatus == 1) & (table.status.notin(["Completed", "Closed"])) - & (child.ordered_qty >= child.planned_qty) + & (child.planned_qty > child.ordered_qty) ) ).run(as_dict=True) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 4ff9d29e0b..6ab9232788 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -6,8 +6,8 @@ from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( - get_completed_production_plans, get_items_for_material_requests, + get_non_completed_production_plans, get_sales_orders, get_warehouse_list, ) @@ -1143,9 +1143,9 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) - completed_plans = get_completed_production_plans() + completed_plans = get_non_completed_production_plans() for plan in plans: - self.assertTrue(plan in completed_plans) + self.assertFalse(plan in completed_plans) def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self): from erpnext.stock.utils import get_or_make_bin diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 3dc33ac578..f9fddcbb5e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1515,7 +1515,7 @@ def create_pick_list(source_name, target_doc=None, for_qty=None): def get_reserved_qty_for_production( item_code: str, warehouse: str, - completed_production_plans: list = None, + non_completed_production_plans: list = None, check_production_plan: bool = False, ) -> float: """Get total reserved quantity for any item in specified warehouse""" @@ -1538,19 +1538,22 @@ def get_reserved_qty_for_production( & (wo_item.parent == wo.name) & (wo.docstatus == 1) & (wo_item.source_warehouse == warehouse) - & (wo.status.notin(["Stopped", "Completed", "Closed"])) - & ( - (wo_item.required_qty > wo_item.transferred_qty) - | (wo_item.required_qty > wo_item.consumed_qty) - ) ) ) if check_production_plan: query = query.where(wo.production_plan.isnotnull()) + else: + query = query.where( + (wo.status.notin(["Stopped", "Completed", "Closed"])) + & ( + (wo_item.required_qty > wo_item.transferred_qty) + | (wo_item.required_qty > wo_item.consumed_qty) + ) + ) - if completed_production_plans: - query = query.where(wo.production_plan.notin(completed_production_plans)) + if non_completed_production_plans: + query = query.where(wo.production_plan.isin(non_completed_production_plans)) return query.run()[0][0] or 0.0 From 0cdd6435a556309d62240fc669ce431efee040c3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 11 Oct 2023 14:42:23 +0530 Subject: [PATCH 108/280] refactor: add validation for Advances in SI/PI --- erpnext/controllers/accounts_controller.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6812940ee2..e170044f8e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -13,6 +13,7 @@ from frappe.utils import ( add_days, add_months, cint, + comma_and, flt, fmt_money, formatdate, @@ -181,6 +182,17 @@ class AccountsController(TransactionBase): self.validate_party_account_currency() if self.doctype in ["Purchase Invoice", "Sales Invoice"]: + if invalid_advances := [ + x for x in self.advances if not x.reference_type or not x.reference_name + ]: + frappe.throw( + _( + "Rows: {0} in {1} section are Invalid. Reference Name should point to a valid Payment Entry or Journal Entry." + ).format( + frappe.bold(comma_and([x.idx for x in invalid_advances])), frappe.bold(_("Advance Payments")) + ) + ) + pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() From 5ebf7c8c29eb9c318529cd55943b76b2467f135c Mon Sep 17 00:00:00 2001 From: Rishik Sahu Date: Thu, 12 Oct 2023 14:03:49 +0530 Subject: [PATCH 109/280] fixed-#37231-changed-doc-to-d/closes-the-isse --- erpnext/public/js/controllers/accounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index a2e4bdacac..354552137b 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -116,7 +116,7 @@ erpnext.accounts.taxes = { account_head: function(frm, cdt, cdn) { let d = locals[cdt][cdn]; - if (doc.docstatus == 1) { + if (d.docstatus == 1) { // Should not trigger any changes on change post submit return; } From 2c56ee97c7c14b5250b0fae82c73476baa50a822 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 12 Oct 2023 15:57:50 +0530 Subject: [PATCH 110/280] refactor: back calculate total amt for TDS --- .../tax_withholding_details/tax_withholding_details.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 91ad3d6873..f2ec31c70e 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -68,7 +68,11 @@ def get_result( tax_amount += entry.credit - entry.debit if net_total_map.get(name): - total_amount, grand_total, base_total = net_total_map.get(name) + if voucher_type == "Journal Entry": + # back calcalute total amount from rate and tax_amount + total_amount = grand_total = base_total = tax_amount / (rate / 100) + else: + total_amount, grand_total, base_total = net_total_map.get(name) else: total_amount += entry.credit From 18e3a8907a78c323d1aacce6d46732f97ee8fdd2 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 12 Oct 2023 19:41:11 +0530 Subject: [PATCH 111/280] fix: don't set finance books if gross_purchase_amount is not set (#37480) --- erpnext/assets/doctype/asset/asset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 5395f15e7a..f0e4c82048 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -337,7 +337,7 @@ frappe.ui.form.on('Asset', { item_code: function(frm) { - if(frm.doc.item_code && frm.doc.calculate_depreciation) { + if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) { frm.trigger('set_finance_book'); } else { frm.set_value('finance_books', []); @@ -490,7 +490,7 @@ frappe.ui.form.on('Asset', { calculate_depreciation: function(frm) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); - if (frm.doc.item_code && frm.doc.calculate_depreciation ) { + if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) { frm.trigger("set_finance_book"); } else { frm.set_value("finance_books", []); From 17ca8756a72765e10e17d2a2b81f29129263ab26 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 12 Oct 2023 20:43:15 +0530 Subject: [PATCH 112/280] refactor(patch): ignore links on closing balance patch --- .../doctype/account_closing_balance/account_closing_balance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py index e75af7047f..d06bd833c8 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py @@ -37,6 +37,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date): } ) cle.flags.ignore_permissions = True + cle.flags.ignore_links = True cle.submit() From ad00df0af6556a806e3b9b40ecaac719f0ce20a4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 13 Oct 2023 15:39:54 +0530 Subject: [PATCH 113/280] fix: keyerror on gl and pl comparision report --- .../general_and_payment_ledger_comparison.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py index 553c137f02..099884a48e 100644 --- a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py +++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py @@ -133,15 +133,17 @@ class General_Payment_Ledger_Comparison(object): self.gle_balances = set(val.gle) | self.gle_balances self.ple_balances = set(val.ple) | self.ple_balances - self.diff1 = self.gle_balances.difference(self.ple_balances) - self.diff2 = self.ple_balances.difference(self.gle_balances) + self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances) + self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances) self.diff = frappe._dict({}) - for x in self.diff1: + for x in self.variation_in_payment_ledger: self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]}) - for x in self.diff2: - self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]})) + for x in self.variation_in_general_ledger: + self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update( + frappe._dict({"pl_balance": x[4]}) + ) def generate_data(self): self.data = [] From b2cee396ac9edea5ba920382bfc27f3736600775 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 14 Oct 2023 20:42:26 +0530 Subject: [PATCH 114/280] fix: consider received qty while creating SO -> MR --- .../doctype/sales_order/sales_order.py | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index aae0fee467..b91002eb86 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -606,29 +606,37 @@ def close_or_unclose_sales_orders(names, status): def get_requested_item_qty(sales_order): - return frappe._dict( - frappe.db.sql( - """ - select sales_order_item, sum(qty) - from `tabMaterial Request Item` - where docstatus = 1 - and sales_order = %s - group by sales_order_item - """, - sales_order, - ) - ) + result = {} + for d in frappe.db.get_all( + "Material Request Item", + filters={"docstatus": 1, "sales_order": sales_order}, + fields=["sales_order_item", "sum(qty) as qty", "sum(received_qty) as received_qty"], + group_by="sales_order_item", + ): + result[d.sales_order_item] = frappe._dict({"qty": d.qty, "received_qty": d.received_qty}) + + return result @frappe.whitelist() def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) + def get_remaining_qty(so_item): + return flt( + flt(so_item.qty) + - flt(requested_item_qty.get(so_item.name, {}).get("qty")) + - max( + flt(so_item.get("delivered_qty")) + - flt(requested_item_qty.get(so_item.name, {}).get("received_qty")), + 0, + ) + ) + def update_item(source, target, source_parent): # qty is for packed items, because packed items don't have stock_qty field - qty = source.get("qty") target.project = source_parent.project - target.qty = qty - requested_item_qty.get(source.name, 0) - flt(source.get("delivered_qty")) + target.qty = get_remaining_qty(source) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) args = target.as_dict().copy() @@ -661,8 +669,8 @@ def make_material_request(source_name, target_doc=None): "Sales Order Item": { "doctype": "Material Request Item", "field_map": {"name": "sales_order_item", "parent": "sales_order"}, - "condition": lambda doc: not frappe.db.exists("Product Bundle", doc.item_code) - and (doc.stock_qty - flt(doc.get("delivered_qty"))) > requested_item_qty.get(doc.name, 0), + "condition": lambda item: not frappe.db.exists("Product Bundle", item.item_code) + and get_remaining_qty(item) > 0, "postprocess": update_item, }, }, From c322e5f38140b1fab8f940db542e25c2b122ab54 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Oct 2023 09:28:42 +0530 Subject: [PATCH 115/280] test: use fixtures for sales and purchase invoice --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 8aa1f4c103..442dc99ef4 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -6,7 +6,7 @@ import unittest import frappe from frappe.model.dynamic_links import get_dynamic_link_map -from frappe.tests.utils import change_settings +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today import erpnext @@ -45,7 +45,7 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import from erpnext.stock.utils import get_incoming_rate, get_stock_balance -class TestSalesInvoice(unittest.TestCase): +class TestSalesInvoice(FrappeTestCase): def setUp(self): from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items @@ -53,6 +53,9 @@ class TestSalesInvoice(unittest.TestCase): create_internal_parties() setup_accounts() + def tearDown(self): + frappe.db.rollback() + def make(self): w = frappe.copy_doc(test_records[0]) w.is_pos = 0 From fc50b174eb5f37a40b522f7e073d11260e0c12c8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Oct 2023 10:56:39 +0530 Subject: [PATCH 116/280] refactor(test): unset accounts frozen date --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 442dc99ef4..bc44ef2637 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -52,10 +52,14 @@ class TestSalesInvoice(FrappeTestCase): create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) create_internal_parties() setup_accounts() + self.remove_accounts_frozen_date() def tearDown(self): frappe.db.rollback() + def remove_accounts_frozen_date(self): + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) + def make(self): w = frappe.copy_doc(test_records[0]) w.is_pos = 0 From 58065f31b1e2e550661a47b4442f6861406ebec5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Oct 2023 13:12:21 +0530 Subject: [PATCH 117/280] refactor(test): use @change_settings in sales invoice --- .../sales_invoice/test_sales_invoice.py | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index bc44ef2637..ef31eaa97c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -52,14 +52,10 @@ class TestSalesInvoice(FrappeTestCase): create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) create_internal_parties() setup_accounts() - self.remove_accounts_frozen_date() def tearDown(self): frappe.db.rollback() - def remove_accounts_frozen_date(self): - frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) - def make(self): w = frappe.copy_doc(test_records[0]) w.is_pos = 0 @@ -3080,8 +3076,8 @@ class TestSalesInvoice(FrappeTestCase): si.commission_rate = commission_rate self.assertRaises(frappe.ValidationError, si.save) + @change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)}) def test_sales_invoice_submission_post_account_freezing_date(self): - frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", add_days(getdate(), 1)) si = create_sales_invoice(do_not_save=True) si.posting_date = add_days(getdate(), 1) si.save() @@ -3090,8 +3086,6 @@ class TestSalesInvoice(FrappeTestCase): si.posting_date = getdate() si.submit() - frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) - def test_over_billing_case_against_delivery_note(self): """ Test a case where duplicating the item with qty = 1 in the invoice @@ -3120,6 +3114,13 @@ class TestSalesInvoice(FrappeTestCase): frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", over_billing_allowance) + @change_settings( + "Accounts Settings", + { + "book_deferred_entries_via_journal_entry": 1, + "submit_journal_entries": 1, + }, + ) def test_multi_currency_deferred_revenue_via_journal_entry(self): deferred_account = create_account( account_name="Deferred Revenue", @@ -3127,11 +3128,6 @@ class TestSalesInvoice(FrappeTestCase): company="_Test Company", ) - acc_settings = frappe.get_single("Accounts Settings") - acc_settings.book_deferred_entries_via_journal_entry = 1 - acc_settings.submit_journal_entries = 1 - acc_settings.save() - item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_expense = 1 item.item_defaults[0].deferred_revenue_account = deferred_account @@ -3197,11 +3193,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(expected_gle[i][2], gle.debit) self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) - acc_settings = frappe.get_single("Accounts Settings") - acc_settings.book_deferred_entries_via_journal_entry = 0 - acc_settings.submit_journal_entries = 0 - acc_settings.save() - frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) def test_standalone_serial_no_return(self): From 8ebe5733ac61b6291b22901dbbf070093200706f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 2 Oct 2023 14:57:31 +0530 Subject: [PATCH 118/280] refactor(test): fix broken test cases in Sales Invoice --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ef31eaa97c..842d77a29a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -52,6 +52,7 @@ class TestSalesInvoice(FrappeTestCase): create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) create_internal_parties() setup_accounts() + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) def tearDown(self): frappe.db.rollback() @@ -3193,8 +3194,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(expected_gle[i][2], gle.debit) self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) - frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) - def test_standalone_serial_no_return(self): si = create_sales_invoice( item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1 From de9baef84a77f5bb2aa94f200147d0689462b9c3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 11 Oct 2023 20:18:59 +0530 Subject: [PATCH 119/280] refactor(test): use @change_settings to fix failing test cases --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 842d77a29a..1f54736049 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -183,6 +183,7 @@ class TestSalesInvoice(FrappeTestCase): self.assertRaises(frappe.LinkExistsError, si.cancel) unlink_payment_on_cancel_of_invoice() + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_payment_entry_unlink_against_standalone_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry @@ -1304,6 +1305,7 @@ class TestSalesInvoice(FrappeTestCase): dn.submit() return dn + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_sales_invoice_with_advance(self): from erpnext.accounts.doctype.journal_entry.test_journal_entry import ( test_records as jv_test_records, From 3bdf4f628c40d4e8ac19a41c738a8ba382d90d99 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 15 Oct 2023 05:56:52 +0530 Subject: [PATCH 120/280] refactor(test): use test fixture in subscription --- erpnext/accounts/doctype/subscription/test_subscription.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 803e87900d..785fd04b82 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -4,6 +4,7 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils.data import ( add_days, add_months, @@ -21,11 +22,15 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto test_dependencies = ("UOM", "Item Group", "Item") -class TestSubscription(unittest.TestCase): +class TestSubscription(FrappeTestCase): def setUp(self): make_plans() create_parties() reset_settings() + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) + + def tearDown(self): + frappe.db.rollback() def test_create_subscription_with_trial_with_correct_period(self): subscription = create_subscription( From a2e064d214ffb9f012f2144bee14cd467e935241 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 15 Oct 2023 06:35:22 +0530 Subject: [PATCH 121/280] refactor(test): use test fixture in purchase invoice --- .../doctype/purchase_invoice/test_purchase_invoice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index aa3d1b3c15..cd055e3013 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -5,7 +5,7 @@ import unittest import frappe -from frappe.tests.utils import change_settings +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, flt, getdate, nowdate, today import erpnext @@ -38,7 +38,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ test_ignore = ["Serial No"] -class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): +class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): @classmethod def setUpClass(self): unlink_payment_on_cancel_of_invoice() @@ -48,6 +48,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): def tearDownClass(self): unlink_payment_on_cancel_of_invoice(0) + def tearDown(self): + frappe.db.rollback() + def test_purchase_invoice_received_qty(self): """ 1. Test if received qty is validated against accepted + rejected From 0207d6e7c996cd6c1b04f2ba171fdf3d6ccfa130 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 15 Oct 2023 07:11:11 +0530 Subject: [PATCH 122/280] refactor(test): make use of @change_settings in PI test cases --- .../doctype/purchase_invoice/test_purchase_invoice.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index cd055e3013..e365d60f20 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -425,6 +425,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertEqual(tax.tax_amount, expected_values[i][1]) self.assertEqual(tax.total, expected_values[i][2]) + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_purchase_invoice_with_advance(self): from erpnext.accounts.doctype.journal_entry.test_journal_entry import ( test_records as jv_test_records, @@ -479,6 +480,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): ) ) + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_invoice_with_advance_and_multi_payment_terms(self): from erpnext.accounts.doctype.journal_entry.test_journal_entry import ( test_records as jv_test_records, @@ -1223,6 +1225,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_gain_loss_with_advance_entry(self): unlink_enabled = frappe.db.get_value( "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice" @@ -1423,6 +1426,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): ) frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_purchase_invoice_advance_taxes(self): from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry From fbabf4ac2e96c473884c94e59b715d14dee3f960 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 15 Oct 2023 08:07:29 +0530 Subject: [PATCH 123/280] refactor(test): make sure TDS Payable is available for testing --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1f54736049..c1adffde31 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2781,6 +2781,13 @@ class TestSalesInvoice(FrappeTestCase): company="_Test Company", ) + tds_payable_account = create_account( + account_name="TDS Payable", + account_type="Tax", + parent_account="Duties and Taxes - _TC", + company="_Test Company", + ) + si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1) si.apply_discount_on = "Grand Total" si.additional_discount_account = additional_discount_account From 46add06a29f8a0a5990dfd3aeda39f01413071bb Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 15 Oct 2023 15:46:29 +0530 Subject: [PATCH 124/280] fix: GL Entries not getting created for PR Return --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6afa86e34e..de0db1aa8f 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -339,7 +339,7 @@ class PurchaseReceipt(BuyingController): exchange_rate_map, net_rate_map = get_purchase_document_details(self) for d in self.get("items"): - if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): + if d.item_code in stock_items and flt(d.qty) and (flt(d.valuation_rate) or self.is_return): if warehouse_account.get(d.warehouse): stock_value_diff = frappe.db.get_value( "Stock Ledger Entry", From 253d4782c63963df78216ce51d9f9f9a80791531 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 15 Oct 2023 23:34:58 +0530 Subject: [PATCH 125/280] test: add test case for PR return with zero rate --- .../purchase_receipt/test_purchase_receipt.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index a8ef5e8e48..466e8e7b12 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2086,6 +2086,64 @@ class TestPurchaseReceipt(FrappeTestCase): return_pr.reload() self.assertEqual(return_pr.status, "Completed") + def test_purchase_return_with_zero_rate(self): + company = "_Test Company with perpetual inventory" + + # Step - 1: Create Item + item, warehouse = ( + make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name, + "Stores - TCP1", + ) + + # Step - 2: Create Stock Entry (Material Receipt) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + se = make_stock_entry( + purpose="Material Receipt", + item_code=item, + qty=100, + basic_rate=100, + to_warehouse=warehouse, + company=company, + ) + + # Step - 3: Create Purchase Receipt + pr = make_purchase_receipt( + item_code=item, + qty=5, + rate=0, + warehouse=warehouse, + company=company, + ) + + # Step - 4: Create Purchase Return + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + pr_return = make_return_doc("Purchase Receipt", pr.name) + pr_return.save() + pr_return.submit() + + sl_entries = get_sl_entries(pr_return.doctype, pr_return.name) + gl_entries = get_gl_entries(pr_return.doctype, pr_return.name) + + # Test - 1: SLE Stock Value Difference should be equal to Qty * Average Rate + average_rate = ( + (se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate) + ) / (se.items[0].qty + pr.items[0].qty) + expected_stock_value_difference = pr_return.items[0].qty * average_rate + self.assertEqual( + flt(sl_entries[0].stock_value_difference, 2), flt(expected_stock_value_difference, 2) + ) + + # Test - 2: GL Entries should be created for Stock Value Difference + self.assertEqual(len(gl_entries), 2) + + # Test - 3: SLE Stock Value Difference should be equal to Debit or Credit of GL Entries. + for entry in gl_entries: + self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference)) + + self.assertIsNotNone(get_gl_entries(pr_return.doctype, pr_return.name)) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 240b161e8161c77ce7e59b50b61d8e7ec9c6cdb7 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 16 Oct 2023 01:08:42 +0530 Subject: [PATCH 126/280] fix: purchase return test case --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 466e8e7b12..cdf50532fc 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2142,8 +2142,6 @@ class TestPurchaseReceipt(FrappeTestCase): for entry in gl_entries: self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference)) - self.assertIsNotNone(get_gl_entries(pr_return.doctype, pr_return.name)) - def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 2790ae07443381ec4b2b23d645bc52e98fdf9ee4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 16 Oct 2023 15:43:14 +0530 Subject: [PATCH 127/280] fix: Update frappe.link_search usage refer https://github.com/frappe/frappe/pull/22745 --- erpnext/public/js/utils/item_selector.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/utils/item_selector.js b/erpnext/public/js/utils/item_selector.js index 9fc264086a..e74d291acd 100644 --- a/erpnext/public/js/utils/item_selector.js +++ b/erpnext/public/js/utils/item_selector.js @@ -97,14 +97,14 @@ erpnext.ItemSelector = class ItemSelector { } var me = this; - frappe.link_search("Item", args, function(r) { - $.each(r.values, function(i, d) { + frappe.link_search("Item", args, function(results) { + $.each(results, function(i, d) { if(!d.image) { d.abbr = frappe.get_abbr(d.item_name); d.color = frappe.get_palette(d.item_name); } }); - me.dialog.results.html(frappe.render_template('item_selector', {'data':r.values})); + me.dialog.results.html(frappe.render_template('item_selector', {'data': results})); }); } }; From 08315522bbc198fce1168a5e8522684cad750276 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 3 Oct 2023 09:53:41 +0530 Subject: [PATCH 128/280] refactor: checkbox to toggle exchange rate inheritence in PO->PI --- .../doctype/purchase_invoice/purchase_invoice.json | 10 +++++++++- .../doctype/buying_settings/buying_settings.json | 10 +++++++++- erpnext/controllers/accounts_controller.py | 11 +++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index e4898826ec..2d1f4451b6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -36,6 +36,7 @@ "currency_and_price_list", "currency", "conversion_rate", + "use_transaction_date_exchange_rate", "column_break2", "buying_price_list", "price_list_currency", @@ -1588,13 +1589,20 @@ "label": "Repost Required", "options": "Account", "read_only": 1 + }, + { + "default": "0", + "fieldname": "use_transaction_date_exchange_rate", + "fieldtype": "Check", + "label": "Use Transaction Date Exchange Rate", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-10-01 21:01:47.282533", + "modified": "2023-10-16 16:24:51.886231", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 8c73e56a99..71cb01b188 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -24,6 +24,7 @@ "bill_for_rejected_quantity_in_purchase_invoice", "disable_last_purchase_rate", "show_pay_button", + "use_transaction_date_exchange_rate", "subcontract", "backflush_raw_materials_of_subcontract_based_on", "column_break_11", @@ -164,6 +165,13 @@ "fieldname": "over_order_allowance", "fieldtype": "Float", "label": "Over Order Allowance (%)" + }, + { + "default": "0", + "description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.", + "fieldname": "use_transaction_date_exchange_rate", + "fieldtype": "Check", + "label": "Use Transaction Date Exchange Rate" } ], "icon": "fa fa-cog", @@ -171,7 +179,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-03-02 17:02:14.404622", + "modified": "2023-10-16 16:22:03.201078", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6812940ee2..2beee283cb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -572,6 +572,17 @@ class AccountsController(TransactionBase): self.currency, self.company_currency, transaction_date, args ) + if ( + self.currency + and buying_or_selling == "Buying" + and frappe.db.get_single_value("Buying Settings", "use_transaction_date_exchange_rate") + and self.doctype == "Purchase Invoice" + ): + self.use_transaction_date_exchange_rate = True + self.conversion_rate = get_exchange_rate( + self.currency, self.company_currency, transaction_date, args + ) + def set_missing_item_details(self, for_validate=False): """set missing item values""" from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos From 5b4528e6146aaeb8f86f9fde3f272635d005eeec Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 16 Oct 2023 16:26:03 +0530 Subject: [PATCH 129/280] perf: index `dn_detail` in `Delivery Note Item` --- .../doctype/delivery_note_item/delivery_note_item.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 612d674e01..6148950462 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -725,7 +725,8 @@ "label": "Against Delivery Note Item", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "stock_qty_sec_break", @@ -892,7 +893,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-07-26 12:53:49.357171", + "modified": "2023-10-16 16:18:18.013379", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", @@ -902,4 +903,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file From d2096cfdb752bff03f8d3a00262d86c9eeb76c37 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 16 Oct 2023 16:53:43 +0530 Subject: [PATCH 130/280] fix: keep customer/supplier website role by default --- erpnext/setup/install.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 85eaf5fa92..b106cfcc1a 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -33,6 +33,7 @@ def after_install(): add_app_name() setup_log_settings() hide_workspaces() + update_roles() frappe.db.commit() @@ -232,6 +233,12 @@ def hide_workspaces(): frappe.db.set_value("Workspace", ws, "public", 0) +def update_roles(): + website_user_roles = ("Customer", "Supplier") + for role in website_user_roles: + frappe.db.set_value("Role", role, "desk_access", 0) + + def create_default_role_profiles(): for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items(): role_profile = frappe.new_doc("Role Profile") From 27a1e3bf834cb87f322d5943f8ef5405b0c30801 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:15:18 +0530 Subject: [PATCH 131/280] feat: validate negative stock for inventory dimension (backport #37373) (#37383) * feat: validate negative stock for inventory dimension (#37373) * feat: validate negative stock for inventory dimension * test: test case for validate negative stock for inv dimension (cherry picked from commit 1480acabb0faeae61c7c055bb7d1e81877b87cfb) # Conflicts: # erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py # erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py # erpnext/stock/stock_ledger.py * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix linter issue * chore: fix linter issue * chore: fix linter issue * chore: fix linter issue * chore: fix linter issue --------- Co-authored-by: rohitwaghchaure --- .../inventory_dimension.js | 2 +- .../inventory_dimension.json | 14 +++- .../inventory_dimension.py | 5 +- .../test_inventory_dimension.py | 67 ++++++++++++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 69 ++++++++++++++++++- .../stock_reconciliation.py | 43 +++++++++++- erpnext/stock/stock_ledger.py | 20 +++++- erpnext/stock/utils.py | 9 ++- 8 files changed, 218 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js index 0310682a2c..35d1c02719 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js @@ -37,7 +37,7 @@ frappe.ui.form.on('Inventory Dimension', { if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger && frm.doc.__onload.has_stock_ledger.length) { let allow_to_edit_fields = ['disabled', 'fetch_from_parent', - 'type_of_transaction', 'condition', 'mandatory_depends_on']; + 'type_of_transaction', 'condition', 'mandatory_depends_on', 'validate_negative_stock']; frm.fields.forEach((field) => { if (!in_list(allow_to_edit_fields, field.df.fieldname)) { diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index eb6102a436..0e4055251f 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -17,6 +17,8 @@ "target_fieldname", "applicable_for_documents_tab", "apply_to_all_doctypes", + "column_break_niy2u", + "validate_negative_stock", "column_break_13", "document_type", "type_of_transaction", @@ -173,11 +175,21 @@ "fieldname": "reqd", "fieldtype": "Check", "label": "Mandatory" + }, + { + "fieldname": "column_break_niy2u", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "validate_negative_stock", + "fieldtype": "Check", + "label": "Validate Negative Stock" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-01-31 13:44:38.507698", + "modified": "2023-10-05 12:52:18.705431", "modified_by": "Administrator", "module": "Stock", "name": "Inventory Dimension", diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 8bff4d5147..257d18fc33 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -60,6 +60,7 @@ class InventoryDimension(Document): "fetch_from_parent", "type_of_transaction", "condition", + "validate_negative_stock", ] for field in frappe.get_meta("Inventory Dimension").fields: @@ -160,6 +161,7 @@ class InventoryDimension(Document): insert_after="inventory_dimension", options=self.reference_document, label=label, + search_index=1, reqd=self.reqd, mandatory_depends_on=self.mandatory_depends_on, ), @@ -255,7 +257,7 @@ def field_exists(doctype, fieldname) -> str or None: def get_inventory_documents( doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None ): - and_filters = [["DocField", "parent", "not in", ["Batch", "Serial No"]]] + and_filters = [["DocField", "parent", "not in", ["Batch", "Serial No", "Item Price"]]] or_filters = [ ["DocField", "options", "in", ["Batch", "Serial No"]], ["DocField", "parent", "in", ["Putaway Rule"]], @@ -340,6 +342,7 @@ def get_inventory_dimensions(): fields=[ "distinct target_fieldname as fieldname", "reference_document as doctype", + "validate_negative_stock", ], filters={"disabled": 0}, ) diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 2d273c66fa..33394e5a11 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -414,6 +414,53 @@ class TestInventoryDimension(FrappeTestCase): else: self.assertEqual(d.store, "Inter Transfer Store 2") + def test_validate_negative_stock_for_inventory_dimension(self): + frappe.local.inventory_dimensions = {} + item_code = "Test Negative Inventory Dimension Item" + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) + create_item(item_code) + + inv_dimension = create_inventory_dimension( + apply_to_all_doctypes=1, + dimension_name="Inv Site", + reference_document="Inv Site", + document_type="Inv Site", + validate_negative_stock=1, + ) + + warehouse = create_warehouse("Negative Stock Warehouse") + doc = make_stock_entry(item_code=item_code, target=warehouse, qty=10, do_not_submit=True) + + doc.items[0].to_inv_site = "Site 1" + doc.submit() + + site_name = frappe.get_all( + "Stock Ledger Entry", filters={"voucher_no": doc.name, "is_cancelled": 0}, fields=["inv_site"] + )[0].inv_site + + self.assertEqual(site_name, "Site 1") + + doc = make_stock_entry(item_code=item_code, source=warehouse, qty=100, do_not_submit=True) + + doc.items[0].inv_site = "Site 1" + self.assertRaises(frappe.ValidationError, doc.submit) + + inv_dimension.reload() + inv_dimension.db_set("validate_negative_stock", 0) + frappe.local.inventory_dimensions = {} + + doc = make_stock_entry(item_code=item_code, source=warehouse, qty=100, do_not_submit=True) + + doc.items[0].inv_site = "Site 1" + doc.submit() + self.assertEqual(doc.docstatus, 1) + + site_name = frappe.get_all( + "Stock Ledger Entry", filters={"voucher_no": doc.name, "is_cancelled": 0}, fields=["inv_site"] + )[0].inv_site + + self.assertEqual(site_name, "Site 1") + def get_voucher_sl_entries(voucher_no, fields): return frappe.get_all( @@ -504,6 +551,26 @@ def prepare_test_data(): } ).insert(ignore_permissions=True) + if not frappe.db.exists("DocType", "Inv Site"): + frappe.get_doc( + { + "doctype": "DocType", + "name": "Inv Site", + "module": "Stock", + "custom": 1, + "naming_rule": "By fieldname", + "autoname": "field:site_name", + "fields": [{"label": "Site Name", "fieldname": "site_name", "fieldtype": "Data"}], + "permissions": [ + {"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1} + ], + } + ).insert(ignore_permissions=True) + + for site in ["Site 1", "Site 2"]: + if not frappe.db.exists("Inv Site", site): + frappe.get_doc({"doctype": "Inv Site", "site_name": site}).insert(ignore_permissions=True) + def create_inventory_dimension(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 3ca4bad4e4..c1b205132c 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -5,14 +5,16 @@ from datetime import date import frappe -from frappe import _ +from frappe import _, bold from frappe.core.doctype.role.role import get_users from frappe.model.document import Document -from frappe.utils import add_days, cint, formatdate, get_datetime, getdate +from frappe.utils import add_days, cint, flt, formatdate, get_datetime, getdate from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock +from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.serial_batch_bundle import SerialBatchBundle +from erpnext.stock.stock_ledger import get_previous_sle class StockFreezeError(frappe.ValidationError): @@ -48,6 +50,69 @@ class StockLedgerEntry(Document): self.validate_and_set_fiscal_year() self.block_transactions_against_group_warehouse() self.validate_with_last_transaction_posting_time() + self.validate_inventory_dimension_negative_stock() + + def validate_inventory_dimension_negative_stock(self): + extra_cond = "" + kwargs = {} + + dimensions = self._get_inventory_dimensions() + if not dimensions: + return + + for dimension, values in dimensions.items(): + kwargs[dimension] = values.get("value") + extra_cond += f" and {dimension} = %({dimension})s" + + kwargs.update( + { + "item_code": self.item_code, + "warehouse": self.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "company": self.company, + } + ) + + sle = get_previous_sle(kwargs, extra_cond=extra_cond) + if sle: + flt_precision = cint(frappe.db.get_default("float_precision")) or 2 + diff = sle.qty_after_transaction + flt(self.actual_qty) + diff = flt(diff, flt_precision) + if diff < 0 and abs(diff) > 0.0001: + self.throw_validation_error(diff, dimensions) + + def throw_validation_error(self, diff, dimensions): + dimension_msg = _(", with the inventory {0}: {1}").format( + "dimensions" if len(dimensions) > 1 else "dimension", + ", ".join(f"{bold(d.doctype)} ({d.value})" for k, d in dimensions.items()), + ) + + msg = _( + "{0} units of {1} are required in {2}{3}, on {4} {5} for {6} to complete the transaction." + ).format( + abs(diff), + frappe.get_desk_link("Item", self.item_code), + frappe.get_desk_link("Warehouse", self.warehouse), + dimension_msg, + self.posting_date, + self.posting_time, + frappe.get_desk_link(self.voucher_type, self.voucher_no), + ) + + frappe.throw(msg, title=_("Inventory Dimension Negative Stock")) + + def _get_inventory_dimensions(self): + inv_dimensions = get_inventory_dimensions() + inv_dimension_dict = {} + for dimension in inv_dimensions: + if not dimension.get("validate_negative_stock") or not self.get(dimension.fieldname): + continue + + dimension["value"] = self.get(dimension.fieldname) + inv_dimension_dict.setdefault(dimension.fieldname, dimension) + + return inv_dimension_dict def on_submit(self): self.check_stock_frozen_date() diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e36d5769bd..98b4ffdfcf 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -12,6 +12,7 @@ import erpnext from erpnext.accounts.utils import get_company_default from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty +from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( get_available_serial_nos, ) @@ -50,6 +51,7 @@ class StockReconciliation(StockController): self.clean_serial_nos() self.set_total_qty_and_amount() self.validate_putaway_capacity() + self.validate_inventory_dimension() if self._action == "submit": self.validate_reserved_stock() @@ -57,6 +59,17 @@ class StockReconciliation(StockController): def on_update(self): self.set_serial_and_batch_bundle(ignore_validate=True) + def validate_inventory_dimension(self): + dimensions = get_inventory_dimensions() + for dimension in dimensions: + for row in self.items: + if not row.batch_no and row.current_qty and row.get(dimension.get("fieldname")): + frappe.throw( + _( + "Row #{0}: You cannot use the inventory dimension '{1}' in Stock Reconciliation to modify the quantity or valuation rate. Stock reconciliation with inventory dimensions is intended solely for performing opening entries." + ).format(row.idx, bold(dimension.get("doctype"))) + ) + def on_submit(self): self.update_stock_ledger() self.make_gl_entries() @@ -202,8 +215,19 @@ class StockReconciliation(StockController): self.calculate_difference_amount(item, bundle_data) return True + inventory_dimensions_dict = {} + if not item.batch_no and not item.serial_no: + for dimension in get_inventory_dimensions(): + if item.get(dimension.get("fieldname")): + inventory_dimensions_dict[dimension.get("fieldname")] = item.get(dimension.get("fieldname")) + item_dict = get_stock_balance_for( - item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no + item.item_code, + item.warehouse, + self.posting_date, + self.posting_time, + batch_no=item.batch_no, + inventory_dimensions_dict=inventory_dimensions_dict, ) if (item.qty is None or item.qty == item_dict.get("qty")) and ( @@ -507,7 +531,13 @@ class StockReconciliation(StockController): if not row.batch_no: data.qty_after_transaction = flt(row.qty, row.precision("qty")) - if self.docstatus == 2: + dimensions = get_inventory_dimensions() + has_dimensions = False + for dimension in dimensions: + if row.get(dimension.get("fieldname")): + has_dimensions = True + + if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): if row.current_qty: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) @@ -523,6 +553,13 @@ class StockReconciliation(StockController): data.valuation_rate = flt(row.valuation_rate) data.stock_value_difference = -1 * flt(row.amount_difference) + elif ( + self.docstatus == 1 and has_dimensions and (not row.batch_no or not row.serial_and_batch_bundle) + ): + data.actual_qty = row.qty + data.qty_after_transaction = 0.0 + data.incoming_rate = flt(row.valuation_rate) + self.update_inventory_dimensions(row, data) return data @@ -911,6 +948,7 @@ def get_stock_balance_for( posting_time, batch_no: Optional[str] = None, with_valuation_rate: bool = True, + inventory_dimensions_dict=None, ): frappe.has_permission("Stock Reconciliation", "write", throw=True) @@ -939,6 +977,7 @@ def get_stock_balance_for( posting_time, with_valuation_rate=with_valuation_rate, with_serial_no=has_serial_no, + inventory_dimensions_dict=inventory_dimensions_dict, ) if has_serial_no: diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d3807b0f97..48119b8d1f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -24,6 +24,7 @@ from frappe.utils import ( import erpnext from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty +from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock, ) @@ -711,10 +712,17 @@ class update_entries_after(object): ): sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle) + dimensions = get_inventory_dimensions() + has_dimensions = False + if dimensions: + for dimension in dimensions: + if sle.get(dimension.get("fieldname")): + has_dimensions = True + if sle.serial_and_batch_bundle: self.calculate_valuation_for_serial_batch_bundle(sle) else: - if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no: + if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions: # assert self.wh_data.valuation_rate = sle.valuation_rate self.wh_data.qty_after_transaction = sle.qty_after_transaction @@ -1297,7 +1305,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc return sle[0] if sle else frappe._dict() -def get_previous_sle(args, for_update=False): +def get_previous_sle(args, for_update=False, extra_cond=None): """ get the last sle on or before the current time-bucket, to get actual qty before transaction, this function @@ -1312,7 +1320,9 @@ def get_previous_sle(args, for_update=False): } """ args["name"] = args.get("sle", None) or "" - sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update) + sle = get_stock_ledger_entries( + args, "<=", "desc", "limit 1", for_update=for_update, extra_cond=extra_cond + ) return sle and sle[0] or {} @@ -1324,6 +1334,7 @@ def get_stock_ledger_entries( for_update=False, debug=False, check_serial_no=True, + extra_cond=None, ): """get stock ledger entries filtered by specific posting datetime conditions""" conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format( @@ -1361,6 +1372,9 @@ def get_stock_ledger_entries( if operator in (">", "<=") and previous_sle.get("name"): conditions += " and name!=%(name)s" + if extra_cond: + conditions += f"{extra_cond}" + return frappe.db.sql( """ select *, timestamp(posting_date, posting_time) as "timestamp" diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 02444064c1..bd0d4697c9 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -95,6 +95,7 @@ def get_stock_balance( posting_time=None, with_valuation_rate=False, with_serial_no=False, + inventory_dimensions_dict=None, ): """Returns stock balance quantity at given warehouse on given posting date or current date. @@ -114,7 +115,13 @@ def get_stock_balance( "posting_time": posting_time, } - last_entry = get_previous_sle(args) + extra_cond = "" + if inventory_dimensions_dict: + for field, value in inventory_dimensions_dict.items(): + args[field] = value + extra_cond += f" and {field} = %({field})s" + + last_entry = get_previous_sle(args, extra_cond=extra_cond) if with_valuation_rate: if with_serial_no: From 8a72f4f58aee90e7155105b7275113555e788401 Mon Sep 17 00:00:00 2001 From: HarryPaulo Date: Mon, 16 Oct 2023 18:12:10 -0300 Subject: [PATCH 132/280] fix: billed_qty to show a sum of all invoiced qty from the purchase order item. --- .../report/purchase_order_analysis/purchase_order_analysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index e10c0e2fcc..b6e46302ff 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -6,7 +6,7 @@ import copy import frappe from frappe import _ -from frappe.query_builder.functions import IfNull +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import date_diff, flt, getdate @@ -57,7 +57,7 @@ def get_data(filters): po_item.qty, po_item.received_qty, (po_item.qty - po_item.received_qty).as_("pending_qty"), - IfNull(pi_item.qty, 0).as_("billed_qty"), + Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"), po_item.base_amount.as_("amount"), (po_item.received_qty * po_item.base_rate).as_("received_qty_amount"), (po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"), From fd6aee15e6bf3b7ea18487ab1b24b0a77526ac85 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 17 Oct 2023 12:48:07 +0530 Subject: [PATCH 133/280] fix(test): project test case --- erpnext/projects/doctype/task_depends_on/task_depends_on.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.json b/erpnext/projects/doctype/task_depends_on/task_depends_on.json index 5102986f00..3300b7eb90 100644 --- a/erpnext/projects/doctype/task_depends_on/task_depends_on.json +++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.json @@ -24,6 +24,7 @@ }, { "fetch_from": "task.subject", + "fetch_if_empty": 1, "fieldname": "subject", "fieldtype": "Text", "in_list_view": 1, @@ -31,7 +32,6 @@ "read_only": 1 }, { - "fetch_from": "task.project", "fieldname": "project", "fieldtype": "Text", "label": "Project", @@ -40,7 +40,7 @@ ], "istable": 1, "links": [], - "modified": "2023-10-09 11:34:14.335853", + "modified": "2023-10-17 12:45:21.536165", "modified_by": "Administrator", "module": "Projects", "name": "Task Depends On", From 0b85a525fb36b2d72baa9cf039205a0550c28f25 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Oct 2023 13:28:56 +0530 Subject: [PATCH 134/280] fix: GL Entries for receiving non CWIP assets using Purchase Receipt --- .../purchase_receipt/purchase_receipt.py | 560 ++++++++---------- 1 file changed, 260 insertions(+), 300 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6afa86e34e..de04381122 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -144,8 +144,8 @@ class PurchaseReceipt(BuyingController): if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category): # check cwip accounts before making auto assets # Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account - arbnb_account = self.get_company_default("asset_received_but_not_billed") - cwip_account = get_asset_account( + self.get_company_default("asset_received_but_not_billed") + get_asset_account( "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company ) break @@ -313,7 +313,7 @@ class PurchaseReceipt(BuyingController): self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) self.make_tax_gl_entries(gl_entries) - self.get_asset_gl_entry(gl_entries) + # self.get_asset_gl_entry(gl_entries) return process_gl_map(gl_entries) @@ -322,11 +322,20 @@ class PurchaseReceipt(BuyingController): get_purchase_document_details, ) - stock_rbnb = None + is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) + stock_asset_rbnb = None + stock_asset_account_name = None + remarks = self.get("remarks") or _("Accounting Entry for {0}").format( + "Asset" if is_asset_pr else "Stock" + ) + if erpnext.is_perpetual_inventory_enabled(self.company): - stock_rbnb = self.get_company_default("stock_received_but_not_billed") + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed") + if is_asset_pr + else self.get_company_default("stock_received_but_not_billed") + ) landed_cost_entries = get_item_account_wise_additional_cost(self.name) - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") warehouse_with_no_account = [] stock_items = self.get_stock_items() @@ -336,10 +345,243 @@ class PurchaseReceipt(BuyingController): ) ) + supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") + supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( + "account_currency" + ) exchange_rate_map, net_rate_map = get_purchase_document_details(self) + def make_item_asset_inward_entries(item): + if d.is_fixed_asset: + stock_asset_account_name = ( + get_asset_category_account( + asset_category=item.asset_category, + fieldname="capital_work_in_progress_account", + company=self.company, + ) + if is_cwip_accounting_enabled(d.asset_category) + else get_asset_category_account( + asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company + ) + ) + + stock_value_diff = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate) + elif flt(item.valuation_rate) and flt(item.qty): + # If PR is sub-contracted and fg item rate is zero + # in that case if account for source and target warehouse are same, + # then GL entries should not be posted + if ( + flt(stock_value_diff) == flt(d.rm_supp_cost) + and warehouse_account.get(self.supplier_warehouse) + and stock_asset_account_name == supplier_warehouse_account + ): + return + + stock_asset_account_name = warehouse_account[item.warehouse]["account"] + + account_currency = get_account_currency(stock_asset_account_name) + self.add_gl_entry( + gl_entries=gl_entries, + account=stock_asset_account_name, + cost_center=d.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_asset_rbnb, + account_currency=account_currency, + item=item, + ) + + def make_stock_received_but_not_billed_entry(item, outgoing_amount): + # GL Entry for from warehouse or Stock Received but not billed + # Intentionally passed negative debit amount to avoid incorrect GL Entry validation + credit_amount = ( + flt(item.base_net_amount, item.precision("base_net_amount")) + if credit_currency == self.company_currency + else flt(item.net_amount, item.precision("net_amount")) + ) + + if self.is_internal_transfer() and item.valuation_rate: + outgoing_amount = abs( + frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": self.name, + "voucher_detail_no": item.name, + "warehouse": item.from_warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + ) + credit_amount = outgoing_amount + + if credit_amount: + account = ( + warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=-1 * flt(outgoing_amount, item.precision("base_net_amount")), + credit=0.0, + remarks=remarks, + against_account=stock_asset_account_name, + debit_in_account_currency=-1 * credit_amount, + account_currency=credit_currency, + item=item, + ) + + # check if the exchange rate has changed + if d.get("purchase_invoice"): + if ( + exchange_rate_map[item.purchase_invoice] + and self.conversion_rate != exchange_rate_map[item.purchase_invoice] + and item.net_rate == net_rate_map[item.purchase_invoice_item] + ): + + discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * ( + exchange_rate_map[item.purchase_invoice] - self.conversion_rate + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=discrepancy_caused_by_exchange_rate_difference, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, + item=item, + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=self.get_company_default("exchange_gain_loss_account"), + cost_center=d.cost_center, + debit=discrepancy_caused_by_exchange_rate_difference, + credit=0.0, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, + item=item, + ) + + def make_landed_cost_gl_entries(item): + # Amount added through landed-cos-voucher + if item.landed_cost_voucher_amount and landed_cost_entries: + if (item.item_code, item.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): + account_currency = get_account_currency(account) + credit_amount = ( + flt(amount["base_amount"]) + if (amount["base_amount"] or account_currency != self.company_currency) + else flt(amount["amount"]) + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=stock_asset_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) + + def make_rate_difference_entry(item): + if item.rate_difference_with_purchase_invoice and stock_asset_rbnb: + account_currency = get_account_currency(stock_asset_rbnb) + self.add_gl_entry( + gl_entries=gl_entries, + account=stock_asset_rbnb, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.rate_difference_with_purchase_invoice), + remarks=_("Adjustment based on Purchase Invoice rate"), + against_account=stock_asset_account_name, + account_currency=account_currency, + project=item.project, + item=item, + ) + + def make_sub_contracting_gl_entries(item): + # sub-contracting warehouse + if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): + self.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.rm_supp_cost), + remarks=remarks, + against_account=stock_asset_account_name, + account_currency=supplier_warehouse_account_currency, + item=item, + ) + + def make_divisional_loss_gl_entry(item): + if item.is_fixed_asset: + return + + # divisional loss adjustment + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + valuation_amount_as_per_doc = ( + flt(outgoing_amount, d.precision("base_net_amount")) + + flt(item.landed_cost_voucher_amount) + + flt(item.rm_supp_cost) + + flt(item.item_tax_amount) + + flt(item.rate_difference_with_purchase_invoice) + ) + + divisional_loss = flt( + valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount") + ) + + if divisional_loss: + if self.is_return or flt(item.item_tax_amount): + loss_account = expenses_included_in_valuation + else: + loss_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or stock_asset_rbnb + ) + + cost_center = item.cost_center or frappe.get_cached_value( + "Company", self.company, "cost_center" + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=stock_asset_account_name, + account_currency=credit_currency, + project=item.project, + item=item, + ) + for d in self.get("items"): - if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): + if d.item_code in stock_items or d.is_fixed_asset: + credit_currency = ( + get_account_currency(warehouse_account[d.from_warehouse]["account"]) + if d.from_warehouse + else get_account_currency(stock_asset_rbnb) + ) + if warehouse_account.get(d.warehouse): stock_value_diff = frappe.db.get_value( "Stock Ledger Entry", @@ -353,213 +595,13 @@ class PurchaseReceipt(BuyingController): "stock_value_difference", ) - warehouse_account_name = warehouse_account[d.warehouse]["account"] - warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"] - supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") - supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( - "account_currency" - ) - remarks = self.get("remarks") or _("Accounting Entry for Stock") - - # If PR is sub-contracted and fg item rate is zero - # in that case if account for source and target warehouse are same, - # then GL entries should not be posted - if ( - flt(stock_value_diff) == flt(d.rm_supp_cost) - and warehouse_account.get(self.supplier_warehouse) - and warehouse_account_name == supplier_warehouse_account - ): - continue - - self.add_gl_entry( - gl_entries=gl_entries, - account=warehouse_account_name, - cost_center=d.cost_center, - debit=stock_value_diff, - credit=0.0, - remarks=remarks, - against_account=stock_rbnb, - account_currency=warehouse_account_currency, - item=d, - ) - - # GL Entry for from warehouse or Stock Received but not billed - # Intentionally passed negative debit amount to avoid incorrect GL Entry validation - credit_currency = ( - get_account_currency(warehouse_account[d.from_warehouse]["account"]) - if d.from_warehouse - else get_account_currency(stock_rbnb) - ) - - credit_amount = ( - flt(d.base_net_amount, d.precision("base_net_amount")) - if credit_currency == self.company_currency - else flt(d.net_amount, d.precision("net_amount")) - ) - - outgoing_amount = d.base_net_amount - if self.is_internal_transfer() and d.valuation_rate: - outgoing_amount = abs( - frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": "Purchase Receipt", - "voucher_no": self.name, - "voucher_detail_no": d.name, - "warehouse": d.from_warehouse, - "is_cancelled": 0, - }, - "stock_value_difference", - ) - ) - credit_amount = outgoing_amount - - if credit_amount: - account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")), - credit=0.0, - remarks=remarks, - against_account=warehouse_account_name, - debit_in_account_currency=-1 * credit_amount, - account_currency=credit_currency, - item=d, - ) - - # check if the exchange rate has changed - if d.get("purchase_invoice"): - if ( - exchange_rate_map[d.purchase_invoice] - and self.conversion_rate != exchange_rate_map[d.purchase_invoice] - and d.net_rate == net_rate_map[d.purchase_invoice_item] - ): - - discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * ( - exchange_rate_map[d.purchase_invoice] - self.conversion_rate - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=0.0, - credit=discrepancy_caused_by_exchange_rate_difference, - remarks=remarks, - against_account=self.supplier, - debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, - item=d, - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=self.get_company_default("exchange_gain_loss_account"), - cost_center=d.cost_center, - debit=discrepancy_caused_by_exchange_rate_difference, - credit=0.0, - remarks=remarks, - against_account=self.supplier, - debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, - item=d, - ) - - # Amount added through landed-cos-voucher - if d.landed_cost_voucher_amount and landed_cost_entries: - if (d.item_code, d.name) in landed_cost_entries: - for account, amount in landed_cost_entries[(d.item_code, d.name)].items(): - account_currency = get_account_currency(account) - credit_amount = ( - flt(amount["base_amount"]) - if (amount["base_amount"] or account_currency != self.company_currency) - else flt(amount["amount"]) - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=0.0, - credit=credit_amount, - remarks=remarks, - against_account=warehouse_account_name, - credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, - project=d.project, - item=d, - ) - - if d.rate_difference_with_purchase_invoice and stock_rbnb: - account_currency = get_account_currency(stock_rbnb) - self.add_gl_entry( - gl_entries=gl_entries, - account=stock_rbnb, - cost_center=d.cost_center, - debit=0.0, - credit=flt(d.rate_difference_with_purchase_invoice), - remarks=_("Adjustment based on Purchase Invoice rate"), - against_account=warehouse_account_name, - account_currency=account_currency, - project=d.project, - item=d, - ) - - # sub-contracting warehouse - if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - self.add_gl_entry( - gl_entries=gl_entries, - account=supplier_warehouse_account, - cost_center=d.cost_center, - debit=0.0, - credit=flt(d.rm_supp_cost), - remarks=remarks, - against_account=warehouse_account_name, - account_currency=supplier_warehouse_account_currency, - item=d, - ) - - # divisional loss adjustment - valuation_amount_as_per_doc = ( - flt(outgoing_amount, d.precision("base_net_amount")) - + flt(d.landed_cost_voucher_amount) - + flt(d.rm_supp_cost) - + flt(d.item_tax_amount) - + flt(d.rate_difference_with_purchase_invoice) - ) - - divisional_loss = flt( - valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount") - ) - - if divisional_loss: - if self.is_return or flt(d.item_tax_amount): - loss_account = expenses_included_in_valuation - else: - loss_account = ( - self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb - ) - - cost_center = d.cost_center or frappe.get_cached_value( - "Company", self.company, "cost_center" - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=loss_account, - cost_center=cost_center, - debit=divisional_loss, - credit=0.0, - remarks=remarks, - against_account=warehouse_account_name, - account_currency=credit_currency, - project=d.project, - item=d, - ) - + outgoing_amount = d.base_net_amount + d.item_tax_amount + make_item_asset_inward_entries(d) + make_stock_received_but_not_billed_entry(d, outgoing_amount) + make_landed_cost_gl_entries(d) + make_rate_difference_entry(d) + make_sub_contracting_gl_entries(d) + make_divisional_loss_gl_entry(d) elif ( d.warehouse not in warehouse_with_no_account or d.rejected_warehouse not in warehouse_with_no_account @@ -576,6 +618,9 @@ class PurchaseReceipt(BuyingController): d, gl_entries, self.posting_date, d.get("provisional_expense_account") ) + if d.is_fixed_asset: + self.update_assets(d, d.valuation_rate) + if warehouse_with_no_account: frappe.msgprint( _("No accounting entries for the following warehouses") @@ -709,94 +754,9 @@ class PurchaseReceipt(BuyingController): self.add_lcv_gl_entries(item, gl_entries) # update assets gross amount by its valuation rate # valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item - self.update_assets(item, item.valuation_rate) + return gl_entries - def add_asset_gl_entries(self, item, gl_entries): - arbnb_account = self.get_company_default("asset_received_but_not_billed") - # This returns category's cwip account if not then fallback to company's default cwip account - cwip_account = get_asset_account( - "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company - ) - - asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate) - base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) - remarks = self.get("remarks") or _("Accounting Entry for Asset") - - cwip_account_currency = get_account_currency(cwip_account) - # debit cwip account - debit_in_account_currency = ( - base_asset_amount if cwip_account_currency == self.company_currency else asset_amount - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=cwip_account, - cost_center=item.cost_center, - debit=base_asset_amount, - credit=0.0, - remarks=remarks, - against_account=arbnb_account, - debit_in_account_currency=debit_in_account_currency, - item=item, - ) - - asset_rbnb_currency = get_account_currency(arbnb_account) - # credit arbnb account - credit_in_account_currency = ( - base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=arbnb_account, - cost_center=item.cost_center, - debit=0.0, - credit=base_asset_amount, - remarks=remarks, - against_account=cwip_account, - credit_in_account_currency=credit_in_account_currency, - item=item, - ) - - def add_lcv_gl_entries(self, item, gl_entries): - expenses_included_in_asset_valuation = self.get_company_default( - "expenses_included_in_asset_valuation" - ) - if not is_cwip_accounting_enabled(item.asset_category): - asset_account = get_asset_category_account( - asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company - ) - else: - # This returns company's default cwip account - asset_account = get_asset_account("capital_work_in_progress_account", company=self.company) - - remarks = self.get("remarks") or _("Accounting Entry for Stock") - - self.add_gl_entry( - gl_entries=gl_entries, - account=expenses_included_in_asset_valuation, - cost_center=item.cost_center, - debit=0.0, - credit=flt(item.landed_cost_voucher_amount), - remarks=remarks, - against_account=asset_account, - project=item.project, - item=item, - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=asset_account, - cost_center=item.cost_center, - debit=flt(item.landed_cost_voucher_amount), - credit=0.0, - remarks=remarks, - against_account=expenses_included_in_asset_valuation, - project=item.project, - item=item, - ) - def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code} From f900a78995e20958828afcbd5ab8bb515fd2e075 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Tue, 17 Oct 2023 17:05:44 +0530 Subject: [PATCH 135/280] refactor!: drop ecommerce in favor of webshop (#33265) * refactor!: remove ecommerce item group field check Signed-off-by: Sabu Siyad * refactor!: remove `e_commerce` directory Signed-off-by: Sabu Siyad * refactor!: remove `get_context` from `item_group` https://frappeframework.com/docs/v14/user/en/guides/portal-development/context Signed-off-by: Sabu Siyad * refactor!: remove related `./templates` Signed-off-by: Sabu Siyad * refactor!(navbar): remove wishlist (ecommerce) Signed-off-by: Sabu Siyad * refactor!(js): remove js from scripts Signed-off-by: Sabu Siyad * refactor!: remove `www/all-products` Signed-off-by: Sabu Siyad * refactor!: remove pages and js Signed-off-by: Sabu Siyad * refactor!: remove js/customer_reviews Signed-off-by: Sabu Siyad * refactor!(portal utils): remove shopping cart debtor account Signed-off-by: Sabu Siyad * refactor!: remove e_commerce events from hooks Signed-off-by: Sabu Siyad * refactor!(web): remove e_commerce js from bundle Signed-off-by: Sabu Siyad * refactor!(setup): remove shopping cart setup Signed-off-by: Sabu Siyad * refactor!: remove pages Signed-off-by: Sabu Siyad * refactor(item): remove website item button Signed-off-by: Sabu Siyad * refactor!(payment request): remove `on_payment_authorized` Signed-off-by: Sabu Siyad * refactor!: @staticmethod `get_gateway_details` to avoid monkey patching, in custom apps https://discuss.erpnext.com/t/how-to-override-method-in-frappe/28786/36 Signed-off-by: Sabu Siyad * refactor!(pages): remove product page Signed-off-by: Sabu Siyad * refactor!(homepage): do not setup website items Signed-off-by: Sabu Siyad * refactor(workspace): remove link to ecommerce settings Signed-off-by: Sabu Siyad * refactor!(www): remove shop-by-category Signed-off-by: Sabu Siyad * refactor!(homepage): remove featured product Signed-off-by: Sabu Siyad * refactor: remove products in homepage Signed-off-by: Sabu Siyad * refactor(homepage): remove explore button Signed-off-by: Sabu Siyad * refactor: remove products fields from homepage Signed-off-by: Sabu Siyad * Revert "refactor!: @staticmethod `get_gateway_details`" This reverts commit 561bcd96680a930bb92627869502d9346b10611b. Signed-off-by: Sabu Siyad * refactor!: remove payment gateway e_commerce import Signed-off-by: Sabu Siyad * chore: pre-commit Signed-off-by: Sabu Siyad * refactor!: pass `party` into `get_price` Signed-off-by: Sabu Siyad * refactor: move `get_item_codes_by_attributes` to `utilities/product` Signed-off-by: Sabu Siyad * refactor!(quotation): input customer group Signed-off-by: Sabu Siyad * chore: pre-commit * refactor: remove custom `navbar_items.html` * refactor!(item): remove `published_in_website` * refactor: move `validate_duplicate_website_item` before rename * test: remove `test_shopping_cart_without_website_item` * chore: add doctype drop patch * refactor: removed website item related code * refactor: removed shopping_cart code * refactor: removed e-commerce related patches * refactor: removed website related fields from item group * fix: patch create_asset_depreciation_schedules_from_assets, KeyError: '0K BU64 AUY' --------- Signed-off-by: Sabu Siyad Co-authored-by: Rohit Waghchaure --- .../payment_request/payment_request.py | 46 +- .../subscription_plan/subscription_plan.py | 3 +- erpnext/accounts/doctype/tax_rule/tax_rule.py | 18 +- erpnext/controllers/item_variant.py | 29 +- erpnext/e_commerce/__init__.py | 0 erpnext/e_commerce/api.py | 81 - erpnext/e_commerce/doctype/__init__.py | 0 .../doctype/e_commerce_settings/__init__.py | 0 .../e_commerce_settings.js | 58 - .../e_commerce_settings.json | 395 ----- .../e_commerce_settings.py | 185 --- .../test_e_commerce_settings.py | 53 - .../doctype/item_review/__init__.py | 0 .../doctype/item_review/item_review.js | 8 - .../doctype/item_review/item_review.json | 134 -- .../doctype/item_review/item_review.py | 153 -- .../doctype/item_review/test_item_review.py | 84 - .../doctype/recommended_items/__init__.py | 0 .../recommended_items/recommended_items.json | 88 -- .../recommended_items/recommended_items.py | 9 - .../doctype/website_item/__init__.py | 0 .../website_item/templates/website_item.html | 7 - .../templates/website_item_row.html | 4 - .../doctype/website_item/test_website_item.py | 564 ------- .../doctype/website_item/website_item.js | 37 - .../doctype/website_item/website_item.json | 414 ----- .../doctype/website_item/website_item.py | 469 ------ .../doctype/website_item/website_item_list.js | 20 - .../website_item_tabbed_section/__init__.py | 0 .../website_item_tabbed_section.json | 37 - .../website_item_tabbed_section.py | 10 - .../doctype/website_offer/__init__.py | 0 .../doctype/website_offer/website_offer.json | 43 - .../doctype/website_offer/website_offer.py | 15 - .../e_commerce/doctype/wishlist/__init__.py | 0 .../doctype/wishlist/test_wishlist.py | 117 -- .../e_commerce/doctype/wishlist/wishlist.js | 8 - .../e_commerce/doctype/wishlist/wishlist.json | 65 - .../e_commerce/doctype/wishlist/wishlist.py | 70 - .../doctype/wishlist_item/__init__.py | 0 .../doctype/wishlist_item/wishlist_item.json | 147 -- .../doctype/wishlist_item/wishlist_item.py | 10 - erpnext/e_commerce/legacy_search.py | 134 -- .../e_commerce/product_data_engine/filters.py | 158 -- .../e_commerce/product_data_engine/query.py | 321 ---- .../test_item_group_product_data_engine.py | 170 -- .../test_product_data_engine.py | 348 ----- erpnext/e_commerce/product_ui/grid.js | 201 --- erpnext/e_commerce/product_ui/list.js | 205 --- erpnext/e_commerce/product_ui/search.js | 244 --- erpnext/e_commerce/product_ui/views.js | 548 ------- erpnext/e_commerce/redisearch_utils.py | 255 --- erpnext/e_commerce/shopping_cart/__init__.py | 0 erpnext/e_commerce/shopping_cart/cart.py | 721 --------- .../e_commerce/shopping_cart/product_info.py | 99 -- .../shopping_cart/test_shopping_cart.py | 398 ----- erpnext/e_commerce/shopping_cart/utils.py | 54 - .../e_commerce/variant_selector/__init__.py | 0 .../variant_selector/item_variants_cache.py | 130 -- .../variant_selector/test_variant_selector.py | 125 -- erpnext/e_commerce/variant_selector/utils.py | 251 --- erpnext/e_commerce/web_template/__init__.py | 0 .../web_template/hero_slider/__init__.py | 0 .../web_template/hero_slider/hero_slider.html | 86 - .../web_template/hero_slider/hero_slider.json | 288 ---- .../web_template/item_card_group/__init__.py | 0 .../item_card_group/item_card_group.html | 37 - .../item_card_group/item_card_group.json | 270 ---- .../web_template/product_card/__init__.py | 0 .../product_card/product_card.html | 0 .../product_card/product_card.json | 31 - .../product_category_cards/__init__.py | 0 .../product_category_cards.html | 47 - .../product_category_cards.json | 85 - erpnext/hooks.py | 15 +- erpnext/modules.txt | 3 +- erpnext/patches.txt | 10 +- ...ebsite_item_in_item_card_group_template.py | 60 - ...py_custom_field_filters_to_website_item.py | 94 -- erpnext/patches/v13_0/create_website_items.py | 85 - .../v13_0/fetch_thumbnail_in_website_items.py | 11 - .../make_homepage_products_website_items.py | 15 - .../v13_0/populate_e_commerce_settings.py | 68 - .../v13_0/shopping_cart_to_ecommerce.py | 29 - ...sset_depreciation_schedules_from_assets.py | 3 + .../v15_0/delete_ecommerce_doctypes.py | 30 + erpnext/portal/doctype/homepage/homepage.js | 9 - erpnext/portal/doctype/homepage/homepage.json | 27 +- erpnext/portal/doctype/homepage/homepage.py | 23 - .../homepage_featured_product/__init__.py | 0 .../homepage_featured_product.json | 118 -- .../homepage_featured_product.py | 9 - erpnext/portal/utils.py | 27 +- erpnext/public/js/customer_reviews.js | 138 -- erpnext/public/js/erpnext-web.bundle.js | 7 - erpnext/public/js/shopping_cart.js | 243 --- erpnext/public/js/wishlist.js | 204 --- erpnext/public/scss/erpnext-web.bundle.scss | 1 - erpnext/public/scss/shopping_cart.scss | 1381 ----------------- .../selling/doctype/quotation/quotation.py | 32 +- .../doctype/quotation/test_quotation.py | 9 - .../setup/doctype/item_group/item_group.js | 14 - .../setup/doctype/item_group/item_group.json | 104 +- .../setup/doctype/item_group/item_group.py | 146 +- .../setup_wizard/operations/company_setup.py | 14 - .../operations/install_fixtures.py | 15 - .../erpnext_settings/erpnext_settings.json | 998 ++++++------ erpnext/stock/doctype/item/item.js | 30 - erpnext/stock/doctype/item/item.json | 9 - erpnext/stock/doctype/item/item.py | 77 - erpnext/stock/doctype/item/item_dashboard.py | 1 - .../stock/doctype/price_list/price_list.py | 16 - erpnext/templates/generators/item/item.html | 80 - .../generators/item/item_add_to_cart.html | 180 --- .../generators/item/item_configure.html | 20 - .../generators/item/item_configure.js | 343 ---- .../generators/item/item_details.html | 63 - .../templates/generators/item/item_image.html | 108 -- .../generators/item/item_inquiry.html | 11 - .../templates/generators/item/item_inquiry.js | 77 - .../generators/item/item_reviews.html | 88 -- .../generators/item/item_specifications.html | 20 - erpnext/templates/generators/item_group.html | 72 - .../templates/includes/cart/address_card.html | 17 - .../includes/cart/address_picker_card.html | 12 - .../templates/includes/cart/cart_address.html | 189 --- .../includes/cart/cart_address_picker.html | 3 - .../includes/cart/cart_dropdown.html | 27 - .../templates/includes/cart/cart_items.html | 113 -- .../includes/cart/cart_items_dropdown.html | 12 - .../includes/cart/cart_items_total.html | 10 - .../templates/includes/cart/cart_macros.html | 22 - .../includes/cart/cart_payment_summary.html | 84 - .../includes/navbar/navbar_items.html | 22 - .../includes/order/order_macros.html | 52 - erpnext/templates/includes/product_page.js | 217 --- erpnext/templates/pages/cart.html | 132 -- erpnext/templates/pages/cart.js | 303 ---- erpnext/templates/pages/cart.py | 11 - erpnext/templates/pages/customer_reviews.html | 67 - erpnext/templates/pages/customer_reviews.py | 25 - erpnext/templates/pages/home.html | 23 - erpnext/templates/pages/home.py | 7 - erpnext/templates/pages/order.js | 42 - erpnext/templates/pages/order.py | 7 - erpnext/templates/pages/product_search.html | 32 - erpnext/templates/pages/product_search.py | 152 -- erpnext/templates/pages/wishlist.html | 28 - erpnext/templates/pages/wishlist.py | 81 - erpnext/utilities/product.py | 156 +- erpnext/www/all-products/__init__.py | 0 erpnext/www/all-products/index.html | 51 - erpnext/www/all-products/index.js | 27 - erpnext/www/all-products/index.py | 22 - erpnext/www/all-products/not_found.html | 1 - erpnext/www/shop-by-category/__init__.py | 0 .../category_card_section.html | 30 - erpnext/www/shop-by-category/index.html | 48 - erpnext/www/shop-by-category/index.js | 12 - erpnext/www/shop-by-category/index.py | 91 -- 160 files changed, 630 insertions(+), 15222 deletions(-) delete mode 100644 erpnext/e_commerce/__init__.py delete mode 100644 erpnext/e_commerce/api.py delete mode 100644 erpnext/e_commerce/doctype/__init__.py delete mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/__init__.py delete mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js delete mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json delete mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py delete mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py delete mode 100644 erpnext/e_commerce/doctype/item_review/__init__.py delete mode 100644 erpnext/e_commerce/doctype/item_review/item_review.js delete mode 100644 erpnext/e_commerce/doctype/item_review/item_review.json delete mode 100644 erpnext/e_commerce/doctype/item_review/item_review.py delete mode 100644 erpnext/e_commerce/doctype/item_review/test_item_review.py delete mode 100644 erpnext/e_commerce/doctype/recommended_items/__init__.py delete mode 100644 erpnext/e_commerce/doctype/recommended_items/recommended_items.json delete mode 100644 erpnext/e_commerce/doctype/recommended_items/recommended_items.py delete mode 100644 erpnext/e_commerce/doctype/website_item/__init__.py delete mode 100644 erpnext/e_commerce/doctype/website_item/templates/website_item.html delete mode 100644 erpnext/e_commerce/doctype/website_item/templates/website_item_row.html delete mode 100644 erpnext/e_commerce/doctype/website_item/test_website_item.py delete mode 100644 erpnext/e_commerce/doctype/website_item/website_item.js delete mode 100644 erpnext/e_commerce/doctype/website_item/website_item.json delete mode 100644 erpnext/e_commerce/doctype/website_item/website_item.py delete mode 100644 erpnext/e_commerce/doctype/website_item/website_item_list.js delete mode 100644 erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py delete mode 100644 erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json delete mode 100644 erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py delete mode 100644 erpnext/e_commerce/doctype/website_offer/__init__.py delete mode 100644 erpnext/e_commerce/doctype/website_offer/website_offer.json delete mode 100644 erpnext/e_commerce/doctype/website_offer/website_offer.py delete mode 100644 erpnext/e_commerce/doctype/wishlist/__init__.py delete mode 100644 erpnext/e_commerce/doctype/wishlist/test_wishlist.py delete mode 100644 erpnext/e_commerce/doctype/wishlist/wishlist.js delete mode 100644 erpnext/e_commerce/doctype/wishlist/wishlist.json delete mode 100644 erpnext/e_commerce/doctype/wishlist/wishlist.py delete mode 100644 erpnext/e_commerce/doctype/wishlist_item/__init__.py delete mode 100644 erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json delete mode 100644 erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py delete mode 100644 erpnext/e_commerce/legacy_search.py delete mode 100644 erpnext/e_commerce/product_data_engine/filters.py delete mode 100644 erpnext/e_commerce/product_data_engine/query.py delete mode 100644 erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py delete mode 100644 erpnext/e_commerce/product_data_engine/test_product_data_engine.py delete mode 100644 erpnext/e_commerce/product_ui/grid.js delete mode 100644 erpnext/e_commerce/product_ui/list.js delete mode 100644 erpnext/e_commerce/product_ui/search.js delete mode 100644 erpnext/e_commerce/product_ui/views.js delete mode 100644 erpnext/e_commerce/redisearch_utils.py delete mode 100644 erpnext/e_commerce/shopping_cart/__init__.py delete mode 100644 erpnext/e_commerce/shopping_cart/cart.py delete mode 100644 erpnext/e_commerce/shopping_cart/product_info.py delete mode 100644 erpnext/e_commerce/shopping_cart/test_shopping_cart.py delete mode 100644 erpnext/e_commerce/shopping_cart/utils.py delete mode 100644 erpnext/e_commerce/variant_selector/__init__.py delete mode 100644 erpnext/e_commerce/variant_selector/item_variants_cache.py delete mode 100644 erpnext/e_commerce/variant_selector/test_variant_selector.py delete mode 100644 erpnext/e_commerce/variant_selector/utils.py delete mode 100644 erpnext/e_commerce/web_template/__init__.py delete mode 100644 erpnext/e_commerce/web_template/hero_slider/__init__.py delete mode 100644 erpnext/e_commerce/web_template/hero_slider/hero_slider.html delete mode 100644 erpnext/e_commerce/web_template/hero_slider/hero_slider.json delete mode 100644 erpnext/e_commerce/web_template/item_card_group/__init__.py delete mode 100644 erpnext/e_commerce/web_template/item_card_group/item_card_group.html delete mode 100644 erpnext/e_commerce/web_template/item_card_group/item_card_group.json delete mode 100644 erpnext/e_commerce/web_template/product_card/__init__.py delete mode 100644 erpnext/e_commerce/web_template/product_card/product_card.html delete mode 100644 erpnext/e_commerce/web_template/product_card/product_card.json delete mode 100644 erpnext/e_commerce/web_template/product_category_cards/__init__.py delete mode 100644 erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html delete mode 100644 erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json delete mode 100644 erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py delete mode 100644 erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py delete mode 100644 erpnext/patches/v13_0/create_website_items.py delete mode 100644 erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py delete mode 100644 erpnext/patches/v13_0/make_homepage_products_website_items.py delete mode 100644 erpnext/patches/v13_0/populate_e_commerce_settings.py delete mode 100644 erpnext/patches/v13_0/shopping_cart_to_ecommerce.py create mode 100644 erpnext/patches/v15_0/delete_ecommerce_doctypes.py delete mode 100644 erpnext/portal/doctype/homepage_featured_product/__init__.py delete mode 100644 erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json delete mode 100644 erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py delete mode 100644 erpnext/public/js/customer_reviews.js delete mode 100644 erpnext/public/js/shopping_cart.js delete mode 100644 erpnext/public/js/wishlist.js delete mode 100644 erpnext/public/scss/shopping_cart.scss delete mode 100644 erpnext/templates/generators/item/item.html delete mode 100644 erpnext/templates/generators/item/item_add_to_cart.html delete mode 100644 erpnext/templates/generators/item/item_configure.html delete mode 100644 erpnext/templates/generators/item/item_configure.js delete mode 100644 erpnext/templates/generators/item/item_details.html delete mode 100644 erpnext/templates/generators/item/item_image.html delete mode 100644 erpnext/templates/generators/item/item_inquiry.html delete mode 100644 erpnext/templates/generators/item/item_inquiry.js delete mode 100644 erpnext/templates/generators/item/item_reviews.html delete mode 100644 erpnext/templates/generators/item/item_specifications.html delete mode 100644 erpnext/templates/generators/item_group.html delete mode 100644 erpnext/templates/includes/cart/address_card.html delete mode 100644 erpnext/templates/includes/cart/address_picker_card.html delete mode 100644 erpnext/templates/includes/cart/cart_address.html delete mode 100644 erpnext/templates/includes/cart/cart_address_picker.html delete mode 100644 erpnext/templates/includes/cart/cart_dropdown.html delete mode 100644 erpnext/templates/includes/cart/cart_items.html delete mode 100644 erpnext/templates/includes/cart/cart_items_dropdown.html delete mode 100644 erpnext/templates/includes/cart/cart_items_total.html delete mode 100644 erpnext/templates/includes/cart/cart_macros.html delete mode 100644 erpnext/templates/includes/cart/cart_payment_summary.html delete mode 100644 erpnext/templates/includes/navbar/navbar_items.html delete mode 100644 erpnext/templates/includes/order/order_macros.html delete mode 100644 erpnext/templates/includes/product_page.js delete mode 100644 erpnext/templates/pages/cart.html delete mode 100644 erpnext/templates/pages/cart.js delete mode 100644 erpnext/templates/pages/cart.py delete mode 100644 erpnext/templates/pages/customer_reviews.html delete mode 100644 erpnext/templates/pages/customer_reviews.py delete mode 100644 erpnext/templates/pages/order.js delete mode 100644 erpnext/templates/pages/product_search.html delete mode 100644 erpnext/templates/pages/product_search.py delete mode 100644 erpnext/templates/pages/wishlist.html delete mode 100644 erpnext/templates/pages/wishlist.py delete mode 100644 erpnext/www/all-products/__init__.py delete mode 100644 erpnext/www/all-products/index.html delete mode 100644 erpnext/www/all-products/index.js delete mode 100644 erpnext/www/all-products/index.py delete mode 100644 erpnext/www/all-products/not_found.html delete mode 100644 erpnext/www/shop-by-category/__init__.py delete mode 100644 erpnext/www/shop-by-category/category_card_section.html delete mode 100644 erpnext/www/shop-by-category/index.html delete mode 100644 erpnext/www/shop-by-category/index.js delete mode 100644 erpnext/www/shop-by-category/index.py diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index ce15bcff81..5f0b434c70 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -1,13 +1,9 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - import json import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, get_url, nowdate +from frappe.utils import flt, nowdate from frappe.utils.background_jobs import enqueue from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -363,33 +359,6 @@ class PaymentRequest(Document): def get_payment_success_url(self): return self.payment_success_url - def on_payment_authorized(self, status=None): - if not status: - return - - shopping_cart_settings = frappe.get_doc("E Commerce Settings") - - if status in ["Authorized", "Completed"]: - redirect_to = None - self.set_as_paid() - - # if shopping cart enabled and in session - if ( - shopping_cart_settings.enabled - and hasattr(frappe.local, "session") - and frappe.local.session.user != "Guest" - ) and self.payment_channel != "Phone": - - success_url = shopping_cart_settings.payment_success_url - if success_url: - redirect_to = ({"Orders": "/orders", "Invoices": "/invoices", "My Account": "/me"}).get( - success_url, "/me" - ) - else: - redirect_to = get_url("/orders/{0}".format(self.reference_name)) - - return redirect_to - def create_subscription(self, payment_provider, gateway_controller, data): if payment_provider == "stripe": with payment_app_import_guard(): @@ -546,13 +515,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn): def get_gateway_details(args): # nosemgrep - """return gateway and payment account of default payment gateway""" - if args.get("payment_gateway_account"): - return get_payment_gateway_account(args.get("payment_gateway_account")) - - if args.order_type == "Shopping Cart": - payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account - return get_payment_gateway_account(payment_gateway_account) + """ + Return gateway and payment account of default payment gateway + """ + gateway_account = args.get("payment_gateway_account", {"is_default": 1}) + if gateway_account: + return get_payment_gateway_account(gateway_account) gateway_account = get_payment_gateway_account({"is_default": 1}) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index 75223c2ccc..f6e5c56cce 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -22,7 +22,7 @@ class SubscriptionPlan(Document): @frappe.whitelist() def get_plan_rate( - plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1 + plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None ): plan = frappe.get_doc("Subscription Plan", plan) if plan.price_determination == "Fixed Rate": @@ -40,6 +40,7 @@ def get_plan_rate( customer_group=customer_group, company=None, qty=quantity, + party=party, ) if not price: return 0 diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index 87c5e6d588..ac0dd5123a 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -8,7 +8,7 @@ import frappe from frappe import _ from frappe.contacts.doctype.address.address import get_default_address from frappe.model.document import Document -from frappe.utils import cint, cstr +from frappe.utils import cstr from frappe.utils.nestedset import get_root_of from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups @@ -34,7 +34,6 @@ class TaxRule(Document): self.validate_tax_template() self.validate_from_to_dates("from_date", "to_date") self.validate_filters() - self.validate_use_for_shopping_cart() def validate_tax_template(self): if self.tax_type == "Sales": @@ -106,21 +105,6 @@ class TaxRule(Document): if tax_rule[0].priority == self.priority: frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule) - def validate_use_for_shopping_cart(self): - """If shopping cart is enabled and no tax rule exists for shopping cart, enable this one""" - if ( - not self.use_for_shopping_cart - and cint(frappe.db.get_single_value("E Commerce Settings", "enabled")) - and not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1, "name": ["!=", self.name]}) - ): - - self.use_for_shopping_cart = 1 - frappe.msgprint( - _( - "Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart" - ) - ) - @frappe.whitelist() def get_party_details(party, party_type, args=None): diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index e68ee909d9..c8785a5a72 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -9,6 +9,8 @@ import frappe from frappe import _ from frappe.utils import cstr, flt +from erpnext.utilities.product import get_item_codes_by_attributes + class ItemVariantExistsError(frappe.ValidationError): pass @@ -24,7 +26,8 @@ class ItemTemplateCannotHaveStock(frappe.ValidationError): @frappe.whitelist() def get_variant(template, args=None, variant=None, manufacturer=None, manufacturer_part_no=None): - """Validates Attributes and their Values, then looks for an exactly + """ + Validates Attributes and their Values, then looks for an exactly matching Item Variant :param item: Template Item @@ -34,13 +37,14 @@ def get_variant(template, args=None, variant=None, manufacturer=None, manufactur if item_template.variant_based_on == "Manufacturer" and manufacturer: return make_variant_based_on_manufacturer(item_template, manufacturer, manufacturer_part_no) - else: - if isinstance(args, str): - args = json.loads(args) - if not args: - frappe.throw(_("Please specify at least one attribute in the Attributes table")) - return find_variant(template, args, variant) + if isinstance(args, str): + args = json.loads(args) + + if not args: + frappe.throw(_("Please specify at least one attribute in the Attributes table")) + + return find_variant(template, args, variant) def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no): @@ -157,17 +161,6 @@ def get_attribute_values(item): def find_variant(template, args, variant_item_code=None): - conditions = [ - """(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})""".format( - frappe.db.escape(key), frappe.db.escape(cstr(value)) - ) - for key, value in args.items() - ] - - conditions = " or ".join(conditions) - - from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes - possible_variants = [ i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code ] diff --git a/erpnext/e_commerce/__init__.py b/erpnext/e_commerce/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py deleted file mode 100644 index bfada0faa7..0000000000 --- a/erpnext/e_commerce/api.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import json - -import frappe -from frappe.utils import cint - -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder -from erpnext.e_commerce.product_data_engine.query import ProductQuery -from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website - - -@frappe.whitelist(allow_guest=True) -def get_product_filter_data(query_args=None): - """ - Returns filtered products and discount filters. - :param query_args (dict): contains filters to get products list - - Query Args filters: - search (str): Search Term. - field_filters (dict): Keys include item_group, brand, etc. - attribute_filters(dict): Keys include Color, Size, etc. - start (int): Offset items by - item_group (str): Valid Item Group - from_filters (bool): Set as True to jump to page 1 - """ - if isinstance(query_args, str): - query_args = json.loads(query_args) - - query_args = frappe._dict(query_args) - if query_args: - search = query_args.get("search") - field_filters = query_args.get("field_filters", {}) - attribute_filters = query_args.get("attribute_filters", {}) - start = cint(query_args.start) if query_args.get("start") else 0 - item_group = query_args.get("item_group") - from_filters = query_args.get("from_filters") - else: - search, attribute_filters, item_group, from_filters = None, None, None, None - field_filters = {} - start = 0 - - # if new filter is checked, reset start to show filtered items from page 1 - if from_filters: - start = 0 - - sub_categories = [] - if item_group: - sub_categories = get_child_groups_for_website(item_group, immediate=True) - - engine = ProductQuery() - try: - result = engine.query( - attribute_filters, field_filters, search_term=search, start=start, item_group=item_group - ) - except Exception: - frappe.log_error("Product query with filter failed") - return {"exc": "Something went wrong!"} - - # discount filter data - filters = {} - discounts = result["discounts"] - - if discounts: - filter_engine = ProductFiltersBuilder() - filters["discount_filters"] = filter_engine.get_discount_filters(discounts) - - return { - "items": result["items"] or [], - "filters": filters, - "settings": engine.settings, - "sub_categories": sub_categories, - "items_count": result["items_count"], - } - - -@frappe.whitelist(allow_guest=True) -def get_guest_redirect_on_action(): - return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action") diff --git a/erpnext/e_commerce/doctype/__init__.py b/erpnext/e_commerce/doctype/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py b/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js deleted file mode 100644 index c37fa2f6ea..0000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on("E Commerce Settings", { - onload: function(frm) { - if(frm.doc.__onload && frm.doc.__onload.quotation_series) { - frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series; - frm.refresh_field("quotation_series"); - } - - frm.set_query('payment_gateway_account', function() { - return { 'filters': { 'payment_channel': "Email" } }; - }); - }, - refresh: function(frm) { - if (frm.doc.enabled) { - frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html( - `
${__("Follow these steps to create a landing page for your store")}: - - docs/store-landing-page - -
` - ); - } - - frappe.model.with_doctype("Website Item", () => { - const web_item_meta = frappe.get_meta('Website Item'); - - const valid_fields = web_item_meta.fields.filter(df => - ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden - ).map(df => - ({ label: df.label, value: df.fieldname }) - ); - - frm.get_field("filter_fields").grid.update_docfield_property( - 'fieldname', 'options', valid_fields - ); - }); - }, - enabled: function(frm) { - if (frm.doc.enabled === 1) { - frm.set_value('enable_variants', 1); - } - else { - frm.set_value('company', ''); - frm.set_value('price_list', ''); - frm.set_value('default_customer_group', ''); - frm.set_value('quotation_series', ''); - } - }, - - enable_checkout: function(frm) { - if (frm.doc.enable_checkout) { - erpnext.utils.check_payments_app(); - } - } -}); diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json deleted file mode 100644 index e6f08f708a..0000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "actions": [], - "creation": "2021-02-10 17:13:39.139103", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "products_per_page", - "filter_categories_section", - "enable_field_filters", - "filter_fields", - "enable_attribute_filters", - "filter_attributes", - "display_settings_section", - "hide_variants", - "enable_variants", - "show_price", - "column_break_9", - "show_stock_availability", - "show_quantity_in_website", - "allow_items_not_in_stock", - "column_break_13", - "show_apply_coupon_code_in_website", - "show_contact_us_button", - "show_attachments", - "section_break_18", - "company", - "price_list", - "enabled", - "store_page_docs", - "column_break_21", - "default_customer_group", - "quotation_series", - "checkout_settings_section", - "enable_checkout", - "show_price_in_quotation", - "column_break_27", - "save_quotations_as_draft", - "payment_gateway_account", - "payment_success_url", - "add_ons_section", - "enable_wishlist", - "column_break_22", - "enable_reviews", - "column_break_23", - "enable_recommendations", - "item_search_settings_section", - "redisearch_warning", - "search_index_fields", - "is_redisearch_enabled", - "is_redisearch_loaded", - "shop_by_category_section", - "slideshow", - "guest_display_settings_section", - "hide_price_for_guest", - "redirect_on_action" - ], - "fields": [ - { - "default": "6", - "fieldname": "products_per_page", - "fieldtype": "Int", - "label": "Products per Page" - }, - { - "collapsible": 1, - "fieldname": "filter_categories_section", - "fieldtype": "Section Break", - "label": "Filters and Categories" - }, - { - "default": "0", - "fieldname": "hide_variants", - "fieldtype": "Check", - "label": "Hide Variants" - }, - { - "default": "0", - "description": "The field filters will also work as categories in the Shop by Category page.", - "fieldname": "enable_field_filters", - "fieldtype": "Check", - "label": "Enable Field Filters (Categories)" - }, - { - "default": "0", - "fieldname": "enable_attribute_filters", - "fieldtype": "Check", - "label": "Enable Attribute Filters" - }, - { - "depends_on": "enable_field_filters", - "fieldname": "filter_fields", - "fieldtype": "Table", - "label": "Website Item Fields", - "options": "Website Filter Field" - }, - { - "depends_on": "enable_attribute_filters", - "fieldname": "filter_attributes", - "fieldtype": "Table", - "label": "Attributes", - "options": "Website Attribute" - }, - { - "default": "0", - "fieldname": "enabled", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Enable Shopping Cart" - }, - { - "depends_on": "doc.enabled", - "fieldname": "store_page_docs", - "fieldtype": "HTML" - }, - { - "fieldname": "display_settings_section", - "fieldtype": "Section Break", - "label": "Display Settings" - }, - { - "default": "0", - "fieldname": "show_attachments", - "fieldtype": "Check", - "label": "Show Public Attachments" - }, - { - "default": "0", - "fieldname": "show_price", - "fieldtype": "Check", - "label": "Show Price" - }, - { - "default": "0", - "fieldname": "show_stock_availability", - "fieldtype": "Check", - "label": "Show Stock Availability" - }, - { - "default": "0", - "fieldname": "enable_variants", - "fieldtype": "Check", - "label": "Enable Variant Selection" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "show_contact_us_button", - "fieldtype": "Check", - "label": "Show Contact Us Button" - }, - { - "default": "0", - "depends_on": "show_stock_availability", - "fieldname": "show_quantity_in_website", - "fieldtype": "Check", - "label": "Show Stock Quantity" - }, - { - "default": "0", - "fieldname": "show_apply_coupon_code_in_website", - "fieldtype": "Check", - "label": "Show Apply Coupon Code" - }, - { - "default": "0", - "fieldname": "allow_items_not_in_stock", - "fieldtype": "Check", - "label": "Allow items not in stock to be added to cart" - }, - { - "fieldname": "section_break_18", - "fieldtype": "Section Break", - "label": "Shopping Cart" - }, - { - "depends_on": "enabled", - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "mandatory_depends_on": "eval: doc.enabled === 1", - "options": "Company", - "remember_last_selected_value": 1 - }, - { - "depends_on": "enabled", - "description": "Prices will not be shown if Price List is not set", - "fieldname": "price_list", - "fieldtype": "Link", - "label": "Price List", - "mandatory_depends_on": "eval: doc.enabled === 1", - "options": "Price List" - }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break" - }, - { - "depends_on": "enabled", - "fieldname": "default_customer_group", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Customer Group", - "mandatory_depends_on": "eval: doc.enabled === 1", - "options": "Customer Group" - }, - { - "depends_on": "enabled", - "fieldname": "quotation_series", - "fieldtype": "Select", - "label": "Quotation Series", - "mandatory_depends_on": "eval: doc.enabled === 1" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:doc.enable_checkout", - "depends_on": "enabled", - "fieldname": "checkout_settings_section", - "fieldtype": "Section Break", - "label": "Checkout Settings" - }, - { - "default": "0", - "fieldname": "enable_checkout", - "fieldtype": "Check", - "label": "Enable Checkout" - }, - { - "default": "Orders", - "depends_on": "enable_checkout", - "description": "After payment completion redirect user to selected page.", - "fieldname": "payment_success_url", - "fieldtype": "Select", - "label": "Payment Success Url", - "mandatory_depends_on": "enable_checkout", - "options": "\nOrders\nInvoices\nMy Account" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval: doc.enable_checkout == 0", - "fieldname": "save_quotations_as_draft", - "fieldtype": "Check", - "label": "Save Quotations as Draft" - }, - { - "depends_on": "enable_checkout", - "fieldname": "payment_gateway_account", - "fieldtype": "Link", - "label": "Payment Gateway Account", - "mandatory_depends_on": "enable_checkout", - "options": "Payment Gateway Account" - }, - { - "collapsible": 1, - "depends_on": "enable_field_filters", - "fieldname": "shop_by_category_section", - "fieldtype": "Section Break", - "label": "Shop by Category" - }, - { - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Slideshow", - "options": "Website Slideshow" - }, - { - "collapsible": 1, - "fieldname": "add_ons_section", - "fieldtype": "Section Break", - "label": "Add-ons" - }, - { - "default": "0", - "fieldname": "enable_wishlist", - "fieldtype": "Check", - "label": "Enable Wishlist" - }, - { - "default": "0", - "fieldname": "enable_reviews", - "fieldtype": "Check", - "label": "Enable Reviews and Ratings" - }, - { - "fieldname": "search_index_fields", - "fieldtype": "Small Text", - "label": "Search Index Fields", - "mandatory_depends_on": "is_redisearch_enabled", - "read_only_depends_on": "eval:!doc.is_redisearch_loaded" - }, - { - "collapsible": 1, - "fieldname": "item_search_settings_section", - "fieldtype": "Section Break", - "label": "Item Search Settings" - }, - { - "default": "0", - "fieldname": "is_redisearch_loaded", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Redisearch Loaded" - }, - { - "depends_on": "eval:!doc.is_redisearch_loaded", - "fieldname": "redisearch_warning", - "fieldtype": "HTML", - "label": "Redisearch Warning", - "options": "

Redisearch is not loaded. If you want to use the advanced product search feature, refer here.

" - }, - { - "default": "0", - "depends_on": "eval:doc.show_price", - "fieldname": "hide_price_for_guest", - "fieldtype": "Check", - "label": "Hide Price for Guest" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "collapsible": 1, - "fieldname": "guest_display_settings_section", - "fieldtype": "Section Break", - "label": "Guest Display Settings" - }, - { - "description": "Link to redirect Guest on actions that need login such as add to cart, wishlist, etc. E.g.: /login", - "fieldname": "redirect_on_action", - "fieldtype": "Data", - "label": "Redirect on Action" - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_23", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "enable_recommendations", - "fieldtype": "Check", - "label": "Enable Recommendations" - }, - { - "default": "0", - "depends_on": "eval: doc.enable_checkout == 0", - "fieldname": "show_price_in_quotation", - "fieldtype": "Check", - "label": "Show Price in Quotation" - }, - { - "default": "0", - "fieldname": "is_redisearch_enabled", - "fieldtype": "Check", - "label": "Enable Redisearch", - "read_only_depends_on": "eval:!doc.is_redisearch_loaded" - } - ], - "index_web_pages_for_search": 1, - "issingle": 1, - "links": [], - "modified": "2022-04-01 18:35:56.106756", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "E Commerce Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py deleted file mode 100644 index c27d29a62c..0000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import comma_and, flt, unique - -from erpnext.e_commerce.redisearch_utils import ( - create_website_items_index, - define_autocomplete_dictionary, - get_indexable_web_fields, - is_search_module_loaded, -) - - -class ShoppingCartSetupError(frappe.ValidationError): - pass - - -class ECommerceSettings(Document): - def onload(self): - self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") - - # flag >> if redisearch is installed and loaded - self.is_redisearch_loaded = is_search_module_loaded() - - def validate(self): - self.validate_field_filters(self.filter_fields, self.enable_field_filters) - self.validate_attribute_filters() - self.validate_checkout() - self.validate_search_index_fields() - - if self.enabled: - self.validate_price_list_exchange_rate() - - frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings") - - self.is_redisearch_enabled_pre_save = frappe.db.get_single_value( - "E Commerce Settings", "is_redisearch_enabled" - ) - - def after_save(self): - self.create_redisearch_indexes() - - def create_redisearch_indexes(self): - # if redisearch is enabled (value changed) create indexes and dictionary - value_changed = self.is_redisearch_enabled != self.is_redisearch_enabled_pre_save - if self.is_redisearch_loaded and self.is_redisearch_enabled and value_changed: - define_autocomplete_dictionary() - create_website_items_index() - - @staticmethod - def validate_field_filters(filter_fields, enable_field_filters): - if not (enable_field_filters and filter_fields): - return - - web_item_meta = frappe.get_meta("Website Item") - valid_fields = [ - df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] - ] - - for row in filter_fields: - if row.fieldname not in valid_fields: - frappe.throw( - _( - "Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'" - ).format(row.idx, frappe.bold(row.fieldname)) - ) - - def validate_attribute_filters(self): - if not (self.enable_attribute_filters and self.filter_attributes): - return - - # if attribute filters are enabled, hide_variants should be disabled - self.hide_variants = 0 - - def validate_checkout(self): - if self.enable_checkout and not self.payment_gateway_account: - self.enable_checkout = 0 - - def validate_search_index_fields(self): - if not self.search_index_fields: - return - - fields = self.search_index_fields.replace(" ", "") - fields = unique(fields.strip(",").split(",")) # Remove extra ',' and remove duplicates - - # All fields should be indexable - allowed_indexable_fields = get_indexable_web_fields() - - if not (set(fields).issubset(allowed_indexable_fields)): - invalid_fields = list(set(fields).difference(allowed_indexable_fields)) - num_invalid_fields = len(invalid_fields) - invalid_fields = comma_and(invalid_fields) - - if num_invalid_fields > 1: - frappe.throw( - _("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields)) - ) - else: - frappe.throw( - _("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields)) - ) - - self.search_index_fields = ",".join(fields) - - def validate_price_list_exchange_rate(self): - "Check if exchange rate exists for Price List currency (to Company's currency)." - from erpnext.setup.utils import get_exchange_rate - - if not self.enabled or not self.company or not self.price_list: - return # this function is also called from hooks, check values again - - company_currency = frappe.get_cached_value("Company", self.company, "default_currency") - price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency") - - if not company_currency: - msg = f"Please specify currency in Company {self.company}" - frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - - if not price_list_currency: - msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}" - frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - - if price_list_currency != company_currency: - from_currency, to_currency = price_list_currency, company_currency - - # Get exchange rate checks Currency Exchange Records too - exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling") - - if not flt(exchange_rate): - msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}" - frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError) - - def validate_tax_rule(self): - if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"): - frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError) - - def get_tax_master(self, billing_territory): - tax_master = self.get_name_from_territory( - billing_territory, "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master" - ) - return tax_master and tax_master[0] or None - - def get_shipping_rules(self, shipping_territory): - return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") - - def on_change(self): - old_doc = self.get_doc_before_save() - - if old_doc: - old_fields = old_doc.search_index_fields - new_fields = self.search_index_fields - - # if search index fields get changed - if not (new_fields == old_fields): - create_website_items_index() - - -def validate_cart_settings(doc=None, method=None): - frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate") - - -def get_shopping_cart_settings(): - return frappe.get_cached_doc("E Commerce Settings") - - -@frappe.whitelist(allow_guest=True) -def is_cart_enabled(): - return get_shopping_cart_settings().enabled - - -def show_quantity_in_website(): - return get_shopping_cart_settings().show_quantity_in_website - - -def check_shopping_cart_enabled(): - if not get_shopping_cart_settings().enabled: - frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError) - - -def show_attachments(): - return get_shopping_cart_settings().show_attachments diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py deleted file mode 100644 index 662db4d7ae..0000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -import unittest - -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - ShoppingCartSetupError, -) - - -class TestECommerceSettings(unittest.TestCase): - def tearDown(self): - frappe.db.rollback() - - def test_tax_rule_validation(self): - frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") - frappe.db.commit() # nosemgrep - - cart_settings = frappe.get_doc("E Commerce Settings") - cart_settings.enabled = 1 - if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"): - self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule) - - frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1") - - def test_invalid_filter_fields(self): - "Check if Item fields are blocked in E Commerce Settings filter fields." - from frappe.custom.doctype.custom_field.custom_field import create_custom_field - - setup_e_commerce_settings({"enable_field_filters": 1}) - - create_custom_field( - "Item", - dict(owner="Administrator", fieldname="test_data", label="Test", fieldtype="Data"), - ) - settings = frappe.get_doc("E Commerce Settings") - settings.append("filter_fields", {"fieldname": "test_data"}) - - self.assertRaises(frappe.ValidationError, settings.save) - - -def setup_e_commerce_settings(values_dict): - "Accepts a dict of values that updates E Commerce Settings." - if not values_dict: - return - - doc = frappe.get_doc("E Commerce Settings", "E Commerce Settings") - doc.update(values_dict) - doc.save() - - -test_dependencies = ["Tax Rule"] diff --git a/erpnext/e_commerce/doctype/item_review/__init__.py b/erpnext/e_commerce/doctype/item_review/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/item_review/item_review.js b/erpnext/e_commerce/doctype/item_review/item_review.js deleted file mode 100644 index a57c370287..0000000000 --- a/erpnext/e_commerce/doctype/item_review/item_review.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Item Review', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/e_commerce/doctype/item_review/item_review.json b/erpnext/e_commerce/doctype/item_review/item_review.json deleted file mode 100644 index 57f719fc3c..0000000000 --- a/erpnext/e_commerce/doctype/item_review/item_review.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "actions": [], - "beta": 1, - "creation": "2021-03-23 16:47:26.542226", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "website_item", - "user", - "customer", - "column_break_3", - "item", - "published_on", - "reviews_section", - "review_title", - "rating", - "comment" - ], - "fields": [ - { - "fieldname": "website_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User", - "read_only": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fetch_from": "website_item.item_code", - "fieldname": "item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item", - "options": "Item", - "read_only": 1 - }, - { - "fieldname": "reviews_section", - "fieldtype": "Section Break", - "label": "Reviews" - }, - { - "fieldname": "rating", - "fieldtype": "Rating", - "in_list_view": 1, - "label": "Rating", - "read_only": 1 - }, - { - "fieldname": "comment", - "fieldtype": "Small Text", - "label": "Comment", - "read_only": 1 - }, - { - "fieldname": "review_title", - "fieldtype": "Data", - "label": "Review Title", - "read_only": 1 - }, - { - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "options": "Customer", - "read_only": 1 - }, - { - "fieldname": "published_on", - "fieldtype": "Data", - "label": "Published on", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-08-10 12:08:58.119691", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Item Review", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Website Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "report": 1, - "role": "Customer", - "share": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/item_review/item_review.py b/erpnext/e_commerce/doctype/item_review/item_review.py deleted file mode 100644 index 3e540e3885..0000000000 --- a/erpnext/e_commerce/doctype/item_review/item_review.py +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from datetime import datetime - -import frappe -from frappe import _ -from frappe.contacts.doctype.contact.contact import get_contact_name -from frappe.model.document import Document -from frappe.utils import cint, flt - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) - - -class UnverifiedReviewer(frappe.ValidationError): - pass - - -class ItemReview(Document): - def after_insert(self): - # regenerate cache on review creation - reviews_dict = get_queried_reviews(self.website_item) - set_reviews_in_cache(self.website_item, reviews_dict) - - def after_delete(self): - # regenerate cache on review deletion - reviews_dict = get_queried_reviews(self.website_item) - set_reviews_in_cache(self.website_item, reviews_dict) - - -@frappe.whitelist() -def get_item_reviews(web_item, start=0, end=10, data=None): - "Get Website Item Review Data." - start, end = cint(start), cint(end) - settings = get_shopping_cart_settings() - - # Get cached reviews for first page (start=0) - # avoid cache when page is different - from_cache = not bool(start) - - if not data: - data = frappe._dict() - - if settings and settings.get("enable_reviews"): - reviews_cache = frappe.cache().hget("item_reviews", web_item) - if from_cache and reviews_cache: - data = reviews_cache - else: - data = get_queried_reviews(web_item, start, end, data) - if from_cache: - set_reviews_in_cache(web_item, data) - - return data - - -def get_queried_reviews(web_item, start=0, end=10, data=None): - """ - Query Website Item wise reviews and cache if needed. - Cache stores only first page of reviews i.e. 10 reviews maximum. - Returns: - dict: Containing reviews, average ratings, % of reviews per rating and total reviews. - """ - if not data: - data = frappe._dict() - - data.reviews = frappe.db.get_all( - "Item Review", - filters={"website_item": web_item}, - fields=["*"], - limit_start=start, - limit_page_length=end, - ) - - rating_data = frappe.db.get_all( - "Item Review", - filters={"website_item": web_item}, - fields=["avg(rating) as average, count(*) as total"], - )[0] - - data.average_rating = flt(rating_data.average, 1) - data.average_whole_rating = flt(data.average_rating, 0) - - # get % of reviews per rating - reviews_per_rating = [] - for i in range(1, 6): - count = frappe.db.get_all( - "Item Review", filters={"website_item": web_item, "rating": i}, fields=["count(*) as count"] - )[0].count - - percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0 - reviews_per_rating.append(percent) - - data.reviews_per_rating = reviews_per_rating - data.total_reviews = rating_data.total - - return data - - -def set_reviews_in_cache(web_item, reviews_dict): - frappe.cache().hset("item_reviews", web_item, reviews_dict) - - -@frappe.whitelist() -def add_item_review(web_item, title, rating, comment=None): - """Add an Item Review by a user if non-existent.""" - if frappe.session.user == "Guest": - # guest user should not reach here ideally in the case they do via an API, throw error - frappe.throw(_("You are not verified to write a review yet."), exc=UnverifiedReviewer) - - if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}): - doc = frappe.get_doc( - { - "doctype": "Item Review", - "user": frappe.session.user, - "customer": get_customer(), - "website_item": web_item, - "item": frappe.db.get_value("Website Item", web_item, "item_code"), - "review_title": title, - "rating": rating, - "comment": comment, - } - ) - doc.published_on = datetime.today().strftime("%d %B %Y") - doc.insert() - - -def get_customer(silent=False): - """ - silent: Return customer if exists else return nothing. Dont throw error. - """ - user = frappe.session.user - contact_name = get_contact_name(user) - customer = None - - if contact_name: - contact = frappe.get_doc("Contact", contact_name) - for link in contact.links: - if link.link_doctype == "Customer": - customer = link.link_name - break - - if customer: - return frappe.db.get_value("Customer", customer) - elif silent: - return None - else: - # should not reach here unless via an API - frappe.throw( - _("You are not a verified customer yet. Please contact us to proceed."), exc=UnverifiedReviewer - ) diff --git a/erpnext/e_commerce/doctype/item_review/test_item_review.py b/erpnext/e_commerce/doctype/item_review/test_item_review.py deleted file mode 100644 index 8a4befc800..0000000000 --- a/erpnext/e_commerce/doctype/item_review/test_item_review.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -import unittest - -import frappe -from frappe.core.doctype.user_permission.test_user_permission import create_user - -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.item_review.item_review import ( - UnverifiedReviewer, - add_item_review, - get_item_reviews, -) -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.shopping_cart.cart import get_party -from erpnext.stock.doctype.item.test_item import make_item - - -class TestItemReview(unittest.TestCase): - def setUp(self): - item = make_item("Test Mobile Phone") - if not frappe.db.exists("Website Item", {"item_code": "Test Mobile Phone"}): - make_website_item(item, save=True) - - setup_e_commerce_settings({"enable_reviews": 1}) - frappe.local.shopping_cart_settings = None - - def tearDown(self): - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - setup_e_commerce_settings({"enable_reviews": 0}) - - def test_add_and_get_item_reviews_from_customer(self): - "Add / Get Reviews from a User that is a valid customer (has added to cart or purchased in the past)" - # create user - web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"}) - test_user = create_user("test_reviewer@example.com", "Customer") - frappe.set_user(test_user.name) - - # create customer and contact against user - customer = get_party() - - # post review on "Test Mobile Phone" - try: - add_item_review(web_item, "Great Product", 3, "Would recommend this product") - review_name = frappe.db.get_value("Item Review", {"website_item": web_item}) - except Exception: - self.fail(f"Error while publishing review for {web_item}") - - review_data = get_item_reviews(web_item, 0, 10) - - self.assertEqual(len(review_data.reviews), 1) - self.assertEqual(review_data.average_rating, 3) - self.assertEqual(review_data.reviews_per_rating[2], 100) - - # tear down - frappe.set_user("Administrator") - frappe.delete_doc("Item Review", review_name) - customer.delete() - - def test_add_item_review_from_non_customer(self): - "Check if logged in user (who is not a customer yet) is blocked from posting reviews." - web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"}) - test_user = create_user("test_reviewer@example.com", "Customer") - frappe.set_user(test_user.name) - - with self.assertRaises(UnverifiedReviewer): - add_item_review(web_item, "Great Product", 3, "Would recommend this product") - - # tear down - frappe.set_user("Administrator") - - def test_add_item_reviews_from_guest_user(self): - "Check if Guest user is blocked from posting reviews." - web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"}) - frappe.set_user("Guest") - - with self.assertRaises(UnverifiedReviewer): - add_item_review(web_item, "Great Product", 3, "Would recommend this product") - - # tear down - frappe.set_user("Administrator") diff --git a/erpnext/e_commerce/doctype/recommended_items/__init__.py b/erpnext/e_commerce/doctype/recommended_items/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json deleted file mode 100644 index 1821532323..0000000000 --- a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "actions": [], - "creation": "2021-07-12 20:52:12.503470", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "website_item", - "website_item_name", - "column_break_2", - "item_code", - "more_information_section", - "route", - "column_break_6", - "website_item_image", - "website_item_thumbnail" - ], - "fields": [ - { - "fieldname": "website_item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Website Item", - "options": "Website Item" - }, - { - "fetch_from": "website_item.web_item_name", - "fieldname": "website_item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Website Item Name", - "read_only": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fieldname": "more_information_section", - "fieldtype": "Section Break", - "label": "More Information" - }, - { - "fetch_from": "website_item.route", - "fieldname": "route", - "fieldtype": "Small Text", - "label": "Route", - "read_only": 1 - }, - { - "fetch_from": "website_item.website_image", - "fieldname": "website_item_image", - "fieldtype": "Attach", - "label": "Website Item Image", - "read_only": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fetch_from": "website_item.thumbnail", - "fieldname": "website_item_thumbnail", - "fieldtype": "Data", - "label": "Website Item Thumbnail", - "read_only": 1 - }, - { - "fetch_from": "website_item.item_code", - "fieldname": "item_code", - "fieldtype": "Data", - "label": "Item Code" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2022-06-28 16:44:24.718728", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Recommended Items", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.py b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py deleted file mode 100644 index 16b6e52047..0000000000 --- a/erpnext/e_commerce/doctype/recommended_items/recommended_items.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class RecommendedItems(Document): - pass diff --git a/erpnext/e_commerce/doctype/website_item/__init__.py b/erpnext/e_commerce/doctype/website_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item.html b/erpnext/e_commerce/doctype/website_item/templates/website_item.html deleted file mode 100644 index db123090aa..0000000000 --- a/erpnext/e_commerce/doctype/website_item/templates/website_item.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "templates/web.html" %} - -{% block page_content %} -

{{ title }}

-{% endblock %} - - \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html b/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html deleted file mode 100644 index d7014b453a..0000000000 --- a/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py deleted file mode 100644 index 2ba84c0500..0000000000 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ /dev/null @@ -1,564 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - -from erpnext.controllers.item_variant import create_variant -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website -from erpnext.stock.doctype.item.item import DataValidationError -from erpnext.stock.doctype.item.test_item import make_item - -WEBITEM_DESK_TESTS = ("test_website_item_desk_item_sync", "test_publish_variant_and_template") -WEBITEM_PRICE_TESTS = ( - "test_website_item_price_for_logged_in_user", - "test_website_item_price_for_guest_user", -) - - -class TestWebsiteItem(unittest.TestCase): - @classmethod - def setUpClass(cls): - setup_e_commerce_settings( - { - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - } - ) - - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - - def setUp(self): - if self._testMethodName in WEBITEM_DESK_TESTS: - make_item( - "Test Web Item", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}], - }, - ) - elif self._testMethodName in WEBITEM_PRICE_TESTS: - create_user_and_customer_if_not_exists( - "test_contact_customer@example.com", "_Test Contact For _Test Customer" - ) - create_regular_web_item() - make_web_item_price(item_code="Test Mobile Phone") - - # Note: When testing web item pricing rule logged-in user pricing rule must differ from guest pricing rule or test will falsely pass. - # This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor, - # when testing for logged-in user the test will get the previous pricing rule because "selling" is still true. - # - # I've attempted to mitigate this by setting applicable_for=Customer, and customer=Guest however, this only results in PermissionError failing the test. - make_web_pricing_rule( - title="Test Pricing Rule for Test Mobile Phone", item_code="Test Mobile Phone", selling=1 - ) - make_web_pricing_rule( - title="Test Pricing Rule for Test Mobile Phone (Customer)", - item_code="Test Mobile Phone", - selling=1, - discount_percentage="25", - applicable_for="Customer", - customer="_Test Customer", - ) - - def test_index_creation(self): - "Check if index is getting created in db." - from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update - - on_doctype_update() - - indices = frappe.db.sql("show index from `tabWebsite Item`", as_dict=1) - expected_columns = {"route", "item_group", "brand"} - for index in indices: - expected_columns.discard(index.get("Column_name")) - - if expected_columns: - self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") - - def test_website_item_desk_item_sync(self): - "Check creation/updation/deletion of Website Item and its impact on Item master." - web_item = None - item = make_item("Test Web Item") # will return item if exists - try: - web_item = make_website_item(item, save=False) - web_item.save() - except Exception: - self.fail(f"Error while creating website item for {item}") - - # check if website item was created - self.assertTrue(bool(web_item)) - self.assertTrue(bool(web_item.route)) - - item.reload() - self.assertEqual(web_item.published, 1) - self.assertEqual(item.published_in_website, 1) # check if item was back updated - self.assertEqual(web_item.item_group, item.item_group) - - # check if changing item data changes it in website item - item.item_name = "Test Web Item 1" - item.stock_uom = "Unit" - item.save() - web_item.reload() - self.assertEqual(web_item.item_name, item.item_name) - self.assertEqual(web_item.stock_uom, item.stock_uom) - - # check if disabling item unpublished website item - item.disabled = 1 - item.save() - web_item.reload() - self.assertEqual(web_item.published, 0) - - # check if website item deletion, unpublishes desk item - web_item.delete() - item.reload() - self.assertEqual(item.published_in_website, 0) - - item.delete() - - def test_publish_variant_and_template(self): - "Check if template is published on publishing variant." - # template "Test Web Item" created on setUp - variant = create_variant("Test Web Item", {"Test Size": "Large"}) - variant.save() - - # check if template is not published - self.assertIsNone(frappe.db.exists("Website Item", {"item_code": variant.variant_of})) - - variant_web_item = make_website_item(variant, save=False) - variant_web_item.save() - - # check if template is published - try: - template_web_item = frappe.get_doc("Website Item", {"item_code": variant.variant_of}) - except frappe.DoesNotExistError: - self.fail(f"Template of {variant.item_code}, {variant.variant_of} not published") - - # teardown - variant_web_item.delete() - template_web_item.delete() - variant.delete() - - def test_impact_on_merging_items(self): - "Check if merging items is blocked if old and new items both have website items" - first_item = make_item("Test First Item") - second_item = make_item("Test Second Item") - - first_web_item = make_website_item(first_item, save=False) - first_web_item.save() - second_web_item = make_website_item(second_item, save=False) - second_web_item.save() - - with self.assertRaises(DataValidationError): - frappe.rename_doc("Item", "Test First Item", "Test Second Item", merge=True) - - # tear down - second_web_item.delete() - first_web_item.delete() - second_item.delete() - first_item.delete() - - # Website Item Portal Tests Begin - - def test_website_item_breadcrumbs(self): - """ - Check if breadcrumbs include homepage, product listing navigation page, - parent item group(s) and item group - """ - from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups - - item_code = "Test Breadcrumb Item" - item = make_item( - item_code, - { - "item_group": "_Test Item Group B - 1", - }, - ) - - if not frappe.db.exists("Website Item", {"item_code": item_code}): - web_item = make_website_item(item, save=False) - web_item.save() - else: - web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code}) - - frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1) - frappe.db.set_value("Item Group", "_Test Item Group B", "show_in_website", 1) - - breadcrumbs = get_parent_item_groups(item.item_group) - - settings = frappe.get_cached_doc("E Commerce Settings") - if settings.enable_field_filters: - base_breadcrumb = "Shop by Category" - else: - base_breadcrumb = "All Products" - - self.assertEqual(breadcrumbs[0]["name"], "Home") - self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb) - self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group - self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1") - - # tear down - web_item.delete() - item.delete() - - def test_website_item_price_for_logged_in_user(self): - "Check if price details are fetched correctly while logged in." - item_code = "Test Mobile Phone" - - # show price in e commerce settings - setup_e_commerce_settings({"show_price": 1}) - - # price and pricing rule added via setUp - - # login as customer with pricing rule - frappe.set_user("test_contact_customer@example.com") - - # check if price and slashed price is fetched correctly - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertTrue(bool(data.product_info["price"])) - - price_object = data.product_info["price"] - self.assertEqual(price_object.get("discount_percent"), 25.0) - self.assertEqual(price_object.get("price_list_rate"), 750) - self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00") - self.assertEqual(price_object.get("formatted_price"), "₹ 750.00") - self.assertEqual(price_object.get("formatted_discount_percent"), "25.0%") - - # switch to admin and disable show price - frappe.set_user("Administrator") - setup_e_commerce_settings({"show_price": 0}) - - # price should not be fetched for logged in user. - frappe.set_user("test_contact_customer@example.com") - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["price"])) - - # tear down - frappe.set_user("Administrator") - - def test_website_item_price_for_guest_user(self): - "Check if price details are fetched correctly for guest user." - item_code = "Test Mobile Phone" - - # show price for guest user in e commerce settings - setup_e_commerce_settings({"show_price": 1, "hide_price_for_guest": 0}) - - # price and pricing rule added via setUp - - # switch to guest user - frappe.set_user("Guest") - - # price should be fetched - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertTrue(bool(data.product_info["price"])) - - price_object = data.product_info["price"] - self.assertEqual(price_object.get("discount_percent"), 10) - self.assertEqual(price_object.get("price_list_rate"), 900) - - # hide price for guest user - frappe.set_user("Administrator") - setup_e_commerce_settings({"hide_price_for_guest": 1}) - frappe.set_user("Guest") - - # price should not be fetched - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["price"])) - - # tear down - frappe.set_user("Administrator") - - def test_website_item_stock_when_out_of_stock(self): - """ - Check if stock details are fetched correctly for empty inventory when: - 1) Showing stock availability enabled: - - Warehouse unset - - Warehouse set - 2) Showing stock availability disabled - """ - item_code = "Test Mobile Phone" - create_regular_web_item() - setup_e_commerce_settings({"show_stock_availability": 1}) - - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - - # check if stock details are fetched and item not in stock without warehouse set - self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(bool(data.product_info["stock_qty"])) - - # set warehouse - frappe.db.set_value( - "Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC" - ) - - # check if stock details are fetched and item not in stock with warehouse set - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"], 0) - - # disable show stock availability - setup_e_commerce_settings({"show_stock_availability": 0}) - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - - # check if stock detail attributes are not fetched if stock availability is hidden - self.assertIsNone(data.product_info.get("in_stock")) - self.assertIsNone(data.product_info.get("stock_qty")) - self.assertIsNone(data.product_info.get("show_stock_qty")) - - # tear down - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - - def test_website_item_stock_when_in_stock(self): - """ - Check if stock details are fetched correctly for available inventory when: - 1) Showing stock availability enabled: - - Warehouse set - - Warehouse unset - 2) Showing stock availability disabled - """ - from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - - item_code = "Test Mobile Phone" - create_regular_web_item() - setup_e_commerce_settings({"show_stock_availability": 1}) - frappe.local.shopping_cart_settings = None - - # set warehouse - frappe.db.set_value( - "Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC" - ) - - # stock up item - stock_entry = make_stock_entry( - item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100 - ) - - # check if stock details are fetched and item is in stock with warehouse set - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertTrue(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"], 2) - - # unset warehouse - frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "") - - # check if stock details are fetched and item not in stock without warehouse set - # (even though it has stock in some warehouse) - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(data.product_info["stock_qty"]) - - # disable show stock availability - setup_e_commerce_settings({"show_stock_availability": 0}) - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - - # check if stock detail attributes are not fetched if stock availability is hidden - self.assertIsNone(data.product_info.get("in_stock")) - self.assertIsNone(data.product_info.get("stock_qty")) - self.assertIsNone(data.product_info.get("show_stock_qty")) - - # tear down - stock_entry.cancel() - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - - def test_recommended_item(self): - "Check if added recommended items are fetched correctly." - item_code = "Test Mobile Phone" - web_item = create_regular_web_item(item_code) - - setup_e_commerce_settings({"enable_recommendations": 1, "show_price": 1}) - - # create recommended web item and price for it - recommended_web_item = create_regular_web_item("Test Mobile Phone 1") - make_web_item_price(item_code="Test Mobile Phone 1") - - # add recommended item to first web item - web_item.append("recommended_items", {"website_item": recommended_web_item.name}) - web_item.save() - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - # test results if show price is enabled - self.assertEqual(len(recommended_items), 1) - recomm_item = recommended_items[0] - self.assertEqual(recomm_item.get("website_item_name"), "Test Mobile Phone 1") - self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched - - price_info = recomm_item.get("price_info") - self.assertEqual(price_info.get("price_list_rate"), 1000) - self.assertEqual(price_info.get("formatted_price"), "₹ 1,000.00") - - # test results if show price is disabled - setup_e_commerce_settings({"show_price": 0}) - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - self.assertEqual(len(recommended_items), 1) - self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched - - # tear down - web_item.delete() - recommended_web_item.delete() - frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete() - - def test_recommended_item_for_guest_user(self): - "Check if added recommended items are fetched correctly for guest user." - item_code = "Test Mobile Phone" - web_item = create_regular_web_item(item_code) - - # price visible to guests - setup_e_commerce_settings( - {"enable_recommendations": 1, "show_price": 1, "hide_price_for_guest": 0} - ) - - # create recommended web item and price for it - recommended_web_item = create_regular_web_item("Test Mobile Phone 1") - make_web_item_price(item_code="Test Mobile Phone 1") - - # add recommended item to first web item - web_item.append("recommended_items", {"website_item": recommended_web_item.name}) - web_item.save() - - frappe.set_user("Guest") - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - # test results if show price is enabled - self.assertEqual(len(recommended_items), 1) - self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched - - # price hidden from guests - frappe.set_user("Administrator") - setup_e_commerce_settings({"hide_price_for_guest": 1}) - frappe.set_user("Guest") - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - # test results if show price is enabled - self.assertEqual(len(recommended_items), 1) - self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched - - # tear down - frappe.set_user("Administrator") - web_item.delete() - recommended_web_item.delete() - frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete() - - -def create_regular_web_item(item_code=None, item_args=None, web_args=None): - "Create Regular Item and Website Item." - item_code = item_code or "Test Mobile Phone" - item = make_item(item_code, properties=item_args) - - if not frappe.db.exists("Website Item", {"item_code": item_code}): - web_item = make_website_item(item, save=False) - if web_args: - web_item.update(web_args) - web_item.save() - else: - web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code}) - - return web_item - - -def make_web_item_price(**kwargs): - item_code = kwargs.get("item_code") - if not item_code: - return - - if not frappe.db.exists("Item Price", {"item_code": item_code}): - item_price = frappe.get_doc( - { - "doctype": "Item Price", - "item_code": item_code, - "price_list": kwargs.get("price_list") or "_Test Price List India", - "price_list_rate": kwargs.get("price_list_rate") or 1000, - } - ) - item_price.insert() - else: - item_price = frappe.get_cached_doc("Item Price", {"item_code": item_code}) - - return item_price - - -def make_web_pricing_rule(**kwargs): - title = kwargs.get("title") - if not title: - return - - if not frappe.db.exists("Pricing Rule", title): - pricing_rule = frappe.get_doc( - { - "doctype": "Pricing Rule", - "title": title, - "apply_on": kwargs.get("apply_on") or "Item Code", - "items": [{"item_code": kwargs.get("item_code")}], - "selling": kwargs.get("selling") or 0, - "buying": kwargs.get("buying") or 0, - "rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage", - "discount_percentage": kwargs.get("discount_percentage") or 10, - "company": kwargs.get("company") or "_Test Company", - "currency": kwargs.get("currency") or "INR", - "for_price_list": kwargs.get("price_list") or "_Test Price List India", - "applicable_for": kwargs.get("applicable_for") or "", - "customer": kwargs.get("customer") or "", - } - ) - pricing_rule.insert() - else: - pricing_rule = frappe.get_doc("Pricing Rule", {"title": title}) - - return pricing_rule - - -def create_user_and_customer_if_not_exists(email, first_name=None): - if frappe.db.exists("User", email): - return - - frappe.get_doc( - { - "doctype": "User", - "user_type": "Website User", - "email": email, - "send_welcome_email": 0, - "first_name": first_name or email.split("@")[0], - } - ).insert(ignore_permissions=True) - - contact = frappe.get_last_doc("Contact", filters={"email_id": email}) - link = contact.append("links", {}) - link.link_doctype = "Customer" - link.link_name = "_Test Customer" - link.link_title = "_Test Customer" - contact.save() - - -test_dependencies = ["Price List", "Item Price", "Customer", "Contact", "Item"] diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js deleted file mode 100644 index b6595cce8a..0000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Website Item', { - onload: (frm) => { - // should never check Private - frm.fields_dict["website_image"].df.is_private = 0; - }, - - refresh: (frm) => { - frm.add_custom_button(__("Prices"), function() { - frappe.set_route("List", "Item Price", {"item_code": frm.doc.item_code}); - }, __("View")); - - frm.add_custom_button(__("Stock"), function() { - frappe.route_options = { - "item_code": frm.doc.item_code - }; - frappe.set_route("query-report", "Stock Balance"); - }, __("View")); - - frm.add_custom_button(__("E Commerce Settings"), function() { - frappe.set_route("Form", "E Commerce Settings"); - }, __("View")); - }, - - copy_from_item_group: (frm) => { - return frm.call({ - doc: frm.doc, - method: "copy_specification_from_item_group" - }); - }, - - set_meta_tags: (frm) => { - frappe.utils.set_meta_tag(frm.doc.route); - } -}); diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json deleted file mode 100644 index 6f551a0b42..0000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ /dev/null @@ -1,414 +0,0 @@ -{ - "actions": [], - "allow_guest_to_view": 1, - "allow_import": 1, - "autoname": "naming_series", - "creation": "2021-02-09 21:06:14.441698", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "naming_series", - "web_item_name", - "route", - "has_variants", - "variant_of", - "published", - "column_break_3", - "item_code", - "item_name", - "item_group", - "stock_uom", - "column_break_11", - "description", - "brand", - "display_section", - "website_image", - "website_image_alt", - "column_break_13", - "slideshow", - "thumbnail", - "stock_information_section", - "website_warehouse", - "column_break_24", - "on_backorder", - "section_break_17", - "short_description", - "web_long_description", - "column_break_27", - "website_specifications", - "copy_from_item_group", - "display_additional_information_section", - "show_tabbed_section", - "tabs", - "recommended_items_section", - "recommended_items", - "offers_section", - "offers", - "section_break_6", - "ranking", - "set_meta_tags", - "column_break_22", - "website_item_groups", - "advanced_display_section", - "website_content" - ], - "fields": [ - { - "description": "Website display name", - "fetch_from": "item_code.item_name", - "fetch_if_empty": 1, - "fieldname": "web_item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Website Item Name", - "reqd": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "item_code", - "fieldtype": "Link", - "label": "Item Code", - "options": "Item", - "read_only_depends_on": "eval:!doc.__islocal", - "reqd": 1 - }, - { - "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "label": "Search and SEO" - }, - { - "fieldname": "route", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Route", - "no_copy": 1 - }, - { - "description": "Items with higher ranking will be shown higher", - "fieldname": "ranking", - "fieldtype": "Int", - "label": "Ranking" - }, - { - "description": "Show a slideshow at the top of the page", - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Slideshow", - "options": "Website Slideshow" - }, - { - "description": "Item Image (if not slideshow)", - "fieldname": "website_image", - "fieldtype": "Attach Image", - "hidden": 1, - "in_preview": 1, - "label": "Website Image", - "print_hide": 1 - }, - { - "description": "Image Alternative Text", - "fieldname": "website_image_alt", - "fieldtype": "Data", - "label": "Image Description" - }, - { - "fieldname": "thumbnail", - "fieldtype": "Data", - "label": "Thumbnail", - "read_only": 1 - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "description": "Show Stock availability based on this warehouse. If the parent warehouse is selected, then the system will display the consolidated available quantity of all child warehouses.", - "fieldname": "website_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Website Warehouse", - "options": "Warehouse" - }, - { - "description": "List this Item in multiple groups on the website.", - "fieldname": "website_item_groups", - "fieldtype": "Table", - "label": "Website Item Groups", - "options": "Website Item Group" - }, - { - "fieldname": "set_meta_tags", - "fieldtype": "Button", - "label": "Set Meta Tags" - }, - { - "fieldname": "section_break_17", - "fieldtype": "Section Break", - "label": "Display Information" - }, - { - "fieldname": "copy_from_item_group", - "fieldtype": "Button", - "label": "Copy From Item Group" - }, - { - "fieldname": "website_specifications", - "fieldtype": "Table", - "label": "Website Specifications", - "options": "Item Website Specification" - }, - { - "fieldname": "web_long_description", - "fieldtype": "Text Editor", - "label": "Website Description" - }, - { - "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.", - "fieldname": "website_content", - "fieldtype": "HTML Editor", - "label": "Website Content" - }, - { - "fetch_from": "item_code.item_group", - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Group", - "options": "Item Group", - "read_only": 1, - "search_index": 1 - }, - { - "default": "1", - "fieldname": "published", - "fieldtype": "Check", - "label": "Published" - }, - { - "default": "0", - "depends_on": "has_variants", - "fetch_from": "item_code.has_variants", - "fieldname": "has_variants", - "fieldtype": "Check", - "in_standard_filter": 1, - "label": "Has Variants", - "no_copy": 1, - "read_only": 1 - }, - { - "depends_on": "variant_of", - "fetch_from": "item_code.variant_of", - "fieldname": "variant_of", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Variant Of", - "options": "Item", - "read_only": 1, - "search_index": 1, - "set_only_once": 1 - }, - { - "fetch_from": "item_code.stock_uom", - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "options": "UOM", - "read_only": 1 - }, - { - "depends_on": "brand", - "fetch_from": "item_code.brand", - "fieldname": "brand", - "fieldtype": "Link", - "label": "Brand", - "options": "Brand", - "search_index": 1 - }, - { - "collapsible": 1, - "fieldname": "advanced_display_section", - "fieldtype": "Section Break", - "label": "Advanced Display Content" - }, - { - "fieldname": "display_section", - "fieldtype": "Section Break", - "label": "Display Images" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.description", - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Item Description", - "read_only": 1 - }, - { - "default": "WEB-ITM-.####", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "label": "Naming Series", - "no_copy": 1, - "options": "WEB-ITM-.####", - "print_hide": 1 - }, - { - "fieldname": "display_additional_information_section", - "fieldtype": "Section Break", - "label": "Display Additional Information" - }, - { - "depends_on": "show_tabbed_section", - "fieldname": "tabs", - "fieldtype": "Table", - "label": "Tabs", - "options": "Website Item Tabbed Section" - }, - { - "default": "0", - "fieldname": "show_tabbed_section", - "fieldtype": "Check", - "label": "Add Section with Tabs" - }, - { - "collapsible": 1, - "fieldname": "offers_section", - "fieldtype": "Section Break", - "label": "Offers" - }, - { - "fieldname": "offers", - "fieldtype": "Table", - "label": "Offers to Display", - "options": "Website Offer" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "description": "Short Description for List View", - "fieldname": "short_description", - "fieldtype": "Small Text", - "label": "Short Website Description" - }, - { - "collapsible": 1, - "fieldname": "recommended_items_section", - "fieldtype": "Section Break", - "label": "Recommended Items" - }, - { - "fieldname": "recommended_items", - "fieldtype": "Table", - "label": "Recommended/Similar Items", - "options": "Recommended Items" - }, - { - "fieldname": "stock_information_section", - "fieldtype": "Section Break", - "label": "Stock Information" - }, - { - "fieldname": "column_break_24", - "fieldtype": "Column Break" - }, - { - "default": "0", - "description": "Indicate that Item is available on backorder and not usually pre-stocked", - "fieldname": "on_backorder", - "fieldtype": "Check", - "label": "On Backorder" - } - ], - "has_web_view": 1, - "image_field": "website_image", - "index_web_pages_for_search": 1, - "links": [], - "make_attachments_public": 1, - "modified": "2023-09-12 14:19:22.822689", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Website Item", - "naming_rule": "Expression (old style)", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Website Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager", - "share": 1, - "write": 1 - } - ], - "search_fields": "web_item_name, item_code, item_group", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "title_field": "web_item_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py deleted file mode 100644 index 81b8ecab48..0000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ /dev/null @@ -1,469 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import json -from typing import TYPE_CHECKING, List, Union - -if TYPE_CHECKING: - from erpnext.stock.doctype.item.item import Item - -import frappe -from frappe import _ -from frappe.utils import cint, cstr, flt, random_string -from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -from frappe.website.website_generator import WebsiteGenerator - -from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews -from erpnext.e_commerce.redisearch_utils import ( - delete_item_from_index, - insert_item_to_index, - update_index_for_item, -) -from erpnext.e_commerce.shopping_cart.cart import _set_price_list -from erpnext.setup.doctype.item_group.item_group import ( - get_parent_item_groups, - invalidate_cache_for, -) -from erpnext.utilities.product import get_price - - -class WebsiteItem(WebsiteGenerator): - website = frappe._dict( - page_title_field="web_item_name", - condition_field="published", - template="templates/generators/item/item.html", - no_cache=1, - ) - - def autoname(self): - # use naming series to accomodate items with same name (different item code) - from frappe.model.naming import get_default_naming_series, make_autoname - - naming_series = get_default_naming_series("Website Item") - if not self.name and naming_series: - self.name = make_autoname(naming_series, doc=self) - - def onload(self): - super(WebsiteItem, self).onload() - - def validate(self): - super(WebsiteItem, self).validate() - - if not self.item_code: - frappe.throw(_("Item Code is required"), title=_("Mandatory")) - - self.validate_duplicate_website_item() - self.validate_website_image() - self.make_thumbnail() - self.publish_unpublish_desk_item(publish=True) - - if not self.get("__islocal"): - wig = frappe.qb.DocType("Website Item Group") - query = ( - frappe.qb.from_(wig) - .select(wig.item_group) - .where( - (wig.parentfield == "website_item_groups") - & (wig.parenttype == "Website Item") - & (wig.parent == self.name) - ) - ) - result = query.run(as_list=True) - - self.old_website_item_groups = [x[0] for x in result] - - def on_update(self): - invalidate_cache_for_web_item(self) - self.update_template_item() - - def on_trash(self): - super(WebsiteItem, self).on_trash() - delete_item_from_index(self) - self.publish_unpublish_desk_item(publish=False) - - def validate_duplicate_website_item(self): - existing_web_item = frappe.db.exists("Website Item", {"item_code": self.item_code}) - if existing_web_item and existing_web_item != self.name: - message = _("Website Item already exists against Item {0}").format(frappe.bold(self.item_code)) - frappe.throw(message, title=_("Already Published")) - - def publish_unpublish_desk_item(self, publish=True): - if frappe.db.get_value("Item", self.item_code, "published_in_website") and publish: - return # if already published don't publish again - frappe.db.set_value("Item", self.item_code, "published_in_website", publish) - - def make_route(self): - """Called from set_route in WebsiteGenerator.""" - if not self.route: - return ( - cstr(frappe.db.get_value("Item Group", self.item_group, "route")) - + "/" - + self.scrub((self.item_name if self.item_name else self.item_code) + "-" + random_string(5)) - ) - - def update_template_item(self): - """Publish Template Item if Variant is published.""" - if self.variant_of: - if self.published: - # show template - template_item = frappe.get_doc("Item", self.variant_of) - - if not template_item.published_in_website: - template_item.flags.ignore_permissions = True - make_website_item(template_item) - - def validate_website_image(self): - if frappe.flags.in_import: - return - - """Validate if the website image is a public file""" - if not self.website_image: - return - - # find if website image url exists as public - file_doc = frappe.get_all( - "File", - filters={"file_url": self.website_image}, - fields=["name", "is_private"], - order_by="is_private asc", - limit_page_length=1, - ) - - if file_doc: - file_doc = file_doc[0] - - if not file_doc: - frappe.msgprint( - _("Website Image {0} attached to Item {1} cannot be found").format( - self.website_image, self.name - ) - ) - - self.website_image = None - - elif file_doc.is_private: - frappe.msgprint(_("Website Image should be a public file or website URL")) - - self.website_image = None - - def make_thumbnail(self): - """Make a thumbnail of `website_image`""" - if frappe.flags.in_import or frappe.flags.in_migrate: - return - - import requests.exceptions - - db_website_image = frappe.db.get_value(self.doctype, self.name, "website_image") - if not self.is_new() and self.website_image != db_website_image: - self.thumbnail = None - - if self.website_image and not self.thumbnail: - file_doc = None - - try: - file_doc = frappe.get_doc( - "File", - { - "file_url": self.website_image, - "attached_to_doctype": "Website Item", - "attached_to_name": self.name, - }, - ) - except frappe.DoesNotExistError: - pass - # cleanup - frappe.local.message_log.pop() - - except requests.exceptions.HTTPError: - frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image)) - self.website_image = None - - except requests.exceptions.SSLError: - frappe.msgprint( - _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image) - ) - self.website_image = None - - # for CSV import - if self.website_image and not file_doc: - try: - file_doc = frappe.get_doc( - { - "doctype": "File", - "file_url": self.website_image, - "attached_to_doctype": "Website Item", - "attached_to_name": self.name, - } - ).save() - - except IOError: - self.website_image = None - - if file_doc: - if not file_doc.thumbnail_url: - file_doc.make_thumbnail() - - self.thumbnail = file_doc.thumbnail_url - - def get_context(self, context): - context.show_search = True - context.search_link = "/search" - context.body_class = "product-page" - - context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs - self.attributes = frappe.get_all( - "Item Variant Attribute", - fields=["attribute", "attribute_value"], - filters={"parent": self.item_code}, - ) - - if self.slideshow: - context.update(get_slideshow(self)) - - self.set_metatags(context) - self.set_shopping_cart_data(context) - - settings = context.shopping_cart.cart_settings - - self.get_product_details_section(context) - - if settings.get("enable_reviews"): - reviews_data = get_item_reviews(self.name) - context.update(reviews_data) - context.reviews = context.reviews[:4] - - context.wished = False - if frappe.db.exists( - "Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user} - ): - context.wished = True - - context.user_is_customer = check_if_user_is_customer() - - context.recommended_items = None - if settings and settings.enable_recommendations: - context.recommended_items = self.get_recommended_items(settings) - - return context - - def set_selected_attributes(self, variants, context, attribute_values_available): - for variant in variants: - variant.attributes = frappe.get_all( - "Item Variant Attribute", - filters={"parent": variant.name}, - fields=["attribute", "attribute_value as value"], - ) - - # make an attribute-value map for easier access in templates - variant.attribute_map = frappe._dict( - {attr.attribute: attr.value for attr in variant.attributes} - ) - - for attr in variant.attributes: - values = attribute_values_available.setdefault(attr.attribute, []) - if attr.value not in values: - values.append(attr.value) - - if variant.name == context.variant.name: - context.selected_attributes[attr.attribute] = attr.value - - def set_attribute_values(self, attributes, context, attribute_values_available): - for attr in attributes: - values = context.attribute_values.setdefault(attr.attribute, []) - - if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")): - for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt): - values.append(val) - else: - # get list of values defined (for sequence) - for attr_value in frappe.db.get_all( - "Item Attribute Value", - fields=["attribute_value"], - filters={"parent": attr.attribute}, - order_by="idx asc", - ): - - if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): - values.append(attr_value.attribute_value) - - def set_metatags(self, context): - context.metatags = frappe._dict({}) - - safe_description = frappe.utils.to_markdown(self.description) - - context.metatags.url = frappe.utils.get_url() + "/" + context.route - - if context.website_image: - if context.website_image.startswith("http"): - url = context.website_image - else: - url = frappe.utils.get_url() + context.website_image - context.metatags.image = url - - context.metatags.description = safe_description[:300] - - context.metatags.title = self.web_item_name or self.item_name or self.item_code - - context.metatags["og:type"] = "product" - context.metatags["og:site_name"] = "ERPNext" - - def set_shopping_cart_data(self, context): - from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website - - context.shopping_cart = get_product_info_for_website( - self.item_code, skip_quotation_creation=True - ) - - @frappe.whitelist() - def copy_specification_from_item_group(self): - self.set("website_specifications", []) - if self.item_group: - for label, desc in frappe.db.get_values( - "Item Website Specification", {"parent": self.item_group}, ["label", "description"] - ): - row = self.append("website_specifications") - row.label = label - row.description = desc - - def get_product_details_section(self, context): - """Get section with tabs or website specifications.""" - context.show_tabs = self.show_tabbed_section - if self.show_tabbed_section and (self.tabs or self.website_specifications): - context.tabs = self.get_tabs() - else: - context.website_specifications = self.website_specifications - - def get_tabs(self): - tab_values = {} - tab_values["tab_1_title"] = "Product Details" - tab_values["tab_1_content"] = frappe.render_template( - "templates/generators/item/item_specifications.html", - {"website_specifications": self.website_specifications, "show_tabs": self.show_tabbed_section}, - ) - - for row in self.tabs: - tab_values[f"tab_{row.idx + 1}_title"] = _(row.label) - tab_values[f"tab_{row.idx + 1}_content"] = row.content - - return tab_values - - def get_recommended_items(self, settings): - ri = frappe.qb.DocType("Recommended Items") - wi = frappe.qb.DocType("Website Item") - - query = ( - frappe.qb.from_(ri) - .join(wi) - .on(ri.item_code == wi.item_code) - .select(ri.item_code, ri.route, ri.website_item_name, ri.website_item_thumbnail) - .where((ri.parent == self.name) & (wi.published == 1)) - .orderby(ri.idx) - ) - items = query.run(as_dict=True) - - if settings.show_price: - is_guest = frappe.session.user == "Guest" - # Show Price if logged in. - # If not logged in and price is hidden for guest, skip price fetch. - if is_guest and settings.hide_price_for_guest: - return items - - selling_price_list = _set_price_list(settings, None) - for item in items: - item.price_info = get_price( - item.item_code, selling_price_list, settings.default_customer_group, settings.company - ) - - return items - - -def invalidate_cache_for_web_item(doc): - """Invalidate Website Item Group cache and rebuild ItemVariantsCacheManager.""" - from erpnext.stock.doctype.item.item import invalidate_item_variants_cache_for_website - - invalidate_cache_for(doc, doc.item_group) - - website_item_groups = list( - set( - (doc.get("old_website_item_groups") or []) - + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group] - ) - ) - - for item_group in website_item_groups: - invalidate_cache_for(doc, item_group) - - # Update Search Cache - update_index_for_item(doc) - - invalidate_item_variants_cache_for_website(doc) - - -def on_doctype_update(): - # since route is a Text column, it needs a length for indexing - frappe.db.add_index("Website Item", ["route(500)"]) - - -def check_if_user_is_customer(user=None): - from frappe.contacts.doctype.contact.contact import get_contact_name - - if not user: - user = frappe.session.user - - contact_name = get_contact_name(user) - customer = None - - if contact_name: - contact = frappe.get_doc("Contact", contact_name) - for link in contact.links: - if link.link_doctype == "Customer": - customer = link.link_name - break - - return True if customer else False - - -@frappe.whitelist() -def make_website_item(doc: "Item", save: bool = True) -> Union["WebsiteItem", List[str]]: - "Make Website Item from Item. Used via Form UI or patch." - - if not doc: - return - - if isinstance(doc, str): - doc = json.loads(doc) - - if frappe.db.exists("Website Item", {"item_code": doc.get("item_code")}): - message = _("Website Item already exists against {0}").format(frappe.bold(doc.get("item_code"))) - frappe.throw(message, title=_("Already Published")) - - website_item = frappe.new_doc("Website Item") - website_item.web_item_name = doc.get("item_name") - - fields_to_map = [ - "item_code", - "item_name", - "item_group", - "stock_uom", - "brand", - "has_variants", - "variant_of", - "description", - ] - for field in fields_to_map: - website_item.update({field: doc.get(field)}) - - # Needed for publishing/mapping via Form UI only - if not frappe.flags.in_migrate and (doc.get("image") and not website_item.website_image): - website_item.website_image = doc.get("image") - - if not save: - return website_item - - website_item.save() - - # Add to search cache - insert_item_to_index(website_item) - - return [website_item.name, website_item.web_item_name] diff --git a/erpnext/e_commerce/doctype/website_item/website_item_list.js b/erpnext/e_commerce/doctype/website_item/website_item_list.js deleted file mode 100644 index b9dd9214a3..0000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item_list.js +++ /dev/null @@ -1,20 +0,0 @@ -frappe.listview_settings['Website Item'] = { - add_fields: ["item_name", "web_item_name", "published", "website_image", "has_variants", "variant_of"], - filters: [["published", "=", "1"]], - - get_indicator: function(doc) { - if (doc.has_variants && doc.published) { - return [__("Template"), "orange", "has_variants,=,Yes|published,=,1"]; - } else if (doc.has_variants && !doc.published) { - return [__("Template"), "grey", "has_variants,=,Yes|published,=,0"]; - } else if (doc.variant_of && doc.published) { - return [__("Variant"), "blue", "published,=,1|variant_of,=," + doc.variant_of]; - } else if (doc.variant_of && !doc.published) { - return [__("Variant"), "grey", "published,=,0|variant_of,=," + doc.variant_of]; - } else if (doc.published) { - return [__("Published"), "green", "published,=,1"]; - } else { - return [__("Not Published"), "grey", "published,=,0"]; - } - } -}; \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json deleted file mode 100644 index 6601dd81f2..0000000000 --- a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "actions": [], - "creation": "2021-03-18 20:32:15.321402", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label", - "content" - ], - "fields": [ - { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label" - }, - { - "fieldname": "content", - "fieldtype": "HTML Editor", - "in_list_view": 1, - "label": "Content" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-03-18 20:35:26.991192", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Website Item Tabbed Section", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py deleted file mode 100644 index 91148b8b04..0000000000 --- a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class WebsiteItemTabbedSection(Document): - pass diff --git a/erpnext/e_commerce/doctype/website_offer/__init__.py b/erpnext/e_commerce/doctype/website_offer/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/website_offer/website_offer.json b/erpnext/e_commerce/doctype/website_offer/website_offer.json deleted file mode 100644 index 627d548146..0000000000 --- a/erpnext/e_commerce/doctype/website_offer/website_offer.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "actions": [], - "creation": "2021-04-21 13:37:14.162162", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "offer_title", - "offer_subtitle", - "offer_details" - ], - "fields": [ - { - "fieldname": "offer_title", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Offer Title" - }, - { - "fieldname": "offer_subtitle", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Offer Subtitle" - }, - { - "fieldname": "offer_details", - "fieldtype": "Text Editor", - "label": "Offer Details" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-04-21 13:56:04.660331", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Website Offer", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_offer/website_offer.py b/erpnext/e_commerce/doctype/website_offer/website_offer.py deleted file mode 100644 index 8c92f75a1e..0000000000 --- a/erpnext/e_commerce/doctype/website_offer/website_offer.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - - -class WebsiteOffer(Document): - pass - - -@frappe.whitelist(allow_guest=True) -def get_offer_details(offer_id): - return frappe.db.get_value("Website Offer", {"name": offer_id}, ["offer_details"]) diff --git a/erpnext/e_commerce/doctype/wishlist/__init__.py b/erpnext/e_commerce/doctype/wishlist/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py b/erpnext/e_commerce/doctype/wishlist/test_wishlist.py deleted file mode 100644 index 9d27126fdb..0000000000 --- a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -import unittest - -import frappe -from frappe.core.doctype.user_permission.test_user_permission import create_user - -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.doctype.wishlist.wishlist import add_to_wishlist, remove_from_wishlist -from erpnext.stock.doctype.item.test_item import make_item - - -class TestWishlist(unittest.TestCase): - def setUp(self): - item = make_item("Test Phone Series X") - if not frappe.db.exists("Website Item", {"item_code": "Test Phone Series X"}): - make_website_item(item, save=True) - - item = make_item("Test Phone Series Y") - if not frappe.db.exists("Website Item", {"item_code": "Test Phone Series Y"}): - make_website_item(item, save=True) - - def tearDown(self): - frappe.get_cached_doc("Website Item", {"item_code": "Test Phone Series X"}).delete() - frappe.get_cached_doc("Website Item", {"item_code": "Test Phone Series Y"}).delete() - frappe.get_cached_doc("Item", "Test Phone Series X").delete() - frappe.get_cached_doc("Item", "Test Phone Series Y").delete() - - def test_add_remove_items_in_wishlist(self): - "Check if items are added and removed from user's wishlist." - # add first item - add_to_wishlist("Test Phone Series X") - - # check if wishlist was created and item was added - self.assertTrue(frappe.db.exists("Wishlist", {"user": frappe.session.user})) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user} - ) - ) - - # add second item to wishlist - add_to_wishlist("Test Phone Series Y") - wishlist_length = frappe.db.get_value( - "Wishlist Item", {"parent": frappe.session.user}, "count(*)" - ) - self.assertEqual(wishlist_length, 2) - - remove_from_wishlist("Test Phone Series X") - remove_from_wishlist("Test Phone Series Y") - - wishlist_length = frappe.db.get_value( - "Wishlist Item", {"parent": frappe.session.user}, "count(*)" - ) - self.assertIsNone(frappe.db.exists("Wishlist Item", {"parent": frappe.session.user})) - self.assertEqual(wishlist_length, 0) - - # tear down - frappe.get_doc("Wishlist", {"user": frappe.session.user}).delete() - - def test_add_remove_in_wishlist_multiple_users(self): - "Check if items are added and removed from the correct user's wishlist." - test_user = create_user("test_reviewer@example.com", "Customer") - test_user_1 = create_user("test_reviewer_1@example.com", "Customer") - - # add to wishlist for first user - frappe.set_user(test_user.name) - add_to_wishlist("Test Phone Series X") - - # add to wishlist for second user - frappe.set_user(test_user_1.name) - add_to_wishlist("Test Phone Series X") - - # check wishlist and its content for users - self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user.name})) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name} - ) - ) - - self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user_1.name})) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name} - ) - ) - - # remove item for second user - remove_from_wishlist("Test Phone Series X") - - # make sure item was removed for second user and not first - self.assertFalse( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name} - ) - ) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name} - ) - ) - - # remove item for first user - frappe.set_user(test_user.name) - remove_from_wishlist("Test Phone Series X") - self.assertFalse( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name} - ) - ) - - # tear down - frappe.set_user("Administrator") - frappe.get_doc("Wishlist", {"user": test_user.name}).delete() - frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete() diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.js b/erpnext/e_commerce/doctype/wishlist/wishlist.js deleted file mode 100644 index d96e552ecd..0000000000 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Wishlist', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.json b/erpnext/e_commerce/doctype/wishlist/wishlist.json deleted file mode 100644 index 922924e53b..0000000000 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "actions": [], - "autoname": "field:user", - "creation": "2021-03-10 18:52:28.769126", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "user", - "section_break_2", - "items" - ], - "fields": [ - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, - { - "fieldname": "items", - "fieldtype": "Table", - "label": "Items", - "options": "Wishlist Item" - } - ], - "in_create": 1, - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-07-08 13:11:21.693956", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Wishlist", - "owner": "Administrator", - "permissions": [ - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Website Manager", - "share": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py deleted file mode 100644 index eb74027d77..0000000000 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - - -class Wishlist(Document): - pass - - -@frappe.whitelist() -def add_to_wishlist(item_code): - """Insert Item into wishlist.""" - - if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}): - return - - web_item_data = frappe.db.get_value( - "Website Item", - {"item_code": item_code}, - [ - "website_image", - "website_warehouse", - "name", - "web_item_name", - "item_name", - "item_group", - "route", - ], - as_dict=1, - ) - - wished_item_dict = { - "item_code": item_code, - "item_name": web_item_data.get("item_name"), - "item_group": web_item_data.get("item_group"), - "website_item": web_item_data.get("name"), - "web_item_name": web_item_data.get("web_item_name"), - "image": web_item_data.get("website_image"), - "warehouse": web_item_data.get("website_warehouse"), - "route": web_item_data.get("route"), - } - - if not frappe.db.exists("Wishlist", frappe.session.user): - # initialise wishlist - wishlist = frappe.get_doc({"doctype": "Wishlist"}) - wishlist.user = frappe.session.user - wishlist.append("items", wished_item_dict) - wishlist.save(ignore_permissions=True) - else: - wishlist = frappe.get_doc("Wishlist", frappe.session.user) - item = wishlist.append("items", wished_item_dict) - item.db_insert() - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items))) - - -@frappe.whitelist() -def remove_from_wishlist(item_code): - if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}): - frappe.db.delete("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}) - frappe.db.commit() # nosemgrep - - wishlist_items = frappe.db.get_values("Wishlist Item", filters={"parent": frappe.session.user}) - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items))) diff --git a/erpnext/e_commerce/doctype/wishlist_item/__init__.py b/erpnext/e_commerce/doctype/wishlist_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json deleted file mode 100644 index c0414a7f8e..0000000000 --- a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "actions": [], - "creation": "2021-03-10 19:03:00.662714", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "website_item", - "web_item_name", - "column_break_3", - "item_name", - "item_group", - "item_details_section", - "description", - "column_break_7", - "route", - "image", - "image_view", - "section_break_8", - "warehouse_section", - "warehouse" - ], - "fields": [ - { - "fetch_from": "website_item.item_code", - "fetch_if_empty": 1, - "fieldname": "item_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Code", - "options": "Item", - "reqd": 1 - }, - { - "fieldname": "website_item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Website Item", - "options": "Website Item", - "read_only": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.item_name", - "fetch_if_empty": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "item_details_section", - "fieldtype": "Section Break", - "label": "Item Details", - "read_only": 1 - }, - { - "fetch_from": "item_code.description", - "fetch_if_empty": 1, - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Description", - "read_only": 1 - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.image", - "fetch_if_empty": 1, - "fieldname": "image", - "fieldtype": "Attach", - "hidden": 1, - "label": "Image" - }, - { - "fetch_from": "item_code.image", - "fetch_if_empty": 1, - "fieldname": "image_view", - "fieldtype": "Image", - "hidden": 1, - "label": "Image View", - "options": "image", - "print_hide": 1 - }, - { - "fieldname": "warehouse_section", - "fieldtype": "Section Break", - "label": "Warehouse" - }, - { - "fieldname": "warehouse", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Warehouse", - "options": "Warehouse", - "read_only": 1 - }, - { - "fieldname": "section_break_8", - "fieldtype": "Section Break" - }, - { - "fetch_from": "item_code.item_group", - "fetch_if_empty": 1, - "fieldname": "item_group", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "read_only": 1 - }, - { - "fetch_from": "website_item.route", - "fetch_if_empty": 1, - "fieldname": "route", - "fieldtype": "Small Text", - "label": "Route", - "read_only": 1 - }, - { - "fetch_from": "website_item.web_item_name", - "fetch_if_empty": 1, - "fieldname": "web_item_name", - "fieldtype": "Data", - "label": "Website Item Name", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-08-09 10:30:41.964802", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Wishlist Item", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py deleted file mode 100644 index 75ebccbc1b..0000000000 --- a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class WishlistItem(Document): - pass diff --git a/erpnext/e_commerce/legacy_search.py b/erpnext/e_commerce/legacy_search.py deleted file mode 100644 index ef8e86d442..0000000000 --- a/erpnext/e_commerce/legacy_search.py +++ /dev/null @@ -1,134 +0,0 @@ -import frappe -from frappe.search.full_text_search import FullTextSearch -from frappe.utils import strip_html_tags -from whoosh.analysis import StemmingAnalyzer -from whoosh.fields import ID, KEYWORD, TEXT, Schema -from whoosh.qparser import FieldsPlugin, MultifieldParser, WildcardPlugin -from whoosh.query import Prefix - -# TODO: Make obsolete -INDEX_NAME = "products" - - -class ProductSearch(FullTextSearch): - """Wrapper for WebsiteSearch""" - - def get_schema(self): - return Schema( - title=TEXT(stored=True, field_boost=1.5), - name=ID(stored=True), - path=ID(stored=True), - content=TEXT(stored=True, analyzer=StemmingAnalyzer()), - keywords=KEYWORD(stored=True, scorable=True, commas=True), - ) - - def get_id(self): - return "name" - - def get_items_to_index(self): - """Get all routes to be indexed, this includes the static pages - in www/ and routes from published documents - - Returns: - self (object): FullTextSearch Instance - """ - items = get_all_published_items() - documents = [self.get_document_to_index(item) for item in items] - return documents - - def get_document_to_index(self, item): - try: - item = frappe.get_doc("Item", item) - title = item.item_name - keywords = [item.item_group] - - if item.brand: - keywords.append(item.brand) - - if item.website_image_alt: - keywords.append(item.website_image_alt) - - if item.has_variants and item.variant_based_on == "Item Attribute": - keywords = keywords + [attr.attribute for attr in item.attributes] - - if item.web_long_description: - content = strip_html_tags(item.web_long_description) - elif item.description: - content = strip_html_tags(item.description) - - return frappe._dict( - title=title, - name=item.name, - path=item.route, - content=content, - keywords=", ".join(keywords), - ) - except Exception: - pass - - def search(self, text, scope=None, limit=20): - """Search from the current index - - Args: - text (str): String to search for - scope (str, optional): Scope to limit the search. Defaults to None. - limit (int, optional): Limit number of search results. Defaults to 20. - - Returns: - [List(_dict)]: Search results - """ - ix = self.get_index() - - results = None - out = [] - - with ix.searcher() as searcher: - parser = MultifieldParser(["title", "content", "keywords"], ix.schema) - parser.remove_plugin_class(FieldsPlugin) - parser.remove_plugin_class(WildcardPlugin) - query = parser.parse(text) - - filter_scoped = None - if scope: - filter_scoped = Prefix(self.id, scope) - results = searcher.search(query, limit=limit, filter=filter_scoped) - - for r in results: - out.append(self.parse_result(r)) - - return out - - def parse_result(self, result): - title_highlights = result.highlights("title") - content_highlights = result.highlights("content") - keyword_highlights = result.highlights("keywords") - - return frappe._dict( - title=result["title"], - path=result["path"], - keywords=result["keywords"], - title_highlights=title_highlights, - content_highlights=content_highlights, - keyword_highlights=keyword_highlights, - ) - - -def get_all_published_items(): - return frappe.get_all( - "Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code" - ) - - -def update_index_for_path(path): - search = ProductSearch(INDEX_NAME) - return search.update_index_by_name(path) - - -def remove_document_from_index(path): - search = ProductSearch(INDEX_NAME) - return search.remove_document_from_index(path) - - -def build_index_for_all_routes(): - search = ProductSearch(INDEX_NAME) - return search.build() diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py deleted file mode 100644 index e5e5e97f86..0000000000 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe -from frappe.utils import floor - - -class ProductFiltersBuilder: - def __init__(self, item_group=None): - if not item_group: - self.doc = frappe.get_doc("E Commerce Settings") - else: - self.doc = frappe.get_doc("Item Group", item_group) - - self.item_group = item_group - - def get_field_filters(self): - from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website - - if not self.item_group and not self.doc.enable_field_filters: - return - - fields, filter_data = [], [] - filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings - - # filter valid field filters i.e. those that exist in Website Item - web_item_meta = frappe.get_meta("Website Item", cached=True) - fields = [ - web_item_meta.get_field(field) for field in filter_fields if web_item_meta.has_field(field) - ] - - for df in fields: - item_filters, item_or_filters = {"published": 1}, [] - link_doctype_values = self.get_filtered_link_doctype_records(df) - - if df.fieldtype == "Link": - if self.item_group: - include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants") - if include_child: - include_groups = get_child_groups_for_website(self.item_group, include_self=True) - include_groups = [x.name for x in include_groups] - item_or_filters.extend( - [ - ["item_group", "in", include_groups], - ["Website Item Group", "item_group", "=", self.item_group], # consider website item groups - ] - ) - else: - item_or_filters.extend( - [ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group], # consider website item groups - ] - ) - - # exclude variants if mentioned in settings - if frappe.db.get_single_value("E Commerce Settings", "hide_variants"): - item_filters["variant_of"] = ["is", "not set"] - - # Get link field values attached to published items - item_values = frappe.get_all( - "Website Item", - fields=[df.fieldname], - filters=item_filters, - or_filters=item_or_filters, - distinct="True", - pluck=df.fieldname, - ) - - values = list(set(item_values) & link_doctype_values) # intersection of both - else: - # table multiselect - values = list(link_doctype_values) - - # Remove None - if None in values: - values.remove(None) - - if values: - filter_data.append([df, values]) - - return filter_data - - def get_filtered_link_doctype_records(self, field): - """ - Get valid link doctype records depending on filters. - Apply enable/disable/show_in_website filter. - Returns: - set: A set containing valid record names - """ - link_doctype = field.get_link_doctype() - meta = frappe.get_meta(link_doctype, cached=True) if link_doctype else None - if meta: - filters = self.get_link_doctype_filters(meta) - link_doctype_values = set(d.name for d in frappe.get_all(link_doctype, filters)) - - return link_doctype_values if meta else set() - - def get_link_doctype_filters(self, meta): - "Filters for Link Doctype eg. 'show_in_website'." - filters = {} - if not meta: - return filters - - if meta.has_field("enabled"): - filters["enabled"] = 1 - if meta.has_field("disabled"): - filters["disabled"] = 0 - if meta.has_field("show_in_website"): - filters["show_in_website"] = 1 - - return filters - - def get_attribute_filters(self): - if not self.item_group and not self.doc.enable_attribute_filters: - return - - attributes = [row.attribute for row in self.doc.filter_attributes] - - if not attributes: - return [] - - result = frappe.get_all( - "Item Variant Attribute", - filters={"attribute": ["in", attributes], "attribute_value": ["is", "set"]}, - fields=["attribute", "attribute_value"], - distinct=True, - ) - - attribute_value_map = {} - for d in result: - attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value) - - out = [] - for name, values in attribute_value_map.items(): - out.append(frappe._dict(name=name, item_attribute_values=values)) - return out - - def get_discount_filters(self, discounts): - discount_filters = [] - - # [25.89, 60.5] min max - min_discount, max_discount = discounts[0], discounts[1] - # [25, 60] rounded min max - min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount) - - min_range = int(min_discount - (min_range_absolute % 10)) # 20 - max_range = int(max_discount - (max_range_absolute % 10)) # 60 - - min_range = ( - (min_range + 10) if min_range != min_range_absolute else min_range - ) # 30 (upper limit of 25.89 in range of 10) - max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60 - - for discount in range(min_range, (max_range + 1), 10): - label = f"{discount}% and below" - discount_filters.append([discount, label]) - - return discount_filters diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py deleted file mode 100644 index 975f87608a..0000000000 --- a/erpnext/e_commerce/product_data_engine/query.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe.utils import flt - -from erpnext.e_commerce.doctype.item_review.item_review import get_customer -from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website -from erpnext.utilities.product import get_non_stock_item_status - - -class ProductQuery: - """Query engine for product listing - - Attributes: - fields (list): Fields to fetch in query - conditions (string): Conditions for query building - or_conditions (string): Search conditions - page_length (Int): Length of page for the query - settings (Document): E Commerce Settings DocType - """ - - def __init__(self): - self.settings = frappe.get_doc("E Commerce Settings") - self.page_length = self.settings.products_per_page or 20 - - self.or_filters = [] - self.filters = [["published", "=", 1]] - self.fields = [ - "web_item_name", - "name", - "item_name", - "item_code", - "website_image", - "variant_of", - "has_variants", - "item_group", - "web_long_description", - "short_description", - "route", - "website_warehouse", - "ranking", - "on_backorder", - ] - - def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None): - """ - Args: - attributes (dict, optional): Item Attribute filters - fields (dict, optional): Field level filters - search_term (str, optional): Search term to lookup - start (int, optional): Page start - - Returns: - dict: Dict containing items, item count & discount range - """ - # track if discounts included in field filters - self.filter_with_discount = bool(fields.get("discount")) - result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0 - - if fields: - self.build_fields_filters(fields) - if item_group: - self.build_item_group_filters(item_group) - if search_term: - self.build_search_filters(search_term) - if self.settings.hide_variants: - self.filters.append(["variant_of", "is", "not set"]) - - # query results - if attributes: - result, count = self.query_items_with_attributes(attributes, start) - else: - result, count = self.query_items(start=start) - - # sort combined results by ranking - result = sorted(result, key=lambda x: x.get("ranking"), reverse=True) - - if self.settings.enabled: - cart_items = self.get_cart_items() - - result, discount_list = self.add_display_details(result, discount_list, cart_items) - - discounts = [] - if discount_list: - discounts = [min(discount_list), max(discount_list)] - - result = self.filter_results_by_discount(fields, result) - - return {"items": result, "items_count": count, "discounts": discounts} - - def query_items(self, start=0): - """Build a query to fetch Website Items based on field filters.""" - # MySQL does not support offset without limit, - # frappe does not accept two parameters for limit - # https://dev.mysql.com/doc/refman/8.0/en/select.html#id4651989 - count_items = frappe.db.get_all( - "Website Item", - filters=self.filters, - or_filters=self.or_filters, - limit_page_length=184467440737095516, - limit_start=start, # get all items from this offset for total count ahead - order_by="ranking desc", - ) - count = len(count_items) - - # If discounts included, return all rows. - # Slice after filtering rows with discount (See `filter_results_by_discount`). - # Slicing before hand will miss discounted items on the 3rd or 4th page. - # Discounts are fetched on computing Pricing Rules so we cannot query them directly. - page_length = 184467440737095516 if self.filter_with_discount else self.page_length - - items = frappe.db.get_all( - "Website Item", - fields=self.fields, - filters=self.filters, - or_filters=self.or_filters, - limit_page_length=page_length, - limit_start=start, - order_by="ranking desc", - ) - - return items, count - - def query_items_with_attributes(self, attributes, start=0): - """Build a query to fetch Website Items based on field & attribute filters.""" - item_codes = [] - - for attribute, values in attributes.items(): - if not isinstance(values, list): - values = [values] - - # get items that have selected attribute & value - item_code_list = frappe.db.get_all( - "Item", - fields=["item_code"], - filters=[ - ["published_in_website", "=", 1], - ["Item Variant Attribute", "attribute", "=", attribute], - ["Item Variant Attribute", "attribute_value", "in", values], - ], - ) - item_codes.append({x.item_code for x in item_code_list}) - - if item_codes: - item_codes = list(set.intersection(*item_codes)) - self.filters.append(["item_code", "in", item_codes]) - - items, count = self.query_items(start=start) - - return items, count - - def build_fields_filters(self, filters): - """Build filters for field values - - Args: - filters (dict): Filters - """ - for field, values in filters.items(): - if not values or field == "discount": - continue - - # handle multiselect fields in filter addition - meta = frappe.get_meta("Website Item", cached=True) - df = meta.get_field(field) - if df.fieldtype == "Table MultiSelect": - child_doctype = df.options - child_meta = frappe.get_meta(child_doctype, cached=True) - fields = child_meta.get("fields") - if fields: - self.filters.append([child_doctype, fields[0].fieldname, "IN", values]) - elif isinstance(values, list): - # If value is a list use `IN` query - self.filters.append([field, "in", values]) - else: - # `=` will be faster than `IN` for most cases - self.filters.append([field, "=", values]) - - def build_item_group_filters(self, item_group): - "Add filters for Item group page and include Website Item Groups." - from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website - - item_group_filters = [] - - item_group_filters.append(["Website Item", "item_group", "=", item_group]) - # Consider Website Item Groups - item_group_filters.append(["Website Item Group", "item_group", "=", item_group]) - - if frappe.db.get_value("Item Group", item_group, "include_descendants"): - # include child item group's items as well - # eg. Group Node A, will show items of child 1 and child 2 as well - # on it's web page - include_groups = get_child_groups_for_website(item_group, include_self=True) - include_groups = [x.name for x in include_groups] - item_group_filters.append(["Website Item", "item_group", "in", include_groups]) - - self.or_filters.extend(item_group_filters) - - def build_search_filters(self, search_term): - """Query search term in specified fields - - Args: - search_term (str): Search candidate - """ - # Default fields to search from - default_fields = {"item_code", "item_name", "web_long_description", "item_group"} - - # Get meta search fields - meta = frappe.get_meta("Website Item") - meta_fields = set(meta.get_search_fields()) - - # Join the meta fields and default fields set - search_fields = default_fields.union(meta_fields) - if frappe.db.count("Website Item", cache=True) > 50000: - search_fields.discard("web_long_description") - - # Build or filters for query - search = "%{}%".format(search_term) - for field in search_fields: - self.or_filters.append([field, "like", search]) - - def add_display_details(self, result, discount_list, cart_items): - """Add price and availability details in result.""" - for item in result: - product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get( - "product_info" - ) - - if product_info and product_info["price"]: - # update/mutate item and discount_list objects - self.get_price_discount_info(item, product_info["price"], discount_list) - - if self.settings.show_stock_availability: - self.get_stock_availability(item) - - item.in_cart = item.item_code in cart_items - - item.wished = False - if frappe.db.exists( - "Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user} - ): - item.wished = True - - return result, discount_list - - def get_price_discount_info(self, item, price_object, discount_list): - """Modify item object and add price details.""" - fields = ["formatted_mrp", "formatted_price", "price_list_rate"] - for field in fields: - item[field] = price_object.get(field) - - if price_object.get("discount_percent"): - item.discount_percent = flt(price_object.discount_percent) - discount_list.append(price_object.discount_percent) - - if item.formatted_mrp: - item.discount = price_object.get("formatted_discount_percent") or price_object.get( - "formatted_discount_rate" - ) - - def get_stock_availability(self, item): - from erpnext.templates.pages.wishlist import ( - get_stock_availability as get_stock_availability_from_template, - ) - - """Modify item object and add stock details.""" - item.in_stock = False - warehouse = item.get("website_warehouse") - is_stock_item = frappe.get_cached_value("Item", item.item_code, "is_stock_item") - - if item.get("on_backorder"): - return - - if not is_stock_item: - if warehouse: - # product bundle case - item.in_stock = get_non_stock_item_status(item.item_code, "website_warehouse") - else: - item.in_stock = True - elif warehouse: - item.in_stock = get_stock_availability_from_template(item.item_code, warehouse) - - def get_cart_items(self): - customer = get_customer(silent=True) - if customer: - quotation = frappe.get_all( - "Quotation", - fields=["name"], - filters={ - "party_name": customer, - "contact_email": frappe.session.user, - "order_type": "Shopping Cart", - "docstatus": 0, - }, - order_by="modified desc", - limit_page_length=1, - ) - if quotation: - items = frappe.get_all( - "Quotation Item", fields=["item_code"], filters={"parent": quotation[0].get("name")} - ) - items = [row.item_code for row in items] - return items - - return [] - - def filter_results_by_discount(self, fields, result): - if fields and fields.get("discount"): - discount_percent = frappe.utils.flt(fields["discount"][0]) - result = [ - row - for row in result - if row.get("discount_percent") and row.discount_percent <= discount_percent - ] - - if self.filter_with_discount: - # no limit was added to results while querying - # slice results manually - result[: self.page_length] - - return result diff --git a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py deleted file mode 100644 index 45bc20ece6..0000000000 --- a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import unittest - -import frappe - -from erpnext.e_commerce.api import get_product_filter_data -from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item - -test_dependencies = ["Item", "Item Group"] - - -class TestItemGroupProductDataEngine(unittest.TestCase): - "Test Products & Sub-Category Querying for Product Listing on Item Group Page." - - def setUp(self): - item_codes = [ - ("Test Mobile A", "_Test Item Group B"), - ("Test Mobile B", "_Test Item Group B"), - ("Test Mobile C", "_Test Item Group B - 1"), - ("Test Mobile D", "_Test Item Group B - 1"), - ("Test Mobile E", "_Test Item Group B - 2"), - ] - for item in item_codes: - item_code = item[0] - item_args = {"item_group": item[1]} - if not frappe.db.exists("Website Item", {"item_code": item_code}): - create_regular_web_item(item_code, item_args=item_args) - - frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1) - frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1) - - def tearDown(self): - frappe.db.rollback() - - def test_product_listing_in_item_group(self): - "Test if only products belonging to the Item Group are fetched." - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - - items = result.get("items") - item_codes = [item.get("item_code") for item in items] - - self.assertEqual(len(items), 2) - self.assertIn("Test Mobile A", item_codes) - self.assertNotIn("Test Mobile C", item_codes) - - def test_products_in_multiple_item_groups(self): - """Test if product is visible on multiple item group pages barring its own.""" - website_item = frappe.get_doc("Website Item", {"item_code": "Test Mobile E"}) - - # show item belonging to '_Test Item Group B - 2' in '_Test Item Group B - 1' as well - website_item.append("website_item_groups", {"item_group": "_Test Item Group B - 1"}) - website_item.save() - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B - 1", - } - ) - - items = result.get("items") - item_codes = [item.get("item_code") for item in items] - - self.assertEqual(len(items), 3) - self.assertIn("Test Mobile E", item_codes) # visible in other item groups - self.assertIn("Test Mobile C", item_codes) - self.assertIn("Test Mobile D", item_codes) - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B - 2", - } - ) - - items = result.get("items") - - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group - - def test_item_group_with_sub_groups(self): - "Test Valid Sub Item Groups in Item Group Page." - frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0) - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - - self.assertTrue(bool(result.get("sub_categories"))) - - child_groups = [d.name for d in result.get("sub_categories")] - # check if child group is fetched if shown in website - self.assertIn("_Test Item Group B - 1", child_groups) - - frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1) - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - child_groups = [d.name for d in result.get("sub_categories")] - - # check if child group is fetched if shown in website - self.assertIn("_Test Item Group B - 1", child_groups) - self.assertIn("_Test Item Group B - 2", child_groups) - - def test_item_group_page_with_descendants_included(self): - """ - Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3). - > _Test Item Group B [Level 1] - > _Test Item Group B - 1 [Level 2] - > _Test Item Group B - 1 - 1 [Level 3] - """ - frappe.get_doc( - { # create Level 3 nested child group - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 1 - 1", - "parent_item_group": "_Test Item Group B - 1", - } - ).insert() - - create_regular_web_item( # create an item belonging to level 3 item group - "Test Mobile F", item_args={"item_group": "_Test Item Group B - 1 - 1"} - ) - - frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1) - - # enable 'include descendants' in Level 1 - frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1) - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - - items = result.get("items") - item_codes = [item.get("item_code") for item in items] - - # check if all sub groups' items are pulled - self.assertEqual(len(items), 6) - self.assertIn("Test Mobile A", item_codes) - self.assertIn("Test Mobile C", item_codes) - self.assertIn("Test Mobile E", item_codes) - self.assertIn("Test Mobile F", item_codes) diff --git a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py deleted file mode 100644 index c3b6ed5da2..0000000000 --- a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py +++ /dev/null @@ -1,348 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import unittest - -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder -from erpnext.e_commerce.product_data_engine.query import ProductQuery - -test_dependencies = ["Item", "Item Group"] - - -class TestProductDataEngine(unittest.TestCase): - "Test Products Querying and Filters for Product Listing." - - @classmethod - def setUpClass(cls): - item_codes = [ - ("Test 11I Laptop", "Products"), # rank 1 - ("Test 12I Laptop", "Products"), # rank 2 - ("Test 13I Laptop", "Products"), # rank 3 - ("Test 14I Laptop", "Raw Material"), # rank 4 - ("Test 15I Laptop", "Raw Material"), # rank 5 - ("Test 16I Laptop", "Raw Material"), # rank 6 - ("Test 17I Laptop", "Products"), # rank 7 - ] - for index, item in enumerate(item_codes, start=1): - item_code = item[0] - item_args = {"item_group": item[1]} - web_args = {"ranking": index} - if not frappe.db.exists("Website Item", {"item_code": item_code}): - create_regular_web_item(item_code, item_args=item_args, web_args=web_args) - - setup_e_commerce_settings( - { - "products_per_page": 4, - "enable_field_filters": 1, - "filter_fields": [{"fieldname": "item_group"}], - "enable_attribute_filters": 1, - "filter_attributes": [{"attribute": "Test Size"}], - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - } - ) - frappe.local.shopping_cart_settings = None - - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - - def test_product_list_ordering_and_paging(self): - "Test if website items appear by ranking on different pages." - engine = ProductQuery() - result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None) - items = result.get("items") - - self.assertIsNotNone(items) - self.assertEqual(len(items), 4) - self.assertGreater(result.get("items_count"), 4) - - # check if items appear as per ranking set in setUpClass - self.assertEqual(items[0].get("item_code"), "Test 17I Laptop") - self.assertEqual(items[1].get("item_code"), "Test 16I Laptop") - self.assertEqual(items[2].get("item_code"), "Test 15I Laptop") - self.assertEqual(items[3].get("item_code"), "Test 14I Laptop") - - # check next page - result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None) - items = result.get("items") - - # check if items appear as per ranking set in setUpClass on next page - self.assertEqual(items[0].get("item_code"), "Test 13I Laptop") - self.assertEqual(items[1].get("item_code"), "Test 12I Laptop") - self.assertEqual(items[2].get("item_code"), "Test 11I Laptop") - - def test_change_product_ranking(self): - "Test if item on second page appear on first if ranking is changed." - item_code = "Test 12I Laptop" - old_ranking = frappe.db.get_value("Website Item", {"item_code": item_code}, "ranking") - - # low rank, appears on second page - self.assertEqual(old_ranking, 2) - - # set ranking as highest rank - frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", 10) - - engine = ProductQuery() - result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None) - items = result.get("items") - - # check if item is the first item on the first page - self.assertEqual(items[0].get("item_code"), item_code) - self.assertEqual(items[1].get("item_code"), "Test 17I Laptop") - - # tear down - frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", old_ranking) - - def test_product_list_field_filter_builder(self): - "Test if field filters are fetched correctly." - frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 0) - - filter_engine = ProductFiltersBuilder() - field_filters = filter_engine.get_field_filters() - - # Web Items belonging to 'Products' and 'Raw Material' are available - # but only 'Products' has 'show_in_website' enabled - item_group_filters = field_filters[0] - docfield = item_group_filters[0] - valid_item_groups = item_group_filters[1] - - self.assertEqual(docfield.options, "Item Group") - self.assertIn("Products", valid_item_groups) - self.assertNotIn("Raw Material", valid_item_groups) - - frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 1) - field_filters = filter_engine.get_field_filters() - - #'Products' and 'Raw Materials' both have 'show_in_website' enabled - item_group_filters = field_filters[0] - docfield = item_group_filters[0] - valid_item_groups = item_group_filters[1] - - self.assertEqual(docfield.options, "Item Group") - self.assertIn("Products", valid_item_groups) - self.assertIn("Raw Material", valid_item_groups) - - def test_product_list_with_field_filter(self): - "Test if field filters are applied correctly." - field_filters = {"item_group": "Raw Material"} - - engine = ProductQuery() - result = engine.query( - attributes={}, fields=field_filters, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only 'Raw Material' are fetched in the right order - self.assertEqual(len(items), 3) - self.assertEqual(items[0].get("item_code"), "Test 16I Laptop") - self.assertEqual(items[1].get("item_code"), "Test 15I Laptop") - - # def test_product_list_with_field_filter_table_multiselect(self): - # TODO - # pass - - def test_product_list_attribute_filter_builder(self): - "Test if attribute filters are fetched correctly." - create_variant_web_item() - - filter_engine = ProductFiltersBuilder() - attribute_filter = filter_engine.get_attribute_filters()[0] - attribute_values = attribute_filter.item_attribute_values - - self.assertEqual(attribute_filter.name, "Test Size") - self.assertGreater(len(attribute_values), 0) - self.assertIn("Large", attribute_values) - - def test_product_list_with_attribute_filter(self): - "Test if attribute filters are applied correctly." - create_variant_web_item() - - attribute_filters = {"Test Size": ["Large"]} - engine = ProductQuery() - result = engine.query( - attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only items with Test Size 'Large' are fetched - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test Web Item-L") - - def test_product_list_discount_filter_builder(self): - "Test if discount filters are fetched correctly." - from erpnext.e_commerce.doctype.website_item.test_website_item import ( - make_web_item_price, - make_web_pricing_rule, - ) - - item_code = "Test 12I Laptop" - make_web_item_price(item_code=item_code) - make_web_pricing_rule(title=f"Test Pricing Rule for {item_code}", item_code=item_code, selling=1) - - setup_e_commerce_settings({"show_price": 1}) - frappe.local.shopping_cart_settings = None - - engine = ProductQuery() - result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None) - self.assertTrue(bool(result.get("discounts"))) - - filter_engine = ProductFiltersBuilder() - discount_filters = filter_engine.get_discount_filters(result["discounts"]) - - self.assertEqual(len(discount_filters[0]), 2) - self.assertEqual(discount_filters[0][0], 10) - self.assertEqual(discount_filters[0][1], "10% and below") - - def test_product_list_with_discount_filters(self): - "Test if discount filters are applied correctly." - from erpnext.e_commerce.doctype.website_item.test_website_item import ( - make_web_item_price, - make_web_pricing_rule, - ) - - field_filters = {"discount": [10]} - - make_web_item_price(item_code="Test 12I Laptop") - make_web_pricing_rule( - title="Test Pricing Rule for Test 12I Laptop", # 10% discount - item_code="Test 12I Laptop", - selling=1, - ) - make_web_item_price(item_code="Test 13I Laptop") - make_web_pricing_rule( - title="Test Pricing Rule for Test 13I Laptop", # 15% discount - item_code="Test 13I Laptop", - discount_percentage=15, - selling=1, - ) - - setup_e_commerce_settings({"show_price": 1}) - frappe.local.shopping_cart_settings = None - - engine = ProductQuery() - result = engine.query( - attributes={}, fields=field_filters, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only product with 10% and below discount are fetched - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test 12I Laptop") - - def test_product_list_with_api(self): - "Test products listing using API." - from erpnext.e_commerce.api import get_product_filter_data - - create_variant_web_item() - - result = get_product_filter_data( - query_args={ - "field_filters": {"item_group": "Products"}, - "attribute_filters": {"Test Size": ["Large"]}, - "start": 0, - } - ) - - items = result.get("items") - - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test Web Item-L") - - def test_product_list_with_variants(self): - "Test if variants are hideen on hiding variants in settings." - create_variant_web_item() - - setup_e_commerce_settings({"enable_attribute_filters": 0, "hide_variants": 1}) - frappe.local.shopping_cart_settings = None - - attribute_filters = {"Test Size": ["Large"]} - engine = ProductQuery() - result = engine.query( - attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if any variants are fetched even though published variant exists - self.assertEqual(len(items), 0) - - # tear down - setup_e_commerce_settings({"enable_attribute_filters": 1, "hide_variants": 0}) - - def test_custom_field_as_filter(self): - "Test if custom field functions as filter correctly." - from frappe.custom.doctype.custom_field.custom_field import create_custom_field - - create_custom_field( - "Website Item", - dict( - owner="Administrator", - fieldname="supplier", - label="Supplier", - fieldtype="Link", - options="Supplier", - insert_after="on_backorder", - ), - ) - - frappe.db.set_value( - "Website Item", {"item_code": "Test 11I Laptop"}, "supplier", "_Test Supplier" - ) - frappe.db.set_value( - "Website Item", {"item_code": "Test 12I Laptop"}, "supplier", "_Test Supplier 1" - ) - - settings = frappe.get_doc("E Commerce Settings") - settings.append("filter_fields", {"fieldname": "supplier"}) - settings.save() - - filter_engine = ProductFiltersBuilder() - field_filters = filter_engine.get_field_filters() - custom_filter = field_filters[1] - filter_values = custom_filter[1] - - self.assertEqual(custom_filter[0].options, "Supplier") - self.assertEqual(len(filter_values), 2) - self.assertIn("_Test Supplier", filter_values) - - # test if custom filter works in query - field_filters = {"supplier": "_Test Supplier 1"} - engine = ProductQuery() - result = engine.query( - attributes={}, fields=field_filters, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only 'Raw Material' are fetched in the right order - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test 12I Laptop") - - -def create_variant_web_item(): - "Create Variant and Template Website Items." - from erpnext.controllers.item_variant import create_variant - from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - from erpnext.stock.doctype.item.test_item import make_item - - make_item( - "Test Web Item", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}], - }, - ) - if not frappe.db.exists("Item", "Test Web Item-L"): - variant = create_variant("Test Web Item", {"Test Size": "Large"}) - variant.save() - - if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}): - make_website_item(variant, save=True) diff --git a/erpnext/e_commerce/product_ui/grid.js b/erpnext/e_commerce/product_ui/grid.js deleted file mode 100644 index 20a6c30b52..0000000000 --- a/erpnext/e_commerce/product_ui/grid.js +++ /dev/null @@ -1,201 +0,0 @@ -erpnext.ProductGrid = class { - /* Options: - - items: Items - - settings: E Commerce Settings - - products_section: Products Wrapper - - preference: If preference is not grid view, render but hide - */ - constructor(options) { - Object.assign(this, options); - - if (this.preference !== "Grid View") { - this.products_section.addClass("hidden"); - } - - this.products_section.empty(); - this.make(); - } - - make() { - let me = this; - let html = ``; - - this.items.forEach(item => { - let title = item.web_item_name || item.item_name || item.item_code || ""; - title = title.length > 90 ? title.substr(0, 90) + "..." : title; - - html += `
`; - html += me.get_image_html(item, title); - html += me.get_card_body_html(item, title, me.settings); - html += `
`; - }); - - let $product_wrapper = this.products_section; - $product_wrapper.append(html); - } - - get_image_html(item, title) { - let image = item.website_image; - - if (image) { - return ` -
- - ${ title } - -
- `; - } else { - return ` - - `; - } - } - - get_card_body_html(item, title, settings) { - let body_html = ` -
-
- `; - body_html += this.get_title(item, title); - - // get floating elements - if (!item.has_variants) { - if (settings.enable_wishlist) { - body_html += this.get_wishlist_icon(item); - } - if (settings.enabled) { - body_html += this.get_cart_indicator(item); - } - - } - - body_html += `
`; - body_html += `
${ item.item_group || '' }
`; - - if (item.formatted_price) { - body_html += this.get_price_html(item); - } - - body_html += this.get_stock_availability(item, settings); - body_html += this.get_primary_button(item, settings); - body_html += `
`; // close div on line 49 - - return body_html; - } - - get_title(item, title) { - let title_html = ` - -
- ${ title || '' } -
-
- `; - return title_html; - } - - get_wishlist_icon(item) { - let icon_class = item.wished ? "wished" : "not-wished"; - return ` - - `; - } - - get_cart_indicator(item) { - return ` -
- 1 -
- `; - } - - get_price_html(item) { - let price_html = ` -
- ${ item.formatted_price || '' } - `; - - if (item.formatted_mrp) { - price_html += ` - - ${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" } - - - ${ item.discount } OFF - - `; - } - price_html += `
`; - return price_html; - } - - get_stock_availability(item, settings) { - if (settings.show_stock_availability && !item.has_variants) { - if (item.on_backorder) { - return ` - - ${ __("Available on backorder") } - - `; - } else if (!item.in_stock) { - return ` - - ${ __("Out of stock") } - - `; - } - } - - return ``; - } - - get_primary_button(item, settings) { - if (item.has_variants) { - return ` - -
- ${ __('Explore') } -
-
- `; - } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) { - return ` -
- - - - - - ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') } -
- - -
- ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') } -
-
- `; - } else { - return ``; - } - } -}; \ No newline at end of file diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js deleted file mode 100644 index c8fd7672c8..0000000000 --- a/erpnext/e_commerce/product_ui/list.js +++ /dev/null @@ -1,205 +0,0 @@ -erpnext.ProductList = class { - /* Options: - - items: Items - - settings: E Commerce Settings - - products_section: Products Wrapper - - preference: If preference is not list view, render but hide - */ - constructor(options) { - Object.assign(this, options); - - if (this.preference !== "List View") { - this.products_section.addClass("hidden"); - } - - this.products_section.empty(); - this.make(); - } - - make() { - let me = this; - let html = `

`; - - this.items.forEach(item => { - let title = item.web_item_name || item.item_name || item.item_code || ""; - title = title.length > 200 ? title.substr(0, 200) + "..." : title; - - html += `
`; - html += me.get_image_html(item, title, me.settings); - html += me.get_row_body_html(item, title, me.settings); - html += `
`; - }); - - let $product_wrapper = this.products_section; - $product_wrapper.append(html); - } - - get_image_html(item, title, settings) { - let image = item.website_image; - let wishlist_enabled = !item.has_variants && settings.enable_wishlist; - let image_html = ``; - - if (image) { - image_html += ` -
- - ${ title } - - ${ wishlist_enabled ? this.get_wishlist_icon(item): '' } -
- `; - } else { - image_html += ` -
- -
- ${ frappe.get_abbr(title) } -
-
- ${ wishlist_enabled ? this.get_wishlist_icon(item): '' } -
- `; - } - - return image_html; - } - - get_row_body_html(item, title, settings) { - let body_html = `
`; - body_html += this.get_title_html(item, title, settings); - body_html += this.get_item_details(item, settings); - body_html += `
`; - return body_html; - } - - get_title_html(item, title, settings) { - let title_html = `
`; - title_html += ` - - `; - - if (settings.enabled) { - title_html += `
`; - title_html += this.get_primary_button(item, settings); - title_html += `
`; - } - title_html += `
`; - - return title_html; - } - - get_item_details(item, settings) { - let details = ` -

- ${ item.item_group } | Item Code : ${ item.item_code } -

-
- ${ item.short_description || '' } -
-
- ${ item.formatted_price || '' } - `; - - if (item.formatted_mrp) { - details += ` - - ${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" } - - - ${ item.discount } OFF - - `; - } - - details += this.get_stock_availability(item, settings); - details += `
`; - - return details; - } - - get_stock_availability(item, settings) { - if (settings.show_stock_availability && !item.has_variants) { - if (item.on_backorder) { - return ` -
- - ${ __("Available on backorder") } - - `; - } else if (!item.in_stock) { - return ` -
- ${ __("Out of stock") } - `; - } - } - return ``; - } - - get_wishlist_icon(item) { - let icon_class = item.wished ? "wished" : "not-wished"; - - return ` - - `; - } - - get_primary_button(item, settings) { - if (item.has_variants) { - return ` - -
- ${ __('Explore') } -
-
- `; - } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) { - return ` -
- - - - - - ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') } -
- -
- 1 -
- - -
- ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') } -
-
- `; - } else { - return ``; - } - } - -}; diff --git a/erpnext/e_commerce/product_ui/search.js b/erpnext/e_commerce/product_ui/search.js deleted file mode 100644 index 1688cc1fb6..0000000000 --- a/erpnext/e_commerce/product_ui/search.js +++ /dev/null @@ -1,244 +0,0 @@ -erpnext.ProductSearch = class { - constructor(opts) { - /* Options: search_box_id (for custom search box) */ - $.extend(this, opts); - this.MAX_RECENT_SEARCHES = 4; - this.search_box_id = this.search_box_id || "#search-box"; - this.searchBox = $(this.search_box_id); - - this.setupSearchDropDown(); - this.bindSearchAction(); - } - - setupSearchDropDown() { - this.search_area = $("#dropdownMenuSearch"); - this.setupSearchResultContainer(); - this.populateRecentSearches(); - } - - bindSearchAction() { - let me = this; - - // Show Search dropdown - this.searchBox.on("focus", () => { - this.search_dropdown.removeClass("hidden"); - }); - - // If click occurs outside search input/results, hide results. - // Click can happen anywhere on the page - $("body").on("click", (e) => { - let searchEvent = $(e.target).closest(this.search_box_id).length; - let resultsEvent = $(e.target).closest('#search-results-container').length; - let isResultHidden = this.search_dropdown.hasClass("hidden"); - - if (!searchEvent && !resultsEvent && !isResultHidden) { - this.search_dropdown.addClass("hidden"); - } - }); - - // Process search input - this.searchBox.on("input", (e) => { - let query = e.target.value; - - if (query.length == 0) { - me.populateResults(null); - me.populateCategoriesList(null); - } - - if (query.length < 3 || !query.length) return; - - frappe.call({ - method: "erpnext.templates.pages.product_search.search", - args: { - query: query - }, - callback: (data) => { - let product_results = null, category_results = null; - - // Populate product results - product_results = data.message ? data.message.product_results : null; - me.populateResults(product_results); - - // Populate categories - if (me.category_container) { - category_results = data.message ? data.message.category_results : null; - me.populateCategoriesList(category_results); - } - - // Populate recent search chips only on successful queries - if (!$.isEmptyObject(product_results) || !$.isEmptyObject(category_results)) { - me.setRecentSearches(query); - } - } - }); - - this.search_dropdown.removeClass("hidden"); - }); - } - - setupSearchResultContainer() { - this.search_dropdown = this.search_area.append(` - - `).find("#search-results-container"); - - this.setupCategoryContainer(); - this.setupProductsContainer(); - this.setupRecentsContainer(); - } - - setupProductsContainer() { - this.products_container = this.search_dropdown.append(` -
-
-
-
- `).find("#product-scroll"); - } - - setupCategoryContainer() { - this.category_container = this.search_dropdown.append(` -
-
-
-
- `).find(".category-chips"); - } - - setupRecentsContainer() { - let $recents_section = this.search_dropdown.append(` -
-
- ${ __("Recent") } -
-
- `).find(".recent-searches"); - - this.recents_container = $recents_section.append(` -
-
- `).find("#recents"); - } - - getRecentSearches() { - return JSON.parse(localStorage.getItem("recent_searches") || "[]"); - } - - attachEventListenersToChips() { - let me = this; - const chips = $(".recent-search"); - window.chips = chips; - - for (let chip of chips) { - chip.addEventListener("click", () => { - me.searchBox[0].value = chip.innerText.trim(); - - // Start search with `recent query` - me.searchBox.trigger("input"); - me.searchBox.focus(); - }); - } - } - - setRecentSearches(query) { - let recents = this.getRecentSearches(); - if (recents.length >= this.MAX_RECENT_SEARCHES) { - // Remove the `first` query - recents.splice(0, 1); - } - - if (recents.indexOf(query) >= 0) { - return; - } - - recents.push(query); - localStorage.setItem("recent_searches", JSON.stringify(recents)); - - this.populateRecentSearches(); - } - - populateRecentSearches() { - let recents = this.getRecentSearches(); - - if (!recents.length) { - this.recents_container.html(`No searches yet.`); - return; - } - - let html = ""; - recents.forEach((key) => { - html += ` - - `; - }); - - this.recents_container.html(html); - this.attachEventListenersToChips(); - } - - populateResults(product_results) { - if (!product_results || product_results.length === 0) { - let empty_html = ``; - this.products_container.html(empty_html); - return; - } - - let html = ""; - - product_results.forEach((res) => { - let thumbnail = res.thumbnail || '/assets/erpnext/images/ui-states/cart-empty-state.png'; - html += ` - - `; - }); - - this.products_container.html(html); - } - - populateCategoriesList(category_results) { - if (!category_results || category_results.length === 0) { - let empty_html = ` -
-
-
-
- `; - this.category_container.html(empty_html); - return; - } - - let html = ` -
- ${ __("Categories") } -
- `; - - category_results.forEach((category) => { - html += ` - - ${ category.name } - - `; - }); - - this.category_container.html(html); - } -}; diff --git a/erpnext/e_commerce/product_ui/views.js b/erpnext/e_commerce/product_ui/views.js deleted file mode 100644 index fb63b21a08..0000000000 --- a/erpnext/e_commerce/product_ui/views.js +++ /dev/null @@ -1,548 +0,0 @@ -erpnext.ProductView = class { - /* Options: - - View Type - - Products Section Wrapper, - - Item Group: If its an Item Group page - */ - constructor(options) { - Object.assign(this, options); - this.preference = this.view_type; - this.make(); - } - - make(from_filters=false) { - this.products_section.empty(); - this.prepare_toolbar(); - this.get_item_filter_data(from_filters); - } - - prepare_toolbar() { - this.products_section.append(` -
-
- `); - this.prepare_search(); - this.prepare_view_toggler(); - - new erpnext.ProductSearch(); - } - - prepare_view_toggler() { - - if (!$("#list").length || !$("#image-view").length) { - this.render_view_toggler(); - this.bind_view_toggler_actions(); - this.set_view_state(); - } - } - - get_item_filter_data(from_filters=false) { - // Get and render all Product related views - let me = this; - this.from_filters = from_filters; - let args = this.get_query_filters(); - - this.disable_view_toggler(true); - - frappe.call({ - method: "erpnext.e_commerce.api.get_product_filter_data", - args: { - query_args: args - }, - callback: function(result) { - if (!result || result.exc || !result.message || result.message.exc) { - me.render_no_products_section(true); - } else { - // Sub Category results are independent of Items - if (me.item_group && result.message["sub_categories"].length) { - me.render_item_sub_categories(result.message["sub_categories"]); - } - - if (!result.message["items"].length) { - // if result has no items or result is empty - me.render_no_products_section(); - } else { - // Add discount filters - me.re_render_discount_filters(result.message["filters"].discount_filters); - - // Render views - me.render_list_view(result.message["items"], result.message["settings"]); - me.render_grid_view(result.message["items"], result.message["settings"]); - - me.products = result.message["items"]; - me.product_count = result.message["items_count"]; - } - - // Bind filter actions - if (!from_filters) { - // If `get_product_filter_data` was triggered after checking a filter, - // don't touch filters unnecessarily, only data must change - // filter persistence is handle on filter change event - me.bind_filters(); - me.restore_filters_state(); - } - - // Bottom paging - me.add_paging_section(result.message["settings"]); - } - - me.disable_view_toggler(false); - } - }); - } - - disable_view_toggler(disable=false) { - $('#list').prop('disabled', disable); - $('#image-view').prop('disabled', disable); - } - - render_grid_view(items, settings) { - // loop over data and add grid html to it - let me = this; - this.prepare_product_area_wrapper("grid"); - - new erpnext.ProductGrid({ - items: items, - products_section: $("#products-grid-area"), - settings: settings, - preference: me.preference - }); - } - - render_list_view(items, settings) { - let me = this; - this.prepare_product_area_wrapper("list"); - - new erpnext.ProductList({ - items: items, - products_section: $("#products-list-area"), - settings: settings, - preference: me.preference - }); - } - - prepare_product_area_wrapper(view) { - let left_margin = view == "list" ? "ml-2" : ""; - let top_margin = view == "list" ? "mt-6" : "mt-minus-1"; - return this.products_section.append(` -
-
- `); - } - - get_query_filters() { - const filters = frappe.utils.get_query_params(); - let {field_filters, attribute_filters} = filters; - - field_filters = field_filters ? JSON.parse(field_filters) : {}; - attribute_filters = attribute_filters ? JSON.parse(attribute_filters) : {}; - - return { - field_filters: field_filters, - attribute_filters: attribute_filters, - item_group: this.item_group, - start: filters.start || null, - from_filters: this.from_filters || false - }; - } - - add_paging_section(settings) { - $(".product-paging-area").remove(); - - if (this.products) { - let paging_html = ` -
-
-
-
- `; - let query_params = frappe.utils.get_query_params(); - let start = query_params.start ? cint(JSON.parse(query_params.start)) : 0; - let page_length = settings.products_per_page || 0; - - let prev_disable = start > 0 ? "" : "disabled"; - let next_disable = (this.product_count > page_length) ? "" : "disabled"; - - paging_html += ` - `; - - paging_html += ` - - `; - - paging_html += `
`; - - $(".page_content").append(paging_html); - this.bind_paging_action(); - } - } - - prepare_search() { - $(".toolbar").append(` -
- -
- `); - } - - render_view_toggler() { - $(".toolbar").append(`
`); - - ["btn-list-view", "btn-grid-view"].forEach(view => { - let icon = view === "btn-list-view" ? "list" : "image-view"; - $(".toggle-container").append(` -
- -
- `); - }); - } - - bind_view_toggler_actions() { - $("#list").click(function() { - let $btn = $(this); - $btn.removeClass('btn-primary'); - $btn.addClass('btn-primary'); - $(".btn-grid-view").removeClass('btn-primary'); - - $("#products-grid-area").addClass("hidden"); - $("#products-list-area").removeClass("hidden"); - localStorage.setItem("product_view", "List View"); - }); - - $("#image-view").click(function() { - let $btn = $(this); - $btn.removeClass('btn-primary'); - $btn.addClass('btn-primary'); - $(".btn-list-view").removeClass('btn-primary'); - - $("#products-list-area").addClass("hidden"); - $("#products-grid-area").removeClass("hidden"); - localStorage.setItem("product_view", "Grid View"); - }); - } - - set_view_state() { - if (this.preference === "List View") { - $("#list").addClass('btn-primary'); - $("#image-view").removeClass('btn-primary'); - } else { - $("#image-view").addClass('btn-primary'); - $("#list").removeClass('btn-primary'); - } - } - - bind_paging_action() { - let me = this; - $('.btn-prev, .btn-next').click((e) => { - const $btn = $(e.target); - me.from_filters = false; - - $btn.prop('disabled', true); - const start = $btn.data('start'); - - let query_params = frappe.utils.get_query_params(); - query_params.start = start; - let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params); - window.location.href = path; - }); - } - - re_render_discount_filters(filter_data) { - this.get_discount_filter_html(filter_data); - if (this.from_filters) { - // Bind filter action if triggered via filters - // if not from filter action, page load will bind actions - this.bind_discount_filter_action(); - } - // discount filters are rendered with Items (later) - // unlike the other filters - this.restore_discount_filter(); - } - - get_discount_filter_html(filter_data) { - $("#discount-filters").remove(); - if (filter_data) { - $("#product-filters").append(` -
-
${ __("Discounts") }
-
- `); - - let html = `
`; - filter_data.forEach(filter => { - html += ` -
- -
- `; - }); - html += `
`; - - $("#discount-filters").append(html); - } - } - - restore_discount_filter() { - const filters = frappe.utils.get_query_params(); - let field_filters = filters.field_filters; - if (!field_filters) return; - - field_filters = JSON.parse(field_filters); - - if (field_filters && field_filters["discount"]) { - const values = field_filters["discount"]; - const selector = values.map(value => { - return `input[data-filter-name="discount"][data-filter-value="${value}"]`; - }).join(','); - $(selector).prop('checked', true); - this.field_filters = field_filters; - } - } - - bind_discount_filter_action() { - let me = this; - $('.discount-filter').on('change', (e) => { - const $checkbox = $(e.target); - const is_checked = $checkbox.is(':checked'); - - const { - filterValue: filter_value - } = $checkbox.data(); - - delete this.field_filters["discount"]; - - if (is_checked) { - this.field_filters["discount"] = []; - this.field_filters["discount"].push(filter_value); - } - - if (this.field_filters["discount"].length === 0) { - delete this.field_filters["discount"]; - } - - me.change_route_with_filters(); - }); - } - - bind_filters() { - let me = this; - this.field_filters = {}; - this.attribute_filters = {}; - - $('.product-filter').on('change', (e) => { - me.from_filters = true; - - const $checkbox = $(e.target); - const is_checked = $checkbox.is(':checked'); - - if ($checkbox.is('.attribute-filter')) { - const { - attributeName: attribute_name, - attributeValue: attribute_value - } = $checkbox.data(); - - if (is_checked) { - this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || []; - this.attribute_filters[attribute_name].push(attribute_value); - } else { - this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || []; - this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value); - } - - if (this.attribute_filters[attribute_name].length === 0) { - delete this.attribute_filters[attribute_name]; - } - } else if ($checkbox.is('.field-filter') || $checkbox.is('.discount-filter')) { - const { - filterName: filter_name, - filterValue: filter_value - } = $checkbox.data(); - - if ($checkbox.is('.discount-filter')) { - // clear previous discount filter to accomodate new - delete this.field_filters["discount"]; - } - if (is_checked) { - this.field_filters[filter_name] = this.field_filters[filter_name] || []; - if (!in_list(this.field_filters[filter_name], filter_value)) { - this.field_filters[filter_name].push(filter_value); - } - } else { - this.field_filters[filter_name] = this.field_filters[filter_name] || []; - this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value); - } - - if (this.field_filters[filter_name].length === 0) { - delete this.field_filters[filter_name]; - } - } - - me.change_route_with_filters(); - }); - - // bind filter lookup input box - $('.filter-lookup-input').on('keydown', frappe.utils.debounce((e) => { - const $input = $(e.target); - const keyword = ($input.val() || '').toLowerCase(); - const $filter_options = $input.next('.filter-options'); - - $filter_options.find('.filter-lookup-wrapper').show(); - $filter_options.find('.filter-lookup-wrapper').each((i, el) => { - const $el = $(el); - const value = $el.data('value').toLowerCase(); - if (!value.includes(keyword)) { - $el.hide(); - } - }); - }, 300)); - } - - change_route_with_filters() { - let route_params = frappe.utils.get_query_params(); - - let start = this.if_key_exists(route_params.start) || 0; - if (this.from_filters) { - start = 0; // show items from first page if new filters are triggered - } - - const query_string = this.get_query_string({ - start: start, - field_filters: JSON.stringify(this.if_key_exists(this.field_filters)), - attribute_filters: JSON.stringify(this.if_key_exists(this.attribute_filters)), - }); - window.history.pushState('filters', '', `${location.pathname}?` + query_string); - - $('.page_content input').prop('disabled', true); - - this.make(true); - $('.page_content input').prop('disabled', false); - } - - restore_filters_state() { - const filters = frappe.utils.get_query_params(); - let {field_filters, attribute_filters} = filters; - - if (field_filters) { - field_filters = JSON.parse(field_filters); - for (let fieldname in field_filters) { - const values = field_filters[fieldname]; - const selector = values.map(value => { - return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`; - }).join(','); - $(selector).prop('checked', true); - } - this.field_filters = field_filters; - } - if (attribute_filters) { - attribute_filters = JSON.parse(attribute_filters); - for (let attribute in attribute_filters) { - const values = attribute_filters[attribute]; - const selector = values.map(value => { - return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`; - }).join(','); - $(selector).prop('checked', true); - } - this.attribute_filters = attribute_filters; - } - } - - render_no_products_section(error=false) { - let error_section = ` -
- Something went wrong. Please refresh or contact us. -
- `; - let no_results_section = ` -
-
- Empty Cart -
-
${ __('No products found') }

-
- `; - - this.products_section.append(error ? error_section : no_results_section); - } - - render_item_sub_categories(categories) { - if (categories && categories.length) { - let sub_group_html = ` -
`; - - $("#product-listing").prepend(sub_group_html); - } - } - - get_query_string(object) { - const url = new URLSearchParams(); - for (let key in object) { - const value = object[key]; - if (value) { - url.append(key, value); - } - } - return url.toString(); - } - - if_key_exists(obj) { - let exists = false; - for (let key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key]) { - exists = true; - break; - } - } - return exists ? obj : undefined; - } -}; \ No newline at end of file diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py deleted file mode 100644 index 87ca9bd83d..0000000000 --- a/erpnext/e_commerce/redisearch_utils.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import json - -import frappe -from frappe import _ -from frappe.utils.redis_wrapper import RedisWrapper -from redis import ResponseError -from redis.commands.search.field import TagField, TextField -from redis.commands.search.indexDefinition import IndexDefinition -from redis.commands.search.suggestion import Suggestion - -WEBSITE_ITEM_INDEX = "website_items_index" -WEBSITE_ITEM_KEY_PREFIX = "website_item:" -WEBSITE_ITEM_NAME_AUTOCOMPLETE = "website_items_name_dict" -WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = "website_items_category_dict" - - -def get_indexable_web_fields(): - "Return valid fields from Website Item that can be searched for." - web_item_meta = frappe.get_meta("Website Item", cached=True) - valid_fields = filter( - lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"), - web_item_meta.fields, - ) - - return [df.fieldname for df in valid_fields] - - -def is_redisearch_enabled(): - "Return True only if redisearch is loaded and enabled." - is_redisearch_enabled = frappe.db.get_single_value("E Commerce Settings", "is_redisearch_enabled") - return is_search_module_loaded() and is_redisearch_enabled - - -def is_search_module_loaded(): - try: - cache = frappe.cache() - for module in cache.module_list(): - if module.get(b"name") == b"search": - return True - except Exception: - return False # handling older redis versions - - -def if_redisearch_enabled(function): - "Decorator to check if Redisearch is enabled." - - def wrapper(*args, **kwargs): - if is_redisearch_enabled(): - func = function(*args, **kwargs) - return func - return - - return wrapper - - -def make_key(key): - return frappe.cache().make_key(key) - - -@if_redisearch_enabled -def create_website_items_index(): - "Creates Index Definition." - - redis = frappe.cache() - index = redis.ft(WEBSITE_ITEM_INDEX) - - try: - index.dropindex() # drop if already exists - except ResponseError: - # will most likely raise a ResponseError if index does not exist - # ignore and create index - pass - except Exception: - raise_redisearch_error() - - idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)]) - - # Index fields mentioned in e-commerce settings - idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields") - idx_fields = idx_fields.split(",") if idx_fields else [] - - if "web_item_name" in idx_fields: - idx_fields.remove("web_item_name") - - idx_fields = [to_search_field(f) for f in idx_fields] - - # TODO: sortable? - index.create_index( - [TextField("web_item_name", sortable=True)] + idx_fields, - definition=idx_def, - ) - - reindex_all_web_items() - define_autocomplete_dictionary() - - -def to_search_field(field): - if field == "tags": - return TagField("tags", separator=",") - - return TextField(field) - - -@if_redisearch_enabled -def insert_item_to_index(website_item_doc): - # Insert item to index - key = get_cache_key(website_item_doc.name) - cache = frappe.cache() - web_item = create_web_item_map(website_item_doc) - - for field, value in web_item.items(): - super(RedisWrapper, cache).hset(make_key(key), field, value) - - insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name) - - -@if_redisearch_enabled -def insert_to_name_ac(web_name, doc_name): - ac = frappe.cache().ft() - ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(web_name, payload=doc_name)) - - -def create_web_item_map(website_item_doc): - fields_to_index = get_fields_indexed() - web_item = {} - - for field in fields_to_index: - web_item[field] = website_item_doc.get(field) or "" - - return web_item - - -@if_redisearch_enabled -def update_index_for_item(website_item_doc): - # Reinsert to Cache - insert_item_to_index(website_item_doc) - define_autocomplete_dictionary() - - -@if_redisearch_enabled -def delete_item_from_index(website_item_doc): - cache = frappe.cache() - key = get_cache_key(website_item_doc.name) - - try: - cache.delete(key) - except Exception: - raise_redisearch_error() - - delete_from_ac_dict(website_item_doc) - return True - - -@if_redisearch_enabled -def delete_from_ac_dict(website_item_doc): - """Removes this items's name from autocomplete dictionary""" - ac = frappe.cache().ft() - ac.sugdel(website_item_doc.web_item_name) - - -@if_redisearch_enabled -def define_autocomplete_dictionary(): - """ - Defines/Redefines an autocomplete search dictionary for Website Item Name. - Also creats autocomplete dictionary for Published Item Groups. - """ - - cache = frappe.cache() - - # Delete both autocomplete dicts - try: - cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE)) - cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE)) - except Exception: - raise_redisearch_error() - - create_items_autocomplete_dict() - create_item_groups_autocomplete_dict() - - -@if_redisearch_enabled -def create_items_autocomplete_dict(): - "Add items as suggestions in Autocompleter." - - ac = frappe.cache().ft() - items = frappe.get_all( - "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1} - ) - for item in items: - ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(item.web_item_name)) - - -@if_redisearch_enabled -def create_item_groups_autocomplete_dict(): - "Add item groups with weightage as suggestions in Autocompleter." - - published_item_groups = frappe.get_all( - "Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1} - ) - if not published_item_groups: - return - - ac = frappe.cache().ft() - - for item_group in published_item_groups: - payload = json.dumps({"name": item_group.name, "route": item_group.route}) - ac.sugadd( - WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, - Suggestion( - string=item_group.name, - score=frappe.utils.flt(item_group.weightage) or 1.0, - payload=payload, # additional info that can be retrieved later - ), - ) - - -@if_redisearch_enabled -def reindex_all_web_items(): - items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True}) - - cache = frappe.cache() - for item in items: - web_item = create_web_item_map(item) - key = make_key(get_cache_key(item.name)) - - for field, value in web_item.items(): - super(RedisWrapper, cache).hset(key, field, value) - - -def get_cache_key(name): - name = frappe.scrub(name) - return f"{WEBSITE_ITEM_KEY_PREFIX}{name}" - - -def get_fields_indexed(): - fields_to_index = frappe.db.get_single_value("E Commerce Settings", "search_index_fields") - fields_to_index = fields_to_index.split(",") if fields_to_index else [] - - mandatory_fields = ["name", "web_item_name", "route", "thumbnail", "ranking"] - fields_to_index = fields_to_index + mandatory_fields - - return fields_to_index - - -def raise_redisearch_error(): - "Create an Error Log and raise error." - log = frappe.log_error("Redisearch Error") - log_link = frappe.utils.get_link_to_form("Error Log", log.name) - - frappe.throw( - msg=_("Something went wrong. Check {0}").format(log_link), title=_("Redisearch Error") - ) diff --git a/erpnext/e_commerce/shopping_cart/__init__.py b/erpnext/e_commerce/shopping_cart/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py deleted file mode 100644 index 7c7e169c52..0000000000 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ /dev/null @@ -1,721 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import frappe.defaults -from frappe import _, throw -from frappe.contacts.doctype.address.address import get_address_display -from frappe.contacts.doctype.contact.contact import get_contact_name -from frappe.utils import cint, cstr, flt, get_fullname -from frappe.utils.nestedset import get_root_of - -from erpnext.accounts.utils import get_account_name -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.utilities.product import get_web_item_qty_in_stock - - -class WebsitePriceListMissingError(frappe.ValidationError): - pass - - -def set_cart_count(quotation=None): - if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")): - if not quotation: - quotation = _get_cart_quotation() - cart_count = cstr(cint(quotation.get("total_qty"))) - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.set_cookie("cart_count", cart_count) - - -@frappe.whitelist() -def get_cart_quotation(doc=None): - party = get_party() - - if not doc: - quotation = _get_cart_quotation(party) - doc = quotation - set_cart_count(quotation) - - addresses = get_address_docs(party=party) - - if not doc.customer_address and addresses: - update_cart_address("billing", addresses[0].name) - - return { - "doc": decorate_quotation_doc(doc), - "shipping_addresses": get_shipping_addresses(party), - "billing_addresses": get_billing_addresses(party), - "shipping_rules": get_applicable_shipping_rules(party), - "cart_settings": frappe.get_cached_doc("E Commerce Settings"), - } - - -@frappe.whitelist() -def get_shipping_addresses(party=None): - if not party: - party = get_party() - addresses = get_address_docs(party=party) - return [ - {"name": address.name, "title": address.address_title, "display": address.display} - for address in addresses - if address.address_type == "Shipping" - ] - - -@frappe.whitelist() -def get_billing_addresses(party=None): - if not party: - party = get_party() - addresses = get_address_docs(party=party) - return [ - {"name": address.name, "title": address.address_title, "display": address.display} - for address in addresses - if address.address_type == "Billing" - ] - - -@frappe.whitelist() -def place_order(): - quotation = _get_cart_quotation() - cart_settings = frappe.db.get_value( - "E Commerce Settings", None, ["company", "allow_items_not_in_stock"], as_dict=1 - ) - quotation.company = cart_settings.company - - quotation.flags.ignore_permissions = True - quotation.submit() - - if quotation.quotation_to == "Lead" and quotation.party_name: - # company used to create customer accounts - frappe.defaults.set_user_default("company", quotation.company) - - if not (quotation.shipping_address_name or quotation.customer_address): - frappe.throw(_("Set Shipping Address or Billing Address")) - - from erpnext.selling.doctype.quotation.quotation import _make_sales_order - - sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True)) - sales_order.payment_schedule = [] - - if not cint(cart_settings.allow_items_not_in_stock): - for item in sales_order.get("items"): - item.warehouse = frappe.db.get_value( - "Website Item", {"item_code": item.item_code}, "website_warehouse" - ) - is_stock_item = frappe.db.get_value("Item", item.item_code, "is_stock_item") - - if is_stock_item: - item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse") - if not cint(item_stock.in_stock): - throw(_("{0} Not in Stock").format(item.item_code)) - if item.qty > item_stock.stock_qty: - throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty, item.item_code)) - - sales_order.flags.ignore_permissions = True - sales_order.insert() - sales_order.submit() - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.delete_cookie("cart_count") - - return sales_order.name - - -@frappe.whitelist() -def request_for_quotation(): - quotation = _get_cart_quotation() - quotation.flags.ignore_permissions = True - - if get_shopping_cart_settings().save_quotations_as_draft: - quotation.save() - else: - quotation.submit() - return quotation.name - - -@frappe.whitelist() -def update_cart(item_code, qty, additional_notes=None, with_items=False): - quotation = _get_cart_quotation() - - empty_card = False - qty = flt(qty) - if qty == 0: - quotation_items = quotation.get("items", {"item_code": ["!=", item_code]}) - if quotation_items: - quotation.set("items", quotation_items) - else: - empty_card = True - - else: - warehouse = frappe.get_cached_value( - "Website Item", {"item_code": item_code}, "website_warehouse" - ) - - quotation_items = quotation.get("items", {"item_code": item_code}) - if not quotation_items: - quotation.append( - "items", - { - "doctype": "Quotation Item", - "item_code": item_code, - "qty": qty, - "additional_notes": additional_notes, - "warehouse": warehouse, - }, - ) - else: - quotation_items[0].qty = qty - quotation_items[0].additional_notes = additional_notes - quotation_items[0].warehouse = warehouse - - apply_cart_settings(quotation=quotation) - - quotation.flags.ignore_permissions = True - quotation.payment_schedule = [] - if not empty_card: - quotation.save() - else: - quotation.delete() - quotation = None - - set_cart_count(quotation) - - if cint(with_items): - context = get_cart_quotation(quotation) - return { - "items": frappe.render_template("templates/includes/cart/cart_items.html", context), - "total": frappe.render_template("templates/includes/cart/cart_items_total.html", context), - "taxes_and_totals": frappe.render_template( - "templates/includes/cart/cart_payment_summary.html", context - ), - } - else: - return {"name": quotation.name} - - -@frappe.whitelist() -def get_shopping_cart_menu(context=None): - if not context: - context = get_cart_quotation() - - return frappe.render_template("templates/includes/cart/cart_dropdown.html", context) - - -@frappe.whitelist() -def add_new_address(doc): - doc = frappe.parse_json(doc) - doc.update({"doctype": "Address"}) - address = frappe.get_doc(doc) - address.save(ignore_permissions=True) - - return address - - -@frappe.whitelist(allow_guest=True) -def create_lead_for_item_inquiry(lead, subject, message): - lead = frappe.parse_json(lead) - lead_doc = frappe.new_doc("Lead") - for fieldname in ("lead_name", "company_name", "email_id", "phone"): - lead_doc.set(fieldname, lead.get(fieldname)) - - lead_doc.set("lead_owner", "") - - if not frappe.db.exists("Lead Source", "Product Inquiry"): - frappe.get_doc({"doctype": "Lead Source", "source_name": "Product Inquiry"}).insert( - ignore_permissions=True - ) - - lead_doc.set("source", "Product Inquiry") - - try: - lead_doc.save(ignore_permissions=True) - except frappe.exceptions.DuplicateEntryError: - frappe.clear_messages() - lead_doc = frappe.get_doc("Lead", {"email_id": lead["email_id"]}) - - lead_doc.add_comment( - "Comment", - text=""" -
-
{subject}
-

{message}

-
- """.format( - subject=subject, message=message - ), - ) - - return lead_doc - - -@frappe.whitelist() -def get_terms_and_conditions(terms_name): - return frappe.db.get_value("Terms and Conditions", terms_name, "terms") - - -@frappe.whitelist() -def update_cart_address(address_type, address_name): - quotation = _get_cart_quotation() - address_doc = frappe.get_doc("Address", address_name).as_dict() - address_display = get_address_display(address_doc) - - if address_type.lower() == "billing": - quotation.customer_address = address_name - quotation.address_display = address_display - quotation.shipping_address_name = quotation.shipping_address_name or address_name - address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None) - elif address_type.lower() == "shipping": - quotation.shipping_address_name = address_name - quotation.shipping_address = address_display - quotation.customer_address = quotation.customer_address or address_name - address_doc = next( - (doc for doc in get_shipping_addresses() if doc["name"] == address_name), None - ) - apply_cart_settings(quotation=quotation) - - quotation.flags.ignore_permissions = True - quotation.save() - - context = get_cart_quotation(quotation) - context["address"] = address_doc - - return { - "taxes": frappe.render_template("templates/includes/order/order_taxes.html", context), - "address": frappe.render_template("templates/includes/cart/address_card.html", context), - } - - -def guess_territory(): - territory = None - geoip_country = frappe.session.get("session_country") - if geoip_country: - territory = frappe.db.get_value("Territory", geoip_country) - - return ( - territory - or frappe.db.get_value("E Commerce Settings", None, "territory") - or get_root_of("Territory") - ) - - -def decorate_quotation_doc(doc): - for d in doc.get("items", []): - item_code = d.item_code - fields = ["web_item_name", "thumbnail", "website_image", "description", "route"] - - # Variant Item - if not frappe.db.exists("Website Item", {"item_code": item_code}): - variant_data = frappe.db.get_values( - "Item", - filters={"item_code": item_code}, - fieldname=["variant_of", "item_name", "image"], - as_dict=True, - )[0] - item_code = variant_data.variant_of - fields = fields[1:] - d.web_item_name = variant_data.item_name - - if variant_data.image: # get image from variant or template web item - d.thumbnail = variant_data.image - fields = fields[2:] - - d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True)) - website_warehouse = frappe.get_cached_value( - "Website Item", {"item_code": item_code}, "website_warehouse" - ) - d.warehouse = website_warehouse - - return doc - - -def _get_cart_quotation(party=None): - """Return the open Quotation of type "Shopping Cart" or make a new one""" - if not party: - party = get_party() - - quotation = frappe.get_all( - "Quotation", - fields=["name"], - filters={ - "party_name": party.name, - "contact_email": frappe.session.user, - "order_type": "Shopping Cart", - "docstatus": 0, - }, - order_by="modified desc", - limit_page_length=1, - ) - - if quotation: - qdoc = frappe.get_doc("Quotation", quotation[0].name) - else: - company = frappe.db.get_value("E Commerce Settings", None, ["company"]) - qdoc = frappe.get_doc( - { - "doctype": "Quotation", - "naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-", - "quotation_to": party.doctype, - "company": company, - "order_type": "Shopping Cart", - "status": "Draft", - "docstatus": 0, - "__islocal": 1, - "party_name": party.name, - } - ) - - qdoc.contact_person = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) - qdoc.contact_email = frappe.session.user - - qdoc.flags.ignore_permissions = True - qdoc.run_method("set_missing_values") - apply_cart_settings(party, qdoc) - - return qdoc - - -def update_party(fullname, company_name=None, mobile_no=None, phone=None): - party = get_party() - - party.customer_name = company_name or fullname - party.customer_type = "Company" if company_name else "Individual" - - contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) - contact = frappe.get_doc("Contact", contact_name) - contact.first_name = fullname - contact.last_name = None - contact.customer_name = party.customer_name - contact.mobile_no = mobile_no - contact.phone = phone - contact.flags.ignore_permissions = True - contact.save() - - party_doc = frappe.get_doc(party.as_dict()) - party_doc.flags.ignore_permissions = True - party_doc.save() - - qdoc = _get_cart_quotation(party) - if not qdoc.get("__islocal"): - qdoc.customer_name = company_name or fullname - qdoc.run_method("set_missing_lead_customer_details") - qdoc.flags.ignore_permissions = True - qdoc.save() - - -def apply_cart_settings(party=None, quotation=None): - if not party: - party = get_party() - if not quotation: - quotation = _get_cart_quotation(party) - - cart_settings = frappe.get_doc("E Commerce Settings") - - set_price_list_and_rate(quotation, cart_settings) - - quotation.run_method("calculate_taxes_and_totals") - - set_taxes(quotation, cart_settings) - - _apply_shipping_rule(party, quotation, cart_settings) - - -def set_price_list_and_rate(quotation, cart_settings): - """set price list based on billing territory""" - - _set_price_list(cart_settings, quotation) - - # reset values - quotation.price_list_currency = ( - quotation.currency - ) = quotation.plc_conversion_rate = quotation.conversion_rate = None - for item in quotation.get("items"): - item.price_list_rate = item.discount_percentage = item.rate = item.amount = None - - # refetch values - quotation.run_method("set_price_list_and_item_details") - - if hasattr(frappe.local, "cookie_manager"): - # set it in cookies for using in product page - frappe.local.cookie_manager.set_cookie("selling_price_list", quotation.selling_price_list) - - -def _set_price_list(cart_settings, quotation=None): - """Set price list based on customer or shopping cart default""" - from erpnext.accounts.party import get_default_price_list - - party_name = quotation.get("party_name") if quotation else get_party().get("name") - selling_price_list = None - - # check if default customer price list exists - if party_name and frappe.db.exists("Customer", party_name): - selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name)) - - # check default price list in shopping cart - if not selling_price_list: - selling_price_list = cart_settings.price_list - - if quotation: - quotation.selling_price_list = selling_price_list - - return selling_price_list - - -def set_taxes(quotation, cart_settings): - """set taxes based on billing territory""" - from erpnext.accounts.party import set_taxes - - customer_group = frappe.db.get_value("Customer", quotation.party_name, "customer_group") - - quotation.taxes_and_charges = set_taxes( - quotation.party_name, - "Customer", - quotation.transaction_date, - quotation.company, - customer_group=customer_group, - supplier_group=None, - tax_category=quotation.tax_category, - billing_address=quotation.customer_address, - shipping_address=quotation.shipping_address_name, - use_for_shopping_cart=1, - ) - # - # # clear table - quotation.set("taxes", []) - # - # # append taxes - quotation.append_taxes_from_master() - - -def get_party(user=None): - if not user: - user = frappe.session.user - - contact_name = get_contact_name(user) - party = None - - if contact_name: - contact = frappe.get_doc("Contact", contact_name) - if contact.links: - party_doctype = contact.links[0].link_doctype - party = contact.links[0].link_name - - cart_settings = frappe.get_doc("E Commerce Settings") - - debtors_account = "" - - if cart_settings.enable_checkout: - debtors_account = get_debtors_account(cart_settings) - - if party: - return frappe.get_doc(party_doctype, party) - - else: - if not cart_settings.enabled: - frappe.local.flags.redirect_location = "/contact" - raise frappe.Redirect - customer = frappe.new_doc("Customer") - fullname = get_fullname(user) - customer.update( - { - "customer_name": fullname, - "customer_type": "Individual", - "customer_group": get_shopping_cart_settings().default_customer_group, - "territory": get_root_of("Territory"), - } - ) - - customer.append("portal_users", {"user": user}) - - if debtors_account: - customer.update({"accounts": [{"company": cart_settings.company, "account": debtors_account}]}) - - customer.flags.ignore_mandatory = True - customer.insert(ignore_permissions=True) - - contact = frappe.new_doc("Contact") - contact.update({"first_name": fullname, "email_ids": [{"email_id": user, "is_primary": 1}]}) - contact.append("links", dict(link_doctype="Customer", link_name=customer.name)) - contact.flags.ignore_mandatory = True - contact.insert(ignore_permissions=True) - - return customer - - -def get_debtors_account(cart_settings): - if not cart_settings.payment_gateway_account: - frappe.throw(_("Payment Gateway Account not set"), _("Mandatory")) - - payment_gateway_account_currency = frappe.get_doc( - "Payment Gateway Account", cart_settings.payment_gateway_account - ).currency - - account_name = _("Debtors ({0})").format(payment_gateway_account_currency) - - debtors_account_name = get_account_name( - "Receivable", - "Asset", - is_group=0, - account_currency=payment_gateway_account_currency, - company=cart_settings.company, - ) - - if not debtors_account_name: - debtors_account = frappe.get_doc( - { - "doctype": "Account", - "account_type": "Receivable", - "root_type": "Asset", - "is_group": 0, - "parent_account": get_account_name( - root_type="Asset", is_group=1, company=cart_settings.company - ), - "account_name": account_name, - "currency": payment_gateway_account_currency, - } - ).insert(ignore_permissions=True) - - return debtors_account.name - - else: - return debtors_account_name - - -def get_address_docs( - doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20, party=None -): - if not party: - party = get_party() - - if not party: - return [] - - address_names = frappe.db.get_all( - "Dynamic Link", - fields=("parent"), - filters=dict(parenttype="Address", link_doctype=party.doctype, link_name=party.name), - ) - - out = [] - - for a in address_names: - address = frappe.get_doc("Address", a.parent) - address.display = get_address_display(address.as_dict()) - out.append(address) - - return out - - -@frappe.whitelist() -def apply_shipping_rule(shipping_rule): - quotation = _get_cart_quotation() - - quotation.shipping_rule = shipping_rule - - apply_cart_settings(quotation=quotation) - - quotation.flags.ignore_permissions = True - quotation.save() - - return get_cart_quotation(quotation) - - -def _apply_shipping_rule(party=None, quotation=None, cart_settings=None): - if not quotation.shipping_rule: - shipping_rules = get_shipping_rules(quotation, cart_settings) - - if not shipping_rules: - return - - elif quotation.shipping_rule not in shipping_rules: - quotation.shipping_rule = shipping_rules[0] - - if quotation.shipping_rule: - quotation.run_method("apply_shipping_rule") - quotation.run_method("calculate_taxes_and_totals") - - -def get_applicable_shipping_rules(party=None, quotation=None): - shipping_rules = get_shipping_rules(quotation) - - if shipping_rules: - # we need this in sorted order as per the position of the rule in the settings page - return [[rule, rule] for rule in shipping_rules] - - -def get_shipping_rules(quotation=None, cart_settings=None): - if not quotation: - quotation = _get_cart_quotation() - - shipping_rules = [] - if quotation.shipping_address_name: - country = frappe.db.get_value("Address", quotation.shipping_address_name, "country") - if country: - sr_country = frappe.qb.DocType("Shipping Rule Country") - sr = frappe.qb.DocType("Shipping Rule") - query = ( - frappe.qb.from_(sr_country) - .join(sr) - .on(sr.name == sr_country.parent) - .select(sr.name) - .distinct() - .where((sr_country.country == country) & (sr.disabled != 1)) - ) - result = query.run(as_list=True) - shipping_rules = [x[0] for x in result] - - return shipping_rules - - -def get_address_territory(address_name): - """Tries to match city, state and country of address to existing territory""" - territory = None - - if address_name: - address_fields = frappe.db.get_value("Address", address_name, ["city", "state", "country"]) - for value in address_fields: - territory = frappe.db.get_value("Territory", value) - if territory: - break - - return territory - - -def show_terms(doc): - return doc.tc_name - - -@frappe.whitelist(allow_guest=True) -def apply_coupon_code(applied_code, applied_referral_sales_partner): - quotation = True - - if not applied_code: - frappe.throw(_("Please enter a coupon code")) - - coupon_list = frappe.get_all("Coupon Code", filters={"coupon_code": applied_code}) - if not coupon_list: - frappe.throw(_("Please enter a valid coupon code")) - - coupon_name = coupon_list[0].name - - from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code - - validate_coupon_code(coupon_name) - quotation = _get_cart_quotation() - quotation.coupon_code = coupon_name - quotation.flags.ignore_permissions = True - quotation.save() - - if applied_referral_sales_partner: - sales_partner_list = frappe.get_all( - "Sales Partner", filters={"referral_code": applied_referral_sales_partner} - ) - if sales_partner_list: - sales_partner_name = sales_partner_list[0].name - quotation.referral_sales_partner = sales_partner_name - quotation.flags.ignore_permissions = True - quotation.save() - - return quotation diff --git a/erpnext/e_commerce/shopping_cart/product_info.py b/erpnext/e_commerce/shopping_cart/product_info.py deleted file mode 100644 index 0248ca73d7..0000000000 --- a/erpnext/e_commerce/shopping_cart/product_info.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, - show_quantity_in_website, -) -from erpnext.e_commerce.shopping_cart.cart import _get_cart_quotation, _set_price_list -from erpnext.utilities.product import ( - get_non_stock_item_status, - get_price, - get_web_item_qty_in_stock, -) - - -@frappe.whitelist(allow_guest=True) -def get_product_info_for_website(item_code, skip_quotation_creation=False): - """get product price / stock info for website""" - - cart_settings = get_shopping_cart_settings() - if not cart_settings.enabled: - # return settings even if cart is disabled - return frappe._dict({"product_info": {}, "cart_settings": cart_settings}) - - cart_quotation = frappe._dict() - if not skip_quotation_creation: - cart_quotation = _get_cart_quotation() - - selling_price_list = ( - cart_quotation.get("selling_price_list") - if cart_quotation - else _set_price_list(cart_settings, None) - ) - - price = {} - if cart_settings.show_price: - is_guest = frappe.session.user == "Guest" - # Show Price if logged in. - # If not logged in, check if price is hidden for guest. - if not is_guest or not cart_settings.hide_price_for_guest: - price = get_price( - item_code, selling_price_list, cart_settings.default_customer_group, cart_settings.company - ) - - stock_status = None - - if cart_settings.show_stock_availability: - on_backorder = frappe.get_cached_value("Website Item", {"item_code": item_code}, "on_backorder") - if on_backorder: - stock_status = frappe._dict({"on_backorder": True}) - else: - stock_status = get_web_item_qty_in_stock(item_code, "website_warehouse") - - product_info = { - "price": price, - "qty": 0, - "uom": frappe.db.get_value("Item", item_code, "stock_uom"), - "sales_uom": frappe.db.get_value("Item", item_code, "sales_uom"), - } - - if stock_status: - if stock_status.on_backorder: - product_info["on_backorder"] = True - else: - product_info["stock_qty"] = stock_status.stock_qty - product_info["in_stock"] = ( - stock_status.in_stock - if stock_status.is_stock_item - else get_non_stock_item_status(item_code, "website_warehouse") - ) - product_info["show_stock_qty"] = show_quantity_in_website() - - if product_info["price"]: - if frappe.session.user != "Guest": - item = cart_quotation.get({"item_code": item_code}) if cart_quotation else None - if item: - product_info["qty"] = item[0].qty - - return frappe._dict({"product_info": product_info, "cart_settings": cart_settings}) - - -def set_product_info_for_website(item): - """set product price uom for website""" - product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get( - "product_info" - ) - - if product_info: - item.update(product_info) - item["stock_uom"] = product_info.get("uom") - item["sales_uom"] = product_info.get("sales_uom") - if product_info.get("price"): - item["price_stock_uom"] = product_info.get("price").get("formatted_price") - item["price_sales_uom"] = product_info.get("price").get("formatted_price_sales_uom") - else: - item["price_stock_uom"] = "" - item["price_sales_uom"] = "" diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py deleted file mode 100644 index 8210f9743d..0000000000 --- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py +++ /dev/null @@ -1,398 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import unittest - -import frappe -from frappe.tests.utils import change_settings -from frappe.utils import add_months, cint, nowdate - -from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.shopping_cart.cart import ( - _get_cart_quotation, - get_cart_quotation, - get_party, - request_for_quotation, - update_cart, -) - - -class TestShoppingCart(unittest.TestCase): - """ - Note: - Shopping Cart == Quotation - """ - - def setUp(self): - frappe.set_user("Administrator") - self.enable_shopping_cart() - if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}): - make_website_item(frappe.get_cached_doc("Item", "_Test Item")) - - if not frappe.db.exists("Website Item", {"item_code": "_Test Item 2"}): - make_website_item(frappe.get_cached_doc("Item", "_Test Item 2")) - - def tearDown(self): - frappe.db.rollback() - frappe.set_user("Administrator") - self.disable_shopping_cart() - - @classmethod - def tearDownClass(cls): - frappe.db.sql("delete from `tabTax Rule`") - - def test_get_cart_new_user(self): - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - create_address_and_contact( - address_title="_Test Address for Customer 2", - first_name="_Test Contact for Customer 2", - email="test_contact_two_customer@example.com", - customer="_Test Customer 2", - ) - # test if lead is created and quotation with new lead is fetched - customer = frappe.get_doc("Customer", "_Test Customer 2") - quotation = _get_cart_quotation(party=customer) - self.assertEqual(quotation.quotation_to, "Customer") - self.assertEqual( - quotation.contact_person, - frappe.db.get_value("Contact", dict(email_id="test_contact_two_customer@example.com")), - ) - self.assertEqual(quotation.contact_email, frappe.session.user) - - return quotation - - def test_get_cart_customer(self, customer="_Test Customer 2"): - def validate_quotation(customer_name): - # test if quotation with customer is fetched - party = frappe.get_doc("Customer", customer_name) - quotation = _get_cart_quotation(party=party) - self.assertEqual(quotation.quotation_to, "Customer") - self.assertEqual(quotation.party_name, customer_name) - self.assertEqual(quotation.contact_email, frappe.session.user) - return quotation - - quotation = validate_quotation(customer) - return quotation - - def test_add_to_cart(self): - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - create_address_and_contact( - address_title="_Test Address for Customer 2", - first_name="_Test Contact for Customer 2", - email="test_contact_two_customer@example.com", - customer="_Test Customer 2", - ) - # clear existing quotations - self.clear_existing_quotations() - - # add first item - update_cart("_Test Item", 1) - - quotation = self.test_get_cart_customer("_Test Customer 2") - - self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") - self.assertEqual(quotation.get("items")[0].qty, 1) - self.assertEqual(quotation.get("items")[0].amount, 10) - - # add second item - update_cart("_Test Item 2", 1) - quotation = self.test_get_cart_customer("_Test Customer 2") - self.assertEqual(quotation.get("items")[1].item_code, "_Test Item 2") - self.assertEqual(quotation.get("items")[1].qty, 1) - self.assertEqual(quotation.get("items")[1].amount, 20) - - self.assertEqual(len(quotation.get("items")), 2) - - def test_update_cart(self): - # first, add to cart - self.test_add_to_cart() - - # update first item - update_cart("_Test Item", 5) - quotation = self.test_get_cart_customer("_Test Customer 2") - self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") - self.assertEqual(quotation.get("items")[0].qty, 5) - self.assertEqual(quotation.get("items")[0].amount, 50) - self.assertEqual(quotation.net_total, 70) - self.assertEqual(len(quotation.get("items")), 2) - - def test_remove_from_cart(self): - # first, add to cart - self.test_add_to_cart() - - # remove first item - update_cart("_Test Item", 0) - quotation = self.test_get_cart_customer("_Test Customer 2") - - self.assertEqual(quotation.get("items")[0].item_code, "_Test Item 2") - self.assertEqual(quotation.get("items")[0].qty, 1) - self.assertEqual(quotation.get("items")[0].amount, 20) - self.assertEqual(quotation.net_total, 20) - self.assertEqual(len(quotation.get("items")), 1) - - @unittest.skip("Flaky in CI") - def test_tax_rule(self): - self.create_tax_rule() - - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - create_address_and_contact( - address_title="_Test Address for Customer 2", - first_name="_Test Contact for Customer 2", - email="test_contact_two_customer@example.com", - customer="_Test Customer 2", - ) - - quotation = self.create_quotation() - - from erpnext.accounts.party import set_taxes - - tax_rule_master = set_taxes( - quotation.party_name, - "Customer", - None, - quotation.company, - customer_group=None, - supplier_group=None, - tax_category=quotation.tax_category, - billing_address=quotation.customer_address, - shipping_address=quotation.shipping_address_name, - use_for_shopping_cart=1, - ) - - self.assertEqual(quotation.taxes_and_charges, tax_rule_master) - self.assertEqual(quotation.total_taxes_and_charges, 1000.0) - - self.remove_test_quotation(quotation) - - @change_settings( - "E Commerce Settings", - { - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - "show_price": 1, - }, - ) - def test_add_item_variant_without_web_item_to_cart(self): - "Test adding Variants having no Website Items in cart via Template Web Item." - from erpnext.controllers.item_variant import create_variant - from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - from erpnext.stock.doctype.item.test_item import make_item - - template_item = make_item( - "Test-Tshirt-Temp", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}], - }, - ) - variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"}) - variant.save() - make_website_item(template_item) # publish template not variant - - update_cart("Test-Tshirt-Temp-S-R", 1) - - cart = get_cart_quotation() # test if cart page gets data without errors - doc = cart.get("doc") - - self.assertEqual(doc.get("items")[0].item_name, "Test-Tshirt-Temp-S-R") - - # test if items are rendered without error - frappe.render_template("templates/includes/cart/cart_items.html", cart) - - @change_settings("E Commerce Settings", {"save_quotations_as_draft": 1}) - def test_cart_without_checkout_and_draft_quotation(self): - "Test impact of 'save_quotations_as_draft' checkbox." - frappe.local.shopping_cart_settings = None - - # add item to cart - update_cart("_Test Item", 1) - quote_name = request_for_quotation() # Request for Quote - quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus")) - - self.assertEqual(quote_doctstatus, 0) - - frappe.db.set_single_value("E Commerce Settings", "save_quotations_as_draft", 0) - frappe.local.shopping_cart_settings = None - update_cart("_Test Item", 1) - quote_name = request_for_quotation() # Request for Quote - quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus")) - - self.assertEqual(quote_doctstatus, 1) - - def create_tax_rule(self): - tax_rule = frappe.get_test_records("Tax Rule")[0] - try: - frappe.get_doc(tax_rule).insert(ignore_if_duplicate=True) - except (frappe.DuplicateEntryError, ConflictingTaxRule): - pass - - def create_quotation(self): - quotation = frappe.new_doc("Quotation") - - values = { - "doctype": "Quotation", - "quotation_to": "Customer", - "order_type": "Shopping Cart", - "party_name": get_party(frappe.session.user).name, - "docstatus": 0, - "contact_email": frappe.session.user, - "selling_price_list": "_Test Price List Rest of the World", - "currency": "USD", - "taxes_and_charges": "_Test Tax 1 - _TC", - "conversion_rate": 1, - "transaction_date": nowdate(), - "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "_Test Item", "qty": 1}], - "taxes": frappe.get_doc("Sales Taxes and Charges Template", "_Test Tax 1 - _TC").taxes, - "company": "_Test Company", - } - - quotation.update(values) - - quotation.insert(ignore_permissions=True) - - return quotation - - def remove_test_quotation(self, quotation): - frappe.set_user("Administrator") - quotation.delete() - - # helper functions - def enable_shopping_cart(self): - settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings") - - settings.update( - { - "enabled": 1, - "company": "_Test Company", - "default_customer_group": "_Test Customer Group", - "quotation_series": "_T-Quotation-", - "price_list": "_Test Price List India", - } - ) - - # insert item price - if not frappe.db.get_value( - "Item Price", {"price_list": "_Test Price List India", "item_code": "_Test Item"} - ): - frappe.get_doc( - { - "doctype": "Item Price", - "price_list": "_Test Price List India", - "item_code": "_Test Item", - "price_list_rate": 10, - } - ).insert() - frappe.get_doc( - { - "doctype": "Item Price", - "price_list": "_Test Price List India", - "item_code": "_Test Item 2", - "price_list_rate": 20, - } - ).insert() - - settings.save() - frappe.local.shopping_cart_settings = None - - def disable_shopping_cart(self): - settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings") - settings.enabled = 0 - settings.save() - frappe.local.shopping_cart_settings = None - - def login_as_new_user(self): - self.create_user_if_not_exists("test_cart_user@example.com") - frappe.set_user("test_cart_user@example.com") - - def login_as_customer( - self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer" - ): - self.create_user_if_not_exists(email, name) - frappe.set_user(email) - - def clear_existing_quotations(self): - quotations = frappe.get_all( - "Quotation", - filters={"party_name": get_party().name, "order_type": "Shopping Cart", "docstatus": 0}, - order_by="modified desc", - pluck="name", - ) - - for quotation in quotations: - frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True) - - def create_user_if_not_exists(self, email, first_name=None): - if frappe.db.exists("User", email): - return - - user = frappe.get_doc( - { - "doctype": "User", - "user_type": "Website User", - "email": email, - "send_welcome_email": 0, - "first_name": first_name or email.split("@")[0], - } - ).insert(ignore_permissions=True) - - user.add_roles("Customer") - - -def create_address_and_contact(**kwargs): - if not frappe.db.get_value("Address", {"address_title": kwargs.get("address_title")}): - frappe.get_doc( - { - "doctype": "Address", - "address_title": kwargs.get("address_title"), - "address_type": kwargs.get("address_type") or "Office", - "address_line1": kwargs.get("address_line1") or "Station Road", - "city": kwargs.get("city") or "_Test City", - "state": kwargs.get("state") or "Test State", - "country": kwargs.get("country") or "India", - "links": [ - {"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"} - ], - } - ).insert() - - if not frappe.db.get_value("Contact", {"first_name": kwargs.get("first_name")}): - contact = frappe.get_doc( - { - "doctype": "Contact", - "first_name": kwargs.get("first_name"), - "links": [ - {"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"} - ], - } - ) - contact.add_email(kwargs.get("email") or "test_contact_customer@example.com", is_primary=True) - contact.add_phone(kwargs.get("phone") or "+91 0000000000", is_primary_phone=True) - contact.insert() - - -test_dependencies = [ - "Sales Taxes and Charges Template", - "Price List", - "Item Price", - "Shipping Rule", - "Currency Exchange", - "Customer Group", - "Lead", - "Customer", - "Contact", - "Address", - "Item", - "Tax Rule", -] diff --git a/erpnext/e_commerce/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py deleted file mode 100644 index 3d48c28dd1..0000000000 --- a/erpnext/e_commerce/shopping_cart/utils.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import is_cart_enabled - - -def show_cart_count(): - if ( - is_cart_enabled() - and frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User" - ): - return True - - return False - - -def set_cart_count(login_manager): - # since this is run only on hooks login event - # make sure user is already a customer - # before trying to set cart count - user_is_customer = is_customer() - if not user_is_customer: - return - - if show_cart_count(): - from erpnext.e_commerce.shopping_cart.cart import set_cart_count - - # set_cart_count will try to fetch existing cart quotation - # or create one if non existent (and create a customer too) - # cart count is calculated from this quotation's items - set_cart_count() - - -def clear_cart_count(login_manager): - if show_cart_count(): - frappe.local.cookie_manager.delete_cookie("cart_count") - - -def update_website_context(context): - cart_enabled = is_cart_enabled() - context["shopping_cart_enabled"] = cart_enabled - - -def is_customer(): - if frappe.session.user and frappe.session.user != "Guest": - contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user}) - if contact_name: - contact = frappe.get_doc("Contact", contact_name) - for link in contact.links: - if link.link_doctype == "Customer": - return True - - return False diff --git a/erpnext/e_commerce/variant_selector/__init__.py b/erpnext/e_commerce/variant_selector/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/variant_selector/item_variants_cache.py b/erpnext/e_commerce/variant_selector/item_variants_cache.py deleted file mode 100644 index f8439d5d43..0000000000 --- a/erpnext/e_commerce/variant_selector/item_variants_cache.py +++ /dev/null @@ -1,130 +0,0 @@ -import frappe - - -class ItemVariantsCacheManager: - def __init__(self, item_code): - self.item_code = item_code - - def get_item_variants_data(self): - val = frappe.cache().hget("item_variants_data", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("item_variants_data", self.item_code) - - def get_attribute_value_item_map(self): - val = frappe.cache().hget("attribute_value_item_map", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("attribute_value_item_map", self.item_code) - - def get_item_attribute_value_map(self): - val = frappe.cache().hget("item_attribute_value_map", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("item_attribute_value_map", self.item_code) - - def get_optional_attributes(self): - val = frappe.cache().hget("optional_attributes", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("optional_attributes", self.item_code) - - def get_ordered_attribute_values(self): - val = frappe.cache().get_value("ordered_attribute_values_map") - if val: - return val - - all_attribute_values = frappe.get_all( - "Item Attribute Value", ["attribute_value", "idx", "parent"], order_by="idx asc" - ) - - ordered_attribute_values_map = frappe._dict({}) - for d in all_attribute_values: - ordered_attribute_values_map.setdefault(d.parent, []).append(d.attribute_value) - - frappe.cache().set_value("ordered_attribute_values_map", ordered_attribute_values_map) - return ordered_attribute_values_map - - def build_cache(self): - parent_item_code = self.item_code - - attributes = [ - a.attribute - for a in frappe.get_all( - "Item Variant Attribute", {"parent": parent_item_code}, ["attribute"], order_by="idx asc" - ) - ] - - # Get Variants and tehir Attributes that are not disabled - iva = frappe.qb.DocType("Item Variant Attribute") - item = frappe.qb.DocType("Item") - query = ( - frappe.qb.from_(iva) - .join(item) - .on(item.name == iva.parent) - .select(iva.parent, iva.attribute, iva.attribute_value) - .where((iva.variant_of == parent_item_code) & (item.disabled == 0)) - .orderby(iva.name) - ) - item_variants_data = query.run() - - attribute_value_item_map = frappe._dict() - item_attribute_value_map = frappe._dict() - - for row in item_variants_data: - item_code, attribute, attribute_value = row - # (attr, value) => [item1, item2] - attribute_value_item_map.setdefault((attribute, attribute_value), []).append(item_code) - # item => {attr1: value1, attr2: value2} - item_attribute_value_map.setdefault(item_code, {})[attribute] = attribute_value - - optional_attributes = set() - for item_code, attr_dict in item_attribute_value_map.items(): - for attribute in attributes: - if attribute not in attr_dict: - optional_attributes.add(attribute) - - frappe.cache().hset("attribute_value_item_map", parent_item_code, attribute_value_item_map) - frappe.cache().hset("item_attribute_value_map", parent_item_code, item_attribute_value_map) - frappe.cache().hset("item_variants_data", parent_item_code, item_variants_data) - frappe.cache().hset("optional_attributes", parent_item_code, optional_attributes) - - def clear_cache(self): - keys = [ - "attribute_value_item_map", - "item_attribute_value_map", - "item_variants_data", - "optional_attributes", - ] - - for key in keys: - frappe.cache().hdel(key, self.item_code) - - def rebuild_cache(self): - self.clear_cache() - enqueue_build_cache(self.item_code) - - -def build_cache(item_code): - frappe.cache().hset("item_cache_build_in_progress", item_code, 1) - i = ItemVariantsCacheManager(item_code) - i.build_cache() - frappe.cache().hset("item_cache_build_in_progress", item_code, 0) - - -def enqueue_build_cache(item_code): - if frappe.cache().hget("item_cache_build_in_progress", item_code): - return - frappe.enqueue( - "erpnext.e_commerce.variant_selector.item_variants_cache.build_cache", - item_code=item_code, - queue="long", - ) diff --git a/erpnext/e_commerce/variant_selector/test_variant_selector.py b/erpnext/e_commerce/variant_selector/test_variant_selector.py deleted file mode 100644 index 8eb497c1b5..0000000000 --- a/erpnext/e_commerce/variant_selector/test_variant_selector.py +++ /dev/null @@ -1,125 +0,0 @@ -import frappe -from frappe.tests.utils import FrappeTestCase - -from erpnext.controllers.item_variant import create_variant -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values -from erpnext.stock.doctype.item.test_item import make_item - -test_dependencies = ["Item"] - - -class TestVariantSelector(FrappeTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - template_item = make_item( - "Test-Tshirt-Temp", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}], - }, - ) - - # create L-R, L-G, M-R, M-G and S-R - for size in ( - "Large", - "Medium", - ): - for colour in ( - "Red", - "Green", - ): - variant = create_variant("Test-Tshirt-Temp", {"Test Size": size, "Test Colour": colour}) - variant.save() - - variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"}) - variant.save() - - make_website_item(template_item) # publish template not variants - - def test_item_attributes(self): - """ - Test if the right attributes are fetched in the popup. - (Attributes must only come from active items) - - Attribute selection must not be linked to Website Items. - """ - from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values - - attr_data = get_attributes_and_values("Test-Tshirt-Temp") - - self.assertEqual(attr_data[0]["attribute"], "Test Size") - self.assertEqual(attr_data[1]["attribute"], "Test Colour") - self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large'] - self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green'] - - # disable small red tshirt, now there are no small tshirts. - # but there are some red tshirts - small_variant = frappe.get_doc("Item", "Test-Tshirt-Temp-S-R") - small_variant.disabled = 1 - small_variant.save() # trigger cache rebuild - - attr_data = get_attributes_and_values("Test-Tshirt-Temp") - - # Only L and M attribute values must be fetched since S is disabled - self.assertEqual(len(attr_data[0]["values"]), 2) # ['Medium', 'Large'] - - # teardown - small_variant.disabled = 0 - small_variant.save() - - def test_next_item_variant_values(self): - """ - Test if on selecting an attribute value, the next possible values - are filtered accordingly. - Values that dont apply should not be fetched. - E.g. - There is a ** Small-Red ** Tshirt. No other colour in this size. - On selecting ** Small **, only ** Red ** should be selectable next. - """ - next_values = get_next_attribute_and_values( - "Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"} - ) - next_colours = next_values["valid_options_for_attributes"]["Test Colour"] - filtered_items = next_values["filtered_items"] - - self.assertEqual(len(next_colours), 1) - self.assertEqual(next_colours.pop(), "Red") - self.assertEqual(len(filtered_items), 1) - self.assertEqual(filtered_items.pop(), "Test-Tshirt-Temp-S-R") - - def test_exact_match_with_price(self): - """ - Test price fetching and matching of variant without Website Item - """ - from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price - - frappe.set_user("Administrator") - setup_e_commerce_settings( - { - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - "show_price": 1, - } - ) - - make_web_item_price(item_code="Test-Tshirt-Temp-S-R", price_list_rate=100) - - frappe.local.shopping_cart_settings = None # clear cached settings values - next_values = get_next_attribute_and_values( - "Test-Tshirt-Temp", selected_attributes={"Test Size": "Small", "Test Colour": "Red"} - ) - print(">>>>", next_values) - price_info = next_values["product_info"]["price"] - - self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R") - self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R") - self.assertEqual(price_info["price_list_rate"], 100.0) - self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00") diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py deleted file mode 100644 index 88356f5e90..0000000000 --- a/erpnext/e_commerce/variant_selector/utils.py +++ /dev/null @@ -1,251 +0,0 @@ -import frappe -from frappe.utils import cint, flt - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.shopping_cart.cart import _set_price_list -from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager -from erpnext.utilities.product import get_price - - -def get_item_codes_by_attributes(attribute_filters, template_item_code=None): - items = [] - - for attribute, values in attribute_filters.items(): - attribute_values = values - - if not isinstance(attribute_values, list): - attribute_values = [attribute_values] - - if not attribute_values: - continue - - wheres = [] - query_values = [] - for attribute_value in attribute_values: - wheres.append("( attribute = %s and attribute_value = %s )") - query_values += [attribute, attribute_value] - - attribute_query = " or ".join(wheres) - - if template_item_code: - variant_of_query = "AND t2.variant_of = %s" - query_values.append(template_item_code) - else: - variant_of_query = "" - - query = """ - SELECT - t1.parent - FROM - `tabItem Variant Attribute` t1 - WHERE - 1 = 1 - AND ( - {attribute_query} - ) - AND EXISTS ( - SELECT - 1 - FROM - `tabItem` t2 - WHERE - t2.name = t1.parent - {variant_of_query} - ) - GROUP BY - t1.parent - ORDER BY - NULL - """.format( - attribute_query=attribute_query, variant_of_query=variant_of_query - ) - - item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep - items.append(item_codes) - - res = list(set.intersection(*items)) - - return res - - -@frappe.whitelist(allow_guest=True) -def get_attributes_and_values(item_code): - """Build a list of attributes and their possible values. - This will ignore the values upon selection of which there cannot exist one item. - """ - item_cache = ItemVariantsCacheManager(item_code) - item_variants_data = item_cache.get_item_variants_data() - - attributes = get_item_attributes(item_code) - attribute_list = [a.attribute for a in attributes] - - valid_options = {} - for item_code, attribute, attribute_value in item_variants_data: - if attribute in attribute_list: - valid_options.setdefault(attribute, set()).add(attribute_value) - - item_attribute_values = frappe.db.get_all( - "Item Attribute Value", ["parent", "attribute_value", "idx"], order_by="parent asc, idx asc" - ) - ordered_attribute_value_map = frappe._dict() - for iv in item_attribute_values: - ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value) - - # build attribute values in idx order - for attr in attributes: - valid_attribute_values = valid_options.get(attr.attribute, []) - ordered_values = ordered_attribute_value_map.get(attr.attribute, []) - attr["values"] = [v for v in ordered_values if v in valid_attribute_values] - - return attributes - - -@frappe.whitelist(allow_guest=True) -def get_next_attribute_and_values(item_code, selected_attributes): - from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses - - """Find the count of Items that match the selected attributes. - Also, find the attribute values that are not applicable for further searching. - If less than equal to 10 items are found, return item_codes of those items. - If one item is matched exactly, return item_code of that item. - """ - selected_attributes = frappe.parse_json(selected_attributes) - - item_cache = ItemVariantsCacheManager(item_code) - item_variants_data = item_cache.get_item_variants_data() - - attributes = get_item_attributes(item_code) - attribute_list = [a.attribute for a in attributes] - filtered_items = get_items_with_selected_attributes(item_code, selected_attributes) - - next_attribute = None - - for attribute in attribute_list: - if attribute not in selected_attributes: - next_attribute = attribute - break - - valid_options_for_attributes = frappe._dict() - - for a in attribute_list: - valid_options_for_attributes[a] = set() - - selected_attribute = selected_attributes.get(a, None) - if selected_attribute: - # already selected attribute values are valid options - valid_options_for_attributes[a].add(selected_attribute) - - for row in item_variants_data: - item_code, attribute, attribute_value = row - if ( - item_code in filtered_items - and attribute not in selected_attributes - and attribute in attribute_list - ): - valid_options_for_attributes[attribute].add(attribute_value) - - optional_attributes = item_cache.get_optional_attributes() - exact_match = [] - # search for exact match if all selected attributes are required attributes - if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)): - item_attribute_value_map = item_cache.get_item_attribute_value_map() - for item_code, attr_dict in item_attribute_value_map.items(): - if item_code in filtered_items and set(attr_dict.keys()) == set(selected_attributes.keys()): - exact_match.append(item_code) - - filtered_items_count = len(filtered_items) - - # get product info if exact match - # from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website - if exact_match: - cart_settings = get_shopping_cart_settings() - product_info = get_item_variant_price_dict(exact_match[0], cart_settings) - - if product_info: - product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item") - product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock) - else: - product_info = None - - product_id = "" - warehouse = "" - if exact_match or filtered_items: - if exact_match and len(exact_match) == 1: - product_id = exact_match[0] - elif filtered_items_count == 1: - product_id = list(filtered_items)[0] - - if product_id: - warehouse = frappe.get_cached_value( - "Website Item", {"item_code": product_id}, "website_warehouse" - ) - - available_qty = 0.0 - if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: - warehouses = get_child_warehouses(warehouse) - else: - warehouses = [warehouse] if warehouse else [] - - for warehouse in warehouses: - available_qty += flt( - frappe.db.get_value("Bin", {"item_code": product_id, "warehouse": warehouse}, "actual_qty") - ) - - return { - "next_attribute": next_attribute, - "valid_options_for_attributes": valid_options_for_attributes, - "filtered_items_count": filtered_items_count, - "filtered_items": filtered_items if filtered_items_count < 10 else [], - "exact_match": exact_match, - "product_info": product_info, - "available_qty": available_qty, - } - - -def get_items_with_selected_attributes(item_code, selected_attributes): - item_cache = ItemVariantsCacheManager(item_code) - attribute_value_item_map = item_cache.get_attribute_value_item_map() - - items = [] - for attribute, value in selected_attributes.items(): - filtered_items = attribute_value_item_map.get((attribute, value), []) - items.append(set(filtered_items)) - - return set.intersection(*items) - - -# utilities - - -def get_item_attributes(item_code): - attributes = frappe.db.get_all( - "Item Variant Attribute", - fields=["attribute"], - filters={"parenttype": "Item", "parent": item_code}, - order_by="idx asc", - ) - - optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes() - - for a in attributes: - if a.attribute in optional_attributes: - a.optional = True - - return attributes - - -def get_item_variant_price_dict(item_code, cart_settings): - if cart_settings.enabled and cart_settings.show_price: - is_guest = frappe.session.user == "Guest" - # Show Price if logged in. - # If not logged in, check if price is hidden for guest. - if not is_guest or not cart_settings.hide_price_for_guest: - price_list = _set_price_list(cart_settings, None) - price = get_price( - item_code, price_list, cart_settings.default_customer_group, cart_settings.company - ) - return {"price": price} - - return None diff --git a/erpnext/e_commerce/web_template/__init__.py b/erpnext/e_commerce/web_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/web_template/hero_slider/__init__.py b/erpnext/e_commerce/web_template/hero_slider/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html deleted file mode 100644 index fe4fee375b..0000000000 --- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html +++ /dev/null @@ -1,86 +0,0 @@ -{%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%} -{%- set align_class = resolve_class({ - 'text-right': align == 'Right', - 'text-center': align == 'Centre', - 'text-left': align == 'Left', -}) -%} - -{%- set heading_class = resolve_class({ - 'text-white': theme == 'Dark', - '': theme == 'Light', -}) -%} - -{%- endmacro -%} - -{%- set hero_slider_id = 'id-' + frappe.utils.generate_hash('HeroSlider', 12) -%} - - - - diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json deleted file mode 100644 index 39b2b3eaeb..0000000000 --- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json +++ /dev/null @@ -1,288 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:21:51.207221", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "slider_name", - "fieldtype": "Data", - "label": "Slider Name", - "reqd": 1 - }, - { - "default": "1", - "fieldname": "show_indicators", - "fieldtype": "Check", - "label": "Show Indicators", - "reqd": 0 - }, - { - "default": "1", - "fieldname": "show_controls", - "fieldtype": "Check", - "label": "Show Controls", - "reqd": 0 - }, - { - "fieldname": "slide_1", - "fieldtype": "Section Break", - "label": "Slide 1", - "reqd": 0 - }, - { - "fieldname": "slide_1_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_1_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_1_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_1_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_1_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_1_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_1_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_2", - "fieldtype": "Section Break", - "label": "Slide 2", - "reqd": 0 - }, - { - "fieldname": "slide_2_image", - "fieldtype": "Attach Image", - "label": "Image ", - "reqd": 0 - }, - { - "fieldname": "slide_2_title", - "fieldtype": "Data", - "label": "Title ", - "reqd": 0 - }, - { - "fieldname": "slide_2_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle ", - "reqd": 0 - }, - { - "fieldname": "slide_2_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label ", - "reqd": 0 - }, - { - "fieldname": "slide_2_primary_action", - "fieldtype": "Data", - "label": "Primary Action ", - "reqd": 0 - }, - { - "default": "Left", - "fieldname": "slide_2_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_2_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_3", - "fieldtype": "Section Break", - "label": "Slide 3", - "reqd": 0 - }, - { - "fieldname": "slide_3_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_3_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_3_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_3_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_3_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_3_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_3_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_4", - "fieldtype": "Section Break", - "label": "Slide 4", - "reqd": 0 - }, - { - "fieldname": "slide_4_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_4_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_4_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_4_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_4_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_4_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_4_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_5", - "fieldtype": "Section Break", - "label": "Slide 5", - "reqd": 0 - }, - { - "fieldname": "slide_5_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_5_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_5_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_5_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_5_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_5_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_5_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - } - ], - "idx": 2, - "modified": "2023-05-12 15:03:57.604060", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Hero Slider", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Section" -} \ No newline at end of file diff --git a/erpnext/e_commerce/web_template/item_card_group/__init__.py b/erpnext/e_commerce/web_template/item_card_group/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html deleted file mode 100644 index 07952f056a..0000000000 --- a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html +++ /dev/null @@ -1,37 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %} - -
-
-
- {%- if title -%} -

{{ title }}

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

{{ subtitle }}

- {%- endif -%} -
-
- {%- if primary_action -%} - - {{ primary_action_label }} - - {%- endif -%} -
-
- -
- {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] -%} - {%- set item = values['card_' + index + '_item'] -%} - {%- if item -%} - {%- set web_item = frappe.get_doc("Website Item", item) -%} - {{ item_card( - web_item, is_featured=values['card_' + index + '_featured'], - is_full_width=True, align="Center" - ) }} - {%- endif -%} - {%- endfor -%} -
-
- - diff --git a/erpnext/e_commerce/web_template/item_card_group/item_card_group.json b/erpnext/e_commerce/web_template/item_card_group/item_card_group.json deleted file mode 100644 index ad9e2a7b24..0000000000 --- a/erpnext/e_commerce/web_template/item_card_group/item_card_group.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:35:05.285322", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "title", - "fieldtype": "Data", - "label": "Title", - "reqd": 1 - }, - { - "fieldname": "subtitle", - "fieldtype": "Data", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "card_1", - "fieldtype": "Section Break", - "label": "Card 1", - "reqd": 0 - }, - { - "fieldname": "card_1_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_1_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_2", - "fieldtype": "Section Break", - "label": "Card 2", - "reqd": 0 - }, - { - "fieldname": "card_2_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_2_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_3", - "fieldtype": "Section Break", - "label": "Card 3", - "options": "", - "reqd": 0 - }, - { - "fieldname": "card_3_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_3_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_4", - "fieldtype": "Section Break", - "label": "Card 4", - "reqd": 0 - }, - { - "fieldname": "card_4_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_4_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_5", - "fieldtype": "Section Break", - "label": "Card 5", - "reqd": 0 - }, - { - "fieldname": "card_5_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_5_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_6", - "fieldtype": "Section Break", - "label": "Card 6", - "reqd": 0 - }, - { - "fieldname": "card_6_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_6_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_7", - "fieldtype": "Section Break", - "label": "Card 7", - "reqd": 0 - }, - { - "fieldname": "card_7_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_7_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_8", - "fieldtype": "Section Break", - "label": "Card 8", - "reqd": 0 - }, - { - "fieldname": "card_8_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_8_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_9", - "fieldtype": "Section Break", - "label": "Card 9", - "reqd": 0 - }, - { - "fieldname": "card_9_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_9_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_10", - "fieldtype": "Section Break", - "label": "Card 10", - "reqd": 0 - }, - { - "fieldname": "card_10_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_10_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_11", - "fieldtype": "Section Break", - "label": "Card 11", - "reqd": 0 - }, - { - "fieldname": "card_11_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_11_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_12", - "fieldtype": "Section Break", - "label": "Card 12", - "reqd": 0 - }, - { - "fieldname": "card_12_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_12_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - } - ], - "idx": 0, - "modified": "2021-12-21 14:44:59.821335", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Item Card Group", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Section" -} \ No newline at end of file diff --git a/erpnext/e_commerce/web_template/product_card/__init__.py b/erpnext/e_commerce/web_template/product_card/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/web_template/product_card/product_card.html b/erpnext/e_commerce/web_template/product_card/product_card.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/web_template/product_card/product_card.json b/erpnext/e_commerce/web_template/product_card/product_card.json deleted file mode 100644 index 2eb73741ef..0000000000 --- a/erpnext/e_commerce/web_template/product_card/product_card.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:28:47.809342", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "item", - "fieldtype": "Link", - "label": "Item", - "options": "Item", - "reqd": 0 - }, - { - "fieldname": "featured", - "fieldtype": "Check", - "label": "Featured", - "options": "", - "reqd": 0 - } - ], - "idx": 0, - "modified": "2021-02-24 16:05:17.926610", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Product Card", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Component" -} \ No newline at end of file diff --git a/erpnext/e_commerce/web_template/product_category_cards/__init__.py b/erpnext/e_commerce/web_template/product_category_cards/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html deleted file mode 100644 index 6d75a8b1d5..0000000000 --- a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html +++ /dev/null @@ -1,47 +0,0 @@ -{%- macro card(title, image, url, text_primary=False) -%} -{%- set align_class = resolve_class({ - 'text-right': text_primary, - 'text-centre': align == 'Center', - 'text-left': align == 'Left', -}) -%} -
- {% if image %} - {{ title }} - {% else %} -
- - {{ frappe.utils.get_abbr(title or '') }} - -
- {% endif %} - -
- {{ title or '' }} -
- -
-{%- endmacro -%} - -
- {%- if title -%} -

{{ title }}

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

{{ subtitle }}

- {%- endif -%} - -
-
- {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8'] -%} - {%- set category = values['category_' + index] -%} - {%- if category -%} - {%- set category = frappe.get_doc("Item Group", category) -%} - {{ card(category.name, category.image, category.route) }} - {%- endif -%} - {%- endfor -%} -
-
-
- - diff --git a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json deleted file mode 100644 index 0202165d08..0000000000 --- a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:25:50.855934", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "title", - "fieldtype": "Data", - "label": "Title", - "reqd": 1 - }, - { - "fieldname": "subtitle", - "fieldtype": "Data", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "category_1", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_2", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_3", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_4", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_5", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_6", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_7", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_8", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - } - ], - "idx": 0, - "modified": "2021-02-24 16:03:33.835635", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Product Category Cards", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Section" -} \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2155699a4c..7446f2cc36 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -52,11 +52,7 @@ leaderboards = "erpnext.startup.leaderboard.get_leaderboards" filters_config = "erpnext.startup.filters.get_filters_config" additional_print_settings = "erpnext.controllers.print_settings.get_print_settings" -on_session_creation = [ - "erpnext.portal.utils.create_customer_or_supplier", - "erpnext.e_commerce.shopping_cart.utils.set_cart_count", -] -on_logout = "erpnext.e_commerce.shopping_cart.utils.clear_cart_count" +on_session_creation = "erpnext.portal.utils.create_customer_or_supplier" treeviews = [ "Account", @@ -90,15 +86,11 @@ jinja = { } # website -update_website_context = [ - "erpnext.e_commerce.shopping_cart.utils.update_website_context", -] -my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context" webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context" calendars = ["Task", "Work Order", "Sales Order", "Holiday List", "ToDo"] -website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"] +website_generators = ["BOM", "Sales Partner"] website_context = { "favicon": "/assets/erpnext/images/erpnext-favicon.svg", @@ -349,9 +341,6 @@ doc_events = { "Event": { "after_insert": "erpnext.crm.utils.link_events_with_prospect", }, - "Sales Taxes and Charges Template": { - "on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings" - }, "Sales Invoice": { "on_submit": [ "erpnext.regional.create_transaction_log", diff --git a/erpnext/modules.txt b/erpnext/modules.txt index dcb421298d..c53cdf467d 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -17,5 +17,4 @@ Quality Management Communication Telephony Bulk Transaction -E-commerce -Subcontracting \ No newline at end of file +Subcontracting diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8f2d076b53..aebad557dd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -223,9 +223,6 @@ execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Catego erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021 erpnext.patches.v13_0.fix_invoice_statuses -erpnext.patches.v13_0.create_website_items #30-09-2021 -erpnext.patches.v13_0.populate_e_commerce_settings -erpnext.patches.v13_0.make_homepage_products_website_items erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields @@ -242,7 +239,6 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate -erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit erpnext.patches.v14_0.migrate_crm_settings erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty @@ -257,6 +253,7 @@ erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair erpnext.patches.v15_0.delete_taxjar_doctypes +erpnext.patches.v15_0.delete_ecommerce_doctypes erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v15_0.saudi_depreciation_warning @@ -277,8 +274,6 @@ erpnext.patches.v14_0.delete_datev_doctypes erpnext.patches.v14_0.rearrange_company_fields erpnext.patches.v13_0.update_sane_transfer_against erpnext.patches.v14_0.migrate_cost_center_allocations -erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template -erpnext.patches.v13_0.shopping_cart_to_ecommerce erpnext.patches.v13_0.update_reserved_qty_closed_wo erpnext.patches.v13_0.update_exchange_rate_settings erpnext.patches.v14_0.delete_amazon_mws_doctype @@ -288,7 +283,6 @@ erpnext.patches.v14_0.update_batch_valuation_flag erpnext.patches.v14_0.delete_non_profit_doctypes erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 -erpnext.patches.v13_0.copy_custom_field_filters_to_website_item erpnext.patches.v13_0.change_default_item_manufacturer_fieldtype erpnext.patches.v13_0.requeue_recoverable_reposts erpnext.patches.v14_0.discount_accounting_separation @@ -346,4 +340,4 @@ erpnext.patches.v14_0.update_invoicing_period_in_subscription execute:frappe.delete_doc("Page", "welcome-to-erpnext") erpnext.patches.v15_0.delete_payment_gateway_doctypes # below migration patch should always run last -erpnext.patches.v14_0.migrate_gl_to_payment_ledger +erpnext.patches.v14_0.migrate_gl_to_payment_ledger \ No newline at end of file diff --git a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py b/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py deleted file mode 100644 index 1bac0fdbf0..0000000000 --- a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py +++ /dev/null @@ -1,60 +0,0 @@ -import json -from typing import List, Union - -import frappe - -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - - -def execute(): - """ - Convert all Item links to Website Item link values in - exisitng 'Item Card Group' Web Page Block data. - """ - frappe.reload_doc("e_commerce", "web_template", "item_card_group") - - blocks = frappe.db.get_all( - "Web Page Block", - filters={"web_template": "Item Card Group"}, - fields=["parent", "web_template_values", "name"], - ) - - fields = generate_fields_to_edit() - - for block in blocks: - web_template_value = json.loads(block.get("web_template_values")) - - for field in fields: - item = web_template_value.get(field) - if not item: - continue - - if frappe.db.exists("Website Item", {"item_code": item}): - website_item = frappe.db.get_value("Website Item", {"item_code": item}) - else: - website_item = make_new_website_item(item) - - if website_item: - web_template_value[field] = website_item - - frappe.db.set_value( - "Web Page Block", block.name, "web_template_values", json.dumps(web_template_value) - ) - - -def generate_fields_to_edit() -> List: - fields = [] - for i in range(1, 13): - fields.append(f"card_{i}_item") # fields like 'card_1_item', etc. - - return fields - - -def make_new_website_item(item: str) -> Union[str, None]: - try: - doc = frappe.get_doc("Item", item) - web_item = make_website_item(doc) # returns [website_item.name, item_name] - return web_item[0] - except Exception: - doc.log_error("Website Item creation failed") - return None diff --git a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py deleted file mode 100644 index 4ad572fdb0..0000000000 --- a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py +++ /dev/null @@ -1,94 +0,0 @@ -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_field - - -def execute(): - "Add Field Filters, that are not standard fields in Website Item, as Custom Fields." - - def move_table_multiselect_data(docfield): - "Copy child table data (Table Multiselect) from Item to Website Item for a docfield." - table_multiselect_data = get_table_multiselect_data(docfield) - field = docfield.fieldname - - for row in table_multiselect_data: - # add copied multiselect data rows in Website Item - web_item = frappe.db.get_value("Website Item", {"item_code": row.parent}) - web_item_doc = frappe.get_doc("Website Item", web_item) - - child_doc = frappe.new_doc(docfield.options, parent_doc=web_item_doc, parentfield=field) - - for field in ["name", "creation", "modified", "idx"]: - row[field] = None - - child_doc.update(row) - - child_doc.parenttype = "Website Item" - child_doc.parent = web_item - - child_doc.insert() - - def get_table_multiselect_data(docfield): - child_table = frappe.qb.DocType(docfield.options) - item = frappe.qb.DocType("Item") - - table_multiselect_data = ( # query table data for field - frappe.qb.from_(child_table) - .join(item) - .on(item.item_code == child_table.parent) - .select(child_table.star) - .where((child_table.parentfield == docfield.fieldname) & (item.published_in_website == 1)) - ).run(as_dict=True) - - return table_multiselect_data - - settings = frappe.get_doc("E Commerce Settings") - - if not (settings.enable_field_filters or settings.filter_fields): - return - - item_meta = frappe.get_meta("Item") - valid_item_fields = [ - df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] - ] - - web_item_meta = frappe.get_meta("Website Item") - valid_web_item_fields = [ - df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] - ] - - for row in settings.filter_fields: - # skip if illegal field - if row.fieldname not in valid_item_fields: - continue - - # if Item field is not in Website Item, add it as a custom field - if row.fieldname not in valid_web_item_fields: - df = item_meta.get_field(row.fieldname) - create_custom_field( - "Website Item", - dict( - owner="Administrator", - fieldname=df.fieldname, - label=df.label, - fieldtype=df.fieldtype, - options=df.options, - description=df.description, - read_only=df.read_only, - no_copy=df.no_copy, - insert_after="on_backorder", - ), - ) - - # map field values - if df.fieldtype == "Table MultiSelect": - move_table_multiselect_data(df) - else: - frappe.db.sql( # nosemgrep - """ - UPDATE `tabWebsite Item` wi, `tabItem` i - SET wi.{0} = i.{0} - WHERE wi.item_code = i.item_code - """.format( - row.fieldname - ) - ) diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py deleted file mode 100644 index b010f0ecc6..0000000000 --- a/erpnext/patches/v13_0/create_website_items.py +++ /dev/null @@ -1,85 +0,0 @@ -import frappe - -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - - -def execute(): - frappe.reload_doc("e_commerce", "doctype", "website_item") - frappe.reload_doc("e_commerce", "doctype", "website_item_tabbed_section") - frappe.reload_doc("e_commerce", "doctype", "website_offer") - frappe.reload_doc("e_commerce", "doctype", "recommended_items") - frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings") - frappe.reload_doc("stock", "doctype", "item") - - item_fields = [ - "item_code", - "item_name", - "item_group", - "stock_uom", - "brand", - "has_variants", - "variant_of", - "description", - "weightage", - ] - web_fields_to_map = [ - "route", - "slideshow", - "website_image_alt", - "website_warehouse", - "web_long_description", - "website_content", - "website_image", - "thumbnail", - ] - - # get all valid columns (fields) from Item master DB schema - item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) # nosemgrep - item_table_fields = [d.get("Field") for d in item_table_fields] - - # prepare fields to query from Item, check if the web field exists in Item master - web_query_fields = [] - for web_field in web_fields_to_map: - if web_field in item_table_fields: - web_query_fields.append(web_field) - item_fields.append(web_field) - - # check if the filter fields exist in Item master - or_filters = {} - for field in ["show_in_website", "show_variant_in_website"]: - if field in item_table_fields: - or_filters[field] = 1 - - if not web_query_fields or not or_filters: - # web fields to map are not present in Item master schema - # most likely a fresh installation that doesnt need this patch - return - - items = frappe.db.get_all("Item", fields=item_fields, or_filters=or_filters) - total_count = len(items) - - for count, item in enumerate(items, start=1): - if frappe.db.exists("Website Item", {"item_code": item.item_code}): - continue - - # make new website item from item (publish item) - website_item = make_website_item(item, save=False) - website_item.ranking = item.get("weightage") - - for field in web_fields_to_map: - website_item.update({field: item.get(field)}) - - website_item.save() - - # move Website Item Group & Website Specification table to Website Item - for doctype in ("Website Item Group", "Item Website Specification"): - frappe.db.set_value( - doctype, - {"parenttype": "Item", "parent": item.item_code}, # filters - {"parenttype": "Website Item", "parent": website_item.name}, # value dict - ) - - if count % 20 == 0: # commit after every 20 items - frappe.db.commit() - - frappe.utils.update_progress_bar("Creating Website Items", count, total_count) diff --git a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py deleted file mode 100644 index 9197d86058..0000000000 --- a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py +++ /dev/null @@ -1,11 +0,0 @@ -import frappe - - -def execute(): - if frappe.db.has_column("Item", "thumbnail"): - website_item = frappe.qb.DocType("Website Item").as_("wi") - item = frappe.qb.DocType("Item") - - frappe.qb.update(website_item).inner_join(item).on(website_item.item_code == item.item_code).set( - website_item.thumbnail, item.thumbnail - ).where(website_item.website_image.notnull() & website_item.thumbnail.isnull()).run() diff --git a/erpnext/patches/v13_0/make_homepage_products_website_items.py b/erpnext/patches/v13_0/make_homepage_products_website_items.py deleted file mode 100644 index 50bfd358ea..0000000000 --- a/erpnext/patches/v13_0/make_homepage_products_website_items.py +++ /dev/null @@ -1,15 +0,0 @@ -import frappe - - -def execute(): - homepage = frappe.get_doc("Homepage") - - for row in homepage.products: - web_item = frappe.db.get_value("Website Item", {"item_code": row.item_code}, "name") - if not web_item: - continue - - row.item_code = web_item - - homepage.flags.ignore_mandatory = True - homepage.save() diff --git a/erpnext/patches/v13_0/populate_e_commerce_settings.py b/erpnext/patches/v13_0/populate_e_commerce_settings.py deleted file mode 100644 index ecf512b011..0000000000 --- a/erpnext/patches/v13_0/populate_e_commerce_settings.py +++ /dev/null @@ -1,68 +0,0 @@ -import frappe -from frappe.utils import cint - - -def execute(): - frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings") - frappe.reload_doc("portal", "doctype", "website_filter_field") - frappe.reload_doc("portal", "doctype", "website_attribute") - - products_settings_fields = [ - "hide_variants", - "products_per_page", - "enable_attribute_filters", - "enable_field_filters", - ] - - shopping_cart_settings_fields = [ - "enabled", - "show_attachments", - "show_price", - "show_stock_availability", - "enable_variants", - "show_contact_us_button", - "show_quantity_in_website", - "show_apply_coupon_code_in_website", - "allow_items_not_in_stock", - "company", - "price_list", - "default_customer_group", - "quotation_series", - "enable_checkout", - "payment_success_url", - "payment_gateway_account", - "save_quotations_as_draft", - ] - - settings = frappe.get_doc("E Commerce Settings") - - def map_into_e_commerce_settings(doctype, fields): - singles = frappe.qb.DocType("Singles") - query = ( - frappe.qb.from_(singles) - .select(singles["field"], singles.value) - .where((singles.doctype == doctype) & (singles["field"].isin(fields))) - ) - data = query.run(as_dict=True) - - # {'enable_attribute_filters': '1', ...} - mapper = {row.field: row.value for row in data} - - for key, value in mapper.items(): - value = cint(value) if (value and value.isdigit()) else value - settings.update({key: value}) - - settings.save() - - # shift data to E Commerce Settings - map_into_e_commerce_settings("Products Settings", products_settings_fields) - map_into_e_commerce_settings("Shopping Cart Settings", shopping_cart_settings_fields) - - # move filters and attributes tables to E Commerce Settings from Products Settings - for doctype in ("Website Filter Field", "Website Attribute"): - frappe.db.set_value( - doctype, - {"parent": "Products Settings"}, - {"parenttype": "E Commerce Settings", "parent": "E Commerce Settings"}, - update_modified=False, - ) diff --git a/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py b/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py deleted file mode 100644 index 35710a9bb4..0000000000 --- a/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py +++ /dev/null @@ -1,29 +0,0 @@ -import click -import frappe - - -def execute(): - - frappe.delete_doc("DocType", "Shopping Cart Settings", ignore_missing=True) - frappe.delete_doc("DocType", "Products Settings", ignore_missing=True) - frappe.delete_doc("DocType", "Supplier Item Group", ignore_missing=True) - - if frappe.db.get_single_value("E Commerce Settings", "enabled"): - notify_users() - - -def notify_users(): - - click.secho( - "Shopping cart and Product settings are merged into E-commerce settings.\n" - "Checkout the documentation to learn more:" - "https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce", - fg="yellow", - ) - - note = frappe.new_doc("Note") - note.title = "New E-Commerce Module" - note.public = 1 - note.notify_on_login = 1 - note.content = """

You are seeing this message because Shopping Cart is enabled on your site.


Shopping Cart Settings and Products settings are now merged into "E Commerce Settings".


You can learn about new and improved E-Commerce features in the official documentation.

  1. https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce


""" - note.insert(ignore_mandatory=True) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index a53adf1a83..9a2a39fb78 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -11,6 +11,9 @@ def execute(): asset_depreciation_schedules_map = get_asset_depreciation_schedules_map() for asset in assets: + if not asset_depreciation_schedules_map.get(asset.name): + continue + depreciation_schedules = asset_depreciation_schedules_map[asset.name] for fb_row in asset_finance_books_map[asset.name]: diff --git a/erpnext/patches/v15_0/delete_ecommerce_doctypes.py b/erpnext/patches/v15_0/delete_ecommerce_doctypes.py new file mode 100644 index 0000000000..af0398782e --- /dev/null +++ b/erpnext/patches/v15_0/delete_ecommerce_doctypes.py @@ -0,0 +1,30 @@ +import click +import frappe + + +def execute(): + if "webshop" in frappe.get_installed_apps(): + return + + if not frappe.db.table_exists("Website Item"): + return + + doctypes = [ + "E Commerce Settings", + "Website Item", + "Recommended Items", + "Item Review", + "Wishlist Item", + "Wishlist", + "Website Offer", + "Website Item Tabbed Section", + ] + + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + click.secho( + "ECommerce is renamed and moved to a separate app" + "Please install the app for ECommerce features: https://github.com/frappe/webshop", + fg="yellow", + ) diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js index 59f808a315..6797904424 100644 --- a/erpnext/portal/doctype/homepage/homepage.js +++ b/erpnext/portal/doctype/homepage/homepage.js @@ -19,12 +19,3 @@ frappe.ui.form.on('Homepage', { }); }, }); - -frappe.ui.form.on('Homepage Featured Product', { - view: function(frm, cdt, cdn) { - var child= locals[cdt][cdn]; - if (child.item_code && child.route) { - window.open('/' + child.route, '_blank'); - } - } -}); diff --git a/erpnext/portal/doctype/homepage/homepage.json b/erpnext/portal/doctype/homepage/homepage.json index 73f816d4d4..2b891f7268 100644 --- a/erpnext/portal/doctype/homepage/homepage.json +++ b/erpnext/portal/doctype/homepage/homepage.json @@ -15,10 +15,7 @@ "description", "hero_image", "slideshow", - "hero_section", - "products_section", - "products_url", - "products" + "hero_section" ], "fields": [ { @@ -86,30 +83,11 @@ "fieldtype": "Link", "label": "Homepage Section", "options": "Homepage Section" - }, - { - "fieldname": "products_section", - "fieldtype": "Section Break", - "label": "Products" - }, - { - "default": "/all-products", - "fieldname": "products_url", - "fieldtype": "Data", - "label": "URL for \"All Products\"" - }, - { - "description": "Products to be shown on website homepage", - "fieldname": "products", - "fieldtype": "Table", - "label": "Products", - "options": "Homepage Featured Product", - "width": "40px" } ], "issingle": 1, "links": [], - "modified": "2021-02-18 13:29:29.531639", + "modified": "2022-12-19 21:10:29.127277", "modified_by": "Administrator", "module": "Portal", "name": "Homepage", @@ -138,6 +116,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "company", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py index 0d2e360788..c0a0c07d7d 100644 --- a/erpnext/portal/doctype/homepage/homepage.py +++ b/erpnext/portal/doctype/homepage/homepage.py @@ -12,26 +12,3 @@ class Homepage(Document): if not self.description: self.description = frappe._("This is an example website auto-generated from ERPNext") delete_page_cache("home") - - def setup_items(self): - for d in frappe.get_all( - "Website Item", - fields=["name", "item_name", "description", "website_image", "route"], - filters={"published": 1}, - limit=3, - ): - - doc = frappe.get_doc("Website Item", d.name) - if not doc.route: - # set missing route - doc.save() - self.append( - "products", - dict( - item_code=d.name, - item_name=d.item_name, - description=d.description, - image=d.website_image, - route=d.route, - ), - ) diff --git a/erpnext/portal/doctype/homepage_featured_product/__init__.py b/erpnext/portal/doctype/homepage_featured_product/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json deleted file mode 100644 index 63789e35b5..0000000000 --- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "actions": [], - "autoname": "hash", - "creation": "2016-04-22 05:57:06.261401", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "col_break1", - "item_name", - "view", - "section_break_5", - "description", - "column_break_7", - "image", - "thumbnail", - "route" - ], - "fields": [ - { - "bold": 1, - "fieldname": "item_code", - "fieldtype": "Link", - "in_filter": 1, - "in_list_view": 1, - "label": "Item", - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Website Item", - "print_width": "150px", - "reqd": 1, - "search_index": 1, - "width": "150px" - }, - { - "fieldname": "col_break1", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.item_name", - "fetch_if_empty": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Name", - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "print_hide": 1, - "print_width": "150", - "read_only": 1, - "reqd": 1, - "width": "150" - }, - { - "fieldname": "view", - "fieldtype": "Button", - "in_list_view": 1, - "label": "View" - }, - { - "collapsible": 1, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "label": "Details" - }, - { - "fetch_from": "item_code.web_long_description", - "fieldname": "description", - "fieldtype": "Text Editor", - "in_filter": 1, - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Small Text", - "print_width": "300px", - "width": "300px" - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.website_image", - "fetch_if_empty": 1, - "fieldname": "image", - "fieldtype": "Attach Image", - "label": "Image" - }, - { - "fetch_from": "item_code.thumbnail", - "fieldname": "thumbnail", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Thumbnail" - }, - { - "fetch_from": "item_code.route", - "fieldname": "route", - "fieldtype": "Small Text", - "label": "route", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-02-18 13:05:50.669311", - "modified_by": "Administrator", - "module": "Portal", - "name": "Homepage Featured Product", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py deleted file mode 100644 index c21461d631..0000000000 --- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HomepageFeaturedProduct(Document): - pass diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index c8b03e678b..903d4a6196 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -1,10 +1,4 @@ import frappe -from frappe.utils.nestedset import get_root_of - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.shopping_cart.cart import get_debtors_account def set_default_role(doc, method): @@ -56,26 +50,7 @@ def create_customer_or_supplier(): party = frappe.new_doc(doctype) fullname = frappe.utils.get_fullname(user) - if doctype == "Customer": - cart_settings = get_shopping_cart_settings() - - if cart_settings.enable_checkout: - debtors_account = get_debtors_account(cart_settings) - else: - debtors_account = "" - - party.update( - { - "customer_name": fullname, - "customer_type": "Individual", - "customer_group": cart_settings.default_customer_group, - "territory": get_root_of("Territory"), - } - ) - - if debtors_account: - party.update({"accounts": [{"company": cart_settings.company, "account": debtors_account}]}) - else: + if not doctype == "Customer": party.update( { "supplier_name": fullname, diff --git a/erpnext/public/js/customer_reviews.js b/erpnext/public/js/customer_reviews.js deleted file mode 100644 index e13ded6b48..0000000000 --- a/erpnext/public/js/customer_reviews.js +++ /dev/null @@ -1,138 +0,0 @@ -$(() => { - class CustomerReviews { - constructor() { - this.bind_button_actions(); - this.start = 0; - this.page_length = 10; - } - - bind_button_actions() { - this.write_review(); - this.view_more(); - } - - write_review() { - //TODO: make dialog popup on stray page - $('.page_content').on('click', '.btn-write-review', (e) => { - // Bind action on write a review button - const $btn = $(e.currentTarget); - - let d = new frappe.ui.Dialog({ - title: __("Write a Review"), - fields: [ - {fieldname: "title", fieldtype: "Data", label: "Headline", reqd: 1}, - {fieldname: "rating", fieldtype: "Rating", label: "Overall Rating", reqd: 1}, - {fieldtype: "Section Break"}, - {fieldname: "comment", fieldtype: "Small Text", label: "Your Review"} - ], - primary_action: function() { - let data = d.get_values(); - frappe.call({ - method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review", - args: { - web_item: $btn.attr('data-web-item'), - title: data.title, - rating: data.rating, - comment: data.comment - }, - freeze: true, - freeze_message: __("Submitting Review ..."), - callback: (r) => { - if (!r.exc) { - frappe.msgprint({ - message: __("Thank you for submitting your review"), - title: __("Review Submitted"), - indicator: "green" - }); - d.hide(); - location.reload(); - } - } - }); - }, - primary_action_label: __('Submit') - }); - d.show(); - }); - } - - view_more() { - $('.page_content').on('click', '.btn-view-more', (e) => { - // Bind action on view more button - const $btn = $(e.currentTarget); - $btn.prop('disabled', true); - - this.start += this.page_length; - let me = this; - - frappe.call({ - method: "erpnext.e_commerce.doctype.item_review.item_review.get_item_reviews", - args: { - web_item: $btn.attr('data-web-item'), - start: me.start, - end: me.page_length - }, - callback: (result) => { - if (result.message) { - let res = result.message; - me.get_user_review_html(res.reviews); - - $btn.prop('disabled', false); - if (res.total_reviews <= (me.start + me.page_length)) { - $btn.hide(); - } - - } - } - }); - }); - - } - - get_user_review_html(reviews) { - let me = this; - let $content = $('.user-reviews'); - - reviews.forEach((review) => { - $content.append(` -
-
-

- ${__(review.review_title)} -

-
- ${me.get_review_stars(review.rating)} -
-
- -
-

- ${__(review.comment)} -

-
-
- ${__(review.customer)} - - ${__(review.published_on)} -
-
- `); - }); - } - - get_review_stars(rating) { - let stars = ``; - for (let i = 1; i < 6; i++) { - let fill_class = i <= rating ? 'star-click' : ''; - stars += ` - - - - `; - } - return stars; - } - } - - new CustomerReviews(); -}); \ No newline at end of file diff --git a/erpnext/public/js/erpnext-web.bundle.js b/erpnext/public/js/erpnext-web.bundle.js index cbe899dc06..45c6a648ec 100644 --- a/erpnext/public/js/erpnext-web.bundle.js +++ b/erpnext/public/js/erpnext-web.bundle.js @@ -1,8 +1 @@ import "./website_utils"; -import "./wishlist"; -import "./shopping_cart"; -import "./customer_reviews"; -import "../../e_commerce/product_ui/list"; -import "../../e_commerce/product_ui/views"; -import "../../e_commerce/product_ui/grid"; -import "../../e_commerce/product_ui/search"; \ No newline at end of file diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js deleted file mode 100644 index d14740c106..0000000000 --- a/erpnext/public/js/shopping_cart.js +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -// shopping cart -frappe.provide("erpnext.e_commerce.shopping_cart"); -var shopping_cart = erpnext.e_commerce.shopping_cart; - -var getParams = function (url) { - var params = []; - var parser = document.createElement('a'); - parser.href = url; - var query = parser.search.substring(1); - var vars = query.split('&'); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split('='); - params[pair[0]] = decodeURIComponent(pair[1]); - } - return params; -}; - -frappe.ready(function() { - var full_name = frappe.session && frappe.session.user_fullname; - // update user - if(full_name) { - $('.navbar li[data-label="User"] a') - .html(' ' + full_name); - } - // set coupon code and sales partner code - - var url_args = getParams(window.location.href); - - var referral_coupon_code = url_args['cc']; - var referral_sales_partner = url_args['sp']; - - var d = new Date(); - // expires within 30 minutes - d.setTime(d.getTime() + (0.02 * 24 * 60 * 60 * 1000)); - var expires = "expires="+d.toUTCString(); - if (referral_coupon_code) { - document.cookie = "referral_coupon_code=" + referral_coupon_code + ";" + expires + ";path=/"; - } - if (referral_sales_partner) { - document.cookie = "referral_sales_partner=" + referral_sales_partner + ";" + expires + ";path=/"; - } - referral_coupon_code=frappe.get_cookie("referral_coupon_code"); - referral_sales_partner=frappe.get_cookie("referral_sales_partner"); - - if (referral_coupon_code && $(".tot_quotation_discount").val()==undefined ) { - $(".txtcoupon").val(referral_coupon_code); - } - if (referral_sales_partner) { - $(".txtreferral_sales_partner").val(referral_sales_partner); - } - - // update login - shopping_cart.show_shoppingcart_dropdown(); - shopping_cart.set_cart_count(); - shopping_cart.show_cart_navbar(); -}); - -$.extend(shopping_cart, { - show_shoppingcart_dropdown: function() { - $(".shopping-cart").on('shown.bs.dropdown', function() { - if (!$('.shopping-cart-menu .cart-container').length) { - return frappe.call({ - method: 'erpnext.e_commerce.shopping_cart.cart.get_shopping_cart_menu', - callback: function(r) { - if (r.message) { - $('.shopping-cart-menu').html(r.message); - } - } - }); - } - }); - }, - - update_cart: function(opts) { - if (frappe.session.user==="Guest") { - if (localStorage) { - localStorage.setItem("last_visited", window.location.pathname); - } - frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => { - window.location.href = res.message || "/login"; - }); - } else { - shopping_cart.freeze(); - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.update_cart", - args: { - item_code: opts.item_code, - qty: opts.qty, - additional_notes: opts.additional_notes !== undefined ? opts.additional_notes : undefined, - with_items: opts.with_items || 0 - }, - btn: opts.btn, - callback: function(r) { - shopping_cart.unfreeze(); - shopping_cart.set_cart_count(true); - if(opts.callback) - opts.callback(r); - } - }); - } - }, - - set_cart_count: function(animate=false) { - $(".intermediate-empty-cart").remove(); - - var cart_count = frappe.get_cookie("cart_count"); - if(frappe.session.user==="Guest") { - cart_count = 0; - } - - if(cart_count) { - $(".shopping-cart").toggleClass('hidden', false); - } - - var $cart = $('.cart-icon'); - var $badge = $cart.find("#cart-count"); - - if(parseInt(cart_count) === 0 || cart_count === undefined) { - $cart.css("display", "none"); - $(".cart-tax-items").hide(); - $(".btn-place-order").hide(); - $(".cart-payment-addresses").hide(); - - let intermediate_empty_cart_msg = ` -
- ${ __("Cart is Empty") } -
- `; - $(".cart-table").after(intermediate_empty_cart_msg); - } - else { - $cart.css("display", "inline"); - $("#cart-count").text(cart_count); - } - - if(cart_count) { - $badge.html(cart_count); - - if (animate) { - $cart.addClass("cart-animate"); - setTimeout(() => { - $cart.removeClass("cart-animate"); - }, 500); - } - } else { - $badge.remove(); - } - }, - - shopping_cart_update: function({item_code, qty, cart_dropdown, additional_notes}) { - shopping_cart.update_cart({ - item_code, - qty, - additional_notes, - with_items: 1, - btn: this, - callback: function(r) { - if(!r.exc) { - $(".cart-items").html(r.message.items); - $(".cart-tax-items").html(r.message.total); - $(".payment-summary").html(r.message.taxes_and_totals); - shopping_cart.set_cart_count(); - - if (cart_dropdown != true) { - $(".cart-icon").hide(); - } - } - }, - }); - }, - - show_cart_navbar: function () { - frappe.call({ - method: "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.is_cart_enabled", - callback: function(r) { - $(".shopping-cart").toggleClass('hidden', r.message ? false : true); - } - }); - }, - - toggle_button_class(button, remove, add) { - button.removeClass(remove); - button.addClass(add); - }, - - bind_add_to_cart_action() { - $('.page_content').on('click', '.btn-add-to-cart-list', (e) => { - const $btn = $(e.currentTarget); - $btn.prop('disabled', true); - - if (frappe.session.user==="Guest") { - if (localStorage) { - localStorage.setItem("last_visited", window.location.pathname); - } - frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => { - window.location.href = res.message || "/login"; - }); - return; - } - - $btn.addClass('hidden'); - $btn.closest('.cart-action-container').addClass('d-flex'); - $btn.parent().find('.go-to-cart').removeClass('hidden'); - $btn.parent().find('.go-to-cart-grid').removeClass('hidden'); - $btn.parent().find('.cart-indicator').removeClass('hidden'); - - const item_code = $btn.data('item-code'); - erpnext.e_commerce.shopping_cart.update_cart({ - item_code, - qty: 1 - }); - - }); - }, - - freeze() { - if (window.location.pathname !== "/cart") return; - - if (!$('#freeze').length) { - let freeze = $('') - .appendTo("body"); - - setTimeout(function() { - freeze.addClass("show"); - }, 1); - } else { - $("#freeze").addClass("show"); - } - }, - - unfreeze() { - if ($('#freeze').length) { - let freeze = $('#freeze').removeClass("show"); - setTimeout(function() { - freeze.remove(); - }, 1); - } - } -}); diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js deleted file mode 100644 index f6599e9f6d..0000000000 --- a/erpnext/public/js/wishlist.js +++ /dev/null @@ -1,204 +0,0 @@ -frappe.provide("erpnext.e_commerce.wishlist"); -var wishlist = erpnext.e_commerce.wishlist; - -frappe.provide("erpnext.e_commerce.shopping_cart"); -var shopping_cart = erpnext.e_commerce.shopping_cart; - -$.extend(wishlist, { - set_wishlist_count: function(animate=false) { - // set badge count for wishlist icon - var wish_count = frappe.get_cookie("wish_count"); - if (frappe.session.user==="Guest") { - wish_count = 0; - } - - if (wish_count) { - $(".wishlist").toggleClass('hidden', false); - } - - var $wishlist = $('.wishlist-icon'); - var $badge = $wishlist.find("#wish-count"); - - if (parseInt(wish_count) === 0 || wish_count === undefined) { - $wishlist.css("display", "none"); - } else { - $wishlist.css("display", "inline"); - } - if (wish_count) { - $badge.html(wish_count); - if (animate) { - $wishlist.addClass('cart-animate'); - setTimeout(() => { - $wishlist.removeClass('cart-animate'); - }, 500); - } - } else { - $badge.remove(); - } - }, - - bind_move_to_cart_action: function() { - // move item to cart from wishlist - $('.page_content').on("click", ".btn-add-to-cart", (e) => { - const $move_to_cart_btn = $(e.currentTarget); - let item_code = $move_to_cart_btn.data("item-code"); - - shopping_cart.shopping_cart_update({ - item_code, - qty: 1, - cart_dropdown: true - }); - - let success_action = function() { - const $card_wrapper = $move_to_cart_btn.closest(".wishlist-card"); - $card_wrapper.addClass("wish-removed"); - }; - let args = { item_code: item_code }; - this.add_remove_from_wishlist("remove", args, success_action, null, true); - }); - }, - - bind_remove_action: function() { - // remove item from wishlist - let me = this; - - $('.page_content').on("click", ".remove-wish", (e) => { - const $remove_wish_btn = $(e.currentTarget); - let item_code = $remove_wish_btn.data("item-code"); - - let success_action = function() { - const $card_wrapper = $remove_wish_btn.closest(".wishlist-card"); - $card_wrapper.addClass("wish-removed"); - if (frappe.get_cookie("wish_count") == 0) { - $(".page_content").empty(); - me.render_empty_state(); - } - }; - let args = { item_code: item_code }; - this.add_remove_from_wishlist("remove", args, success_action); - }); - }, - - bind_wishlist_action() { - // 'wish'('like') or 'unwish' item in product listing - $('.page_content').on('click', '.like-action, .like-action-list', (e) => { - const $btn = $(e.currentTarget); - this.wishlist_action($btn); - }); - }, - - wishlist_action(btn) { - const $wish_icon = btn.find('.wish-icon'); - let me = this; - - if (frappe.session.user==="Guest") { - if (localStorage) { - localStorage.setItem("last_visited", window.location.pathname); - } - this.redirect_guest(); - return; - } - - let success_action = function() { - erpnext.e_commerce.wishlist.set_wishlist_count(true); - }; - - if ($wish_icon.hasClass('wished')) { - // un-wish item - btn.removeClass("like-animate"); - btn.addClass("like-action-wished"); - this.toggle_button_class($wish_icon, 'wished', 'not-wished'); - - let args = { item_code: btn.data('item-code') }; - let failure_action = function() { - me.toggle_button_class($wish_icon, 'not-wished', 'wished'); - }; - this.add_remove_from_wishlist("remove", args, success_action, failure_action); - } else { - // wish item - btn.addClass("like-animate"); - btn.addClass("like-action-wished"); - this.toggle_button_class($wish_icon, 'not-wished', 'wished'); - - let args = {item_code: btn.data('item-code')}; - let failure_action = function() { - me.toggle_button_class($wish_icon, 'wished', 'not-wished'); - }; - this.add_remove_from_wishlist("add", args, success_action, failure_action); - } - }, - - toggle_button_class(button, remove, add) { - button.removeClass(remove); - button.addClass(add); - }, - - add_remove_from_wishlist(action, args, success_action, failure_action, async=false) { - /* AJAX call to add or remove Item from Wishlist - action: "add" or "remove" - args: args for method (item_code, price, formatted_price), - success_action: method to execute on successs, - failure_action: method to execute on failure, - async: make call asynchronously (true/false). */ - if (frappe.session.user==="Guest") { - if (localStorage) { - localStorage.setItem("last_visited", window.location.pathname); - } - this.redirect_guest(); - } else { - let method = "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist"; - if (action === "remove") { - method = "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist"; - } - - frappe.call({ - async: async, - type: "POST", - method: method, - args: args, - callback: function (r) { - if (r.exc) { - if (failure_action && (typeof failure_action === 'function')) { - failure_action(); - } - frappe.msgprint({ - message: __("Sorry, something went wrong. Please refresh."), - indicator: "red", title: __("Note") - }); - } else if (success_action && (typeof success_action === 'function')) { - success_action(); - } - } - }); - } - }, - - redirect_guest() { - frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => { - window.location.href = res.message || "/login"; - }); - }, - - render_empty_state() { - $(".page_content").append(` -
-
- Empty Cart -
-
${ __('Wishlist is empty !') }

-
- `); - } - -}); - -frappe.ready(function() { - if (window.location.pathname !== "/wishlist") { - $(".wishlist").toggleClass('hidden', true); - wishlist.set_wishlist_count(); - } else { - wishlist.bind_move_to_cart_action(); - wishlist.bind_remove_action(); - } - -}); \ No newline at end of file diff --git a/erpnext/public/scss/erpnext-web.bundle.scss b/erpnext/public/scss/erpnext-web.bundle.scss index 6ef1892a3d..18d7c6cf4e 100644 --- a/erpnext/public/scss/erpnext-web.bundle.scss +++ b/erpnext/public/scss/erpnext-web.bundle.scss @@ -1,2 +1 @@ -@import "./shopping_cart"; @import "./website"; diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss deleted file mode 100644 index 6ae464d2c2..0000000000 --- a/erpnext/public/scss/shopping_cart.scss +++ /dev/null @@ -1,1381 +0,0 @@ -@import "frappe/public/scss/common/mixins"; - -:root { - --green-info: #38A160; - --product-bg-color: white; - --body-bg-color: var(--gray-50); -} - -body.product-page { - background: var(--body-bg-color); -} - -.item-breadcrumbs { - .breadcrumb-container { - a { - color: var(--gray-900); - } - } -} - -.carousel-control { - height: 42px; - width: 42px; - display: flex; - align-items: center; - justify-content: center; - background: white; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.08), 0px 1px 2px 1px rgba(0, 0, 0, 0.06); - border-radius: 100px; -} - -.carousel-control-prev, -.carousel-control-next { - opacity: 1; - width: 8%; - - @media (max-width: 1200px) { - width: 10%; - } - @media (max-width: 768px) { - width: 15%; - } -} - -.carousel-body { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.carousel-content { - max-width: 400px; - margin-left: 5rem; - margin-right: 5rem; -} - -.card { - border: none; -} - -.product-category-section { - .card:hover { - box-shadow: 0px 16px 45px 6px rgba(0, 0, 0, 0.08), 0px 8px 10px -10px rgba(0, 0, 0, 0.04); - } - - .card-grid { - display: grid; - grid-gap: 15px; - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); - } -} - -.no-image-item { - height: 340px; - width: 340px; - background: var(--gray-100); - border-radius: var(--border-radius); - font-size: 2rem; - color: var(--gray-500); - display: flex; - align-items: center; - justify-content: center; -} - -.item-card-group-section { - .card { - height: 100%; - align-items: center; - justify-content: center; - - &:hover { - box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04); - transition: box-shadow 400ms; - } - } - - .card:hover, .card:focus-within { - .btn-add-to-cart-list { - visibility: visible; - } - .like-action { - visibility: visible; - } - .btn-explore-variants { - visibility: visible; - } - } - - - .card-img-container { - height: 210px; - width: 100%; - } - - .card-img { - max-height: 210px; - object-fit: contain; - margin-top: 1.25rem; - } - - .no-image { - @include flex(flex, center, center, null); - height: 220px; - background: var(--gray-100); - width: 100%; - border-radius: var(--border-radius) var(--border-radius) 0 0; - font-size: 2rem; - color: var(--gray-500); - } - - .no-image-list { - @include flex(flex, center, center, null); - height: 150px; - background: var(--gray-100); - border-radius: var(--border-radius); - font-size: 2rem; - color: var(--gray-500); - margin-top: 15px; - margin-bottom: 15px; - } - - .card-body-flex { - display: flex; - flex-direction: column; - } - - .product-title { - font-size: 14px; - color: var(--gray-800); - font-weight: 500; - } - - .product-description { - font-size: 12px; - color: var(--text-color); - margin: 20px 0; - display: -webkit-box; - -webkit-line-clamp: 6; - -webkit-box-orient: vertical; - - p { - margin-bottom: 0.5rem; - } - } - - .product-category { - font-size: 13px; - color: var(--text-muted); - margin: var(--margin-sm) 0; - } - - .product-price { - font-size: 18px; - font-weight: 600; - color: var(--text-color); - margin: var(--margin-sm) 0; - margin-bottom: auto !important; - - .striked-price { - font-weight: 500; - font-size: 15px; - color: var(--gray-500); - } - } - - .product-info-green { - color: var(--green-info); - font-weight: 600; - } - - .item-card { - padding: var(--padding-sm); - min-width: 300px; - } - - .wishlist-card { - padding: var(--padding-sm); - min-width: 260px; - .card-body-flex { - display: flex; - flex-direction: column; - } - } -} - -#products-list-area, #products-grid-area { - padding: 0 5px; -} - -.list-row { - background-color: white; - padding-bottom: 1rem; - padding-top: 1.5rem !important; - border-radius: 8px; - border-bottom: 1px solid var(--gray-50); - - &:hover, &:focus-within { - box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04); - transition: box-shadow 400ms; - - .btn-add-to-cart-list { - visibility: visible; - } - .like-action-list { - visibility: visible; - } - .btn-explore-variants { - visibility: visible; - } - } - - .product-code { - padding-top: 0 !important; - } - - .btn-explore-variants { - min-width: 135px; - max-height: 30px; - float: right; - padding: 0.25rem 1rem; - } -} - -[data-doctype="Item Group"], -#page-index { - .page-header { - font-size: 20px; - font-weight: 700; - color: var(--text-color); - } - - .filters-section { - .title-section { - border-bottom: 1px solid var(--table-border-color); - } - - .filter-title { - font-weight: 500; - } - - .clear-filters { - font-size: 13px; - } - - .filter-lookup-input { - background-color: white; - border: 1px solid var(--gray-300); - - &:focus { - border: 1px solid var(--primary); - } - } - - .filter-label { - font-size: 11px; - font-weight: 600; - color: var(--gray-700); - text-transform: uppercase; - } - - .filter-block { - border-bottom: 1px solid var(--table-border-color); - } - - .checkbox { - .label-area { - font-size: 13px; - color: var(--gray-800); - } - } - } -} - -.product-filter { - width: 14px !important; - height: 14px !important; -} - -.discount-filter { - &:before { - width: 14px !important; - height: 14px !important; - } -} - -.list-image { - border: none !important; - overflow: hidden; - max-height: 200px; - background-color: white; -} - -.product-container { - @include card($padding: var(--padding-md)); - background-color: var(--product-bg-color) !important; - min-height: fit-content; - - .product-details { - max-width: 50%; - - .btn-add-to-cart { - font-size: 14px; - } - } - - &.item-main { - .product-image { - width: 100%; - } - } - - .expand { - max-width: 100% !important; // expand in absence of slideshow - } - - @media (max-width: 789px) { - .product-details { - max-width: 90% !important; - - .btn-add-to-cart { - font-size: 14px; - } - } - } - - .btn-add-to-wishlist { - svg use { - --icon-stroke: #F47A7A; - } - } - - .btn-view-in-wishlist { - svg use { - fill: #F47A7A; - --icon-stroke: none; - } - } - - .product-title { - font-size: 16px; - font-weight: 600; - color: var(--text-color); - padding: 0 !important; - } - - .product-description { - font-size: 13px; - color: var(--gray-800); - } - - .product-image { - border-color: var(--table-border-color) !important; - padding: 15px; - - @media (max-width: var(--md-width)) { - height: 300px; - width: 300px; - } - - @media (min-width: var(--lg-width)) { - height: 350px; - width: 350px; - } - - img { - object-fit: contain; - } - } - - .item-slideshow { - - @media (max-width: var(--md-width)) { - max-height: 320px; - } - - @media (min-width: var(--lg-width)) { - max-height: 430px; - } - - overflow: auto; - } - - .item-slideshow-image { - height: 4rem; - width: 6rem; - object-fit: contain; - padding: 0.5rem; - border: 1px solid var(--table-border-color); - border-radius: 4px; - cursor: pointer; - - &:hover, &.active { - border-color: var(--primary); - } - } - - .item-cart { - .product-price { - font-size: 22px; - color: var(--text-color); - font-weight: 600; - - .formatted-price { - color: var(--text-muted); - font-size: 14px; - } - } - - .no-stock { - font-size: var(--text-base); - } - - .offers-heading { - font-size: 16px !important; - color: var(--text-color); - .tag-icon { - --icon-stroke: var(--gray-500); - } - } - - .w-30-40 { - width: 30%; - - @media (max-width: 992px) { - width: 40%; - } - } - } - - .tab-content { - font-size: 14px; - } -} - -// Item Recommendations -.recommended-item-section { - padding-right: 0; - - .recommendation-header { - font-size: 16px; - font-weight: 500 - } - - .recommendation-container { - padding: .5rem; - min-height: 0px; - - .r-item-image { - min-height: 100px; - width: 40%; - - .r-product-image { - padding: 2px 15px; - } - - .no-image-r-item { - display: flex; justify-content: center; - background-color: var(--gray-200); - align-items: center; - color: var(--gray-400); - margin-top: .15rem; - border-radius: 6px; - height: 100%; - font-size: 24px; - } - } - - .r-item-info { - font-size: 14px; - padding-right: 0; - padding-left: 10px; - width: 60%; - - a { - color: var(--gray-800); - font-weight: 400; - } - - .item-price { - font-size: 15px; - font-weight: 600; - color: var(--text-color); - } - - .striked-item-price { - font-weight: 500; - color: var(--gray-500); - } - } - } -} - -.product-code { - padding: .5rem 0; - color: var(--text-muted); - font-size: 14px; - .product-item-group { - padding-right: .25rem; - border-right: solid 1px var(--text-muted); - } - - .product-item-code { - padding-left: .5rem; - } -} - -.item-configurator-dialog { - .modal-body { - padding-bottom: var(--padding-xl); - - .status-area { - .alert { - padding: var(--padding-xs) var(--padding-sm); - font-size: var(--text-sm); - } - } - - .form-layout { - max-height: 50vh; - overflow-y: auto; - } - - .section-body { - .form-column { - .form-group { - .control-label { - font-size: var(--text-md); - color: var(--gray-700); - } - - .help-box { - margin-top: 2px; - font-size: var(--text-sm); - } - } - } - } - } -} - -.item-group-slideshow { - - .carousel-inner.rounded-carousel { - border-radius: var(--card-border-radius); - } -} - -.sub-category-container { - padding-bottom: .5rem; - margin-bottom: 1.25rem; - border-bottom: 1px solid var(--table-border-color); - - .heading { - color: var(--gray-500); - } -} - -.scroll-categories { - .category-pill { - display: inline-block; - width: fit-content; - padding: 6px 12px; - margin-bottom: 8px; - background-color: #ecf5fe; - font-size: 14px; - border-radius: 18px; - color: var(--blue-500); - } -} - - -.shopping-badge { - position: relative; - top: -10px; - left: -12px; - background: var(--red-600); - align-items: center; - height: 16px; - font-size: 10px; - border-radius: 50%; -} - - -.cart-animate { - animation: wiggle 0.5s linear; -} -@keyframes wiggle { - 8%, - 41% { - transform: translateX(-10px); - } - 25%, - 58% { - transform: translate(10px); - } - 75% { - transform: translate(-5px); - } - 92% { - transform: translate(5px); - } - 0%, - 100% { - transform: translate(0); - } -} - -.total-discount { - font-size: 14px; - color: var(--primary-color) !important; -} - -#page-cart { - .shopping-cart-header { - font-weight: bold; - } - - .cart-container { - color: var(--text-color); - - .frappe-card { - display: flex; - flex-direction: column; - justify-content: space-between; - height: fit-content; - } - - .cart-items-header { - font-weight: 600; - } - - .cart-table { - tr { - margin-bottom: 1rem; - } - - th, tr, td { - border-color: var(--border-color); - border-width: 1px; - } - - th { - font-weight: normal; - font-size: 13px; - color: var(--text-muted); - padding: var(--padding-sm) 0; - } - - td { - padding: var(--padding-sm) 0; - color: var(--text-color); - } - - .cart-item-image { - width: 20%; - min-width: 100px; - img { - max-height: 112px; - } - } - - .cart-items { - .item-title { - width: 80%; - font-size: 14px; - font-weight: 500; - color: var(--text-color); - } - - .item-subtitle { - color: var(--text-muted); - font-size: 13px; - } - - .item-subtotal { - font-size: 14px; - font-weight: 500; - } - - .sm-item-subtotal { - font-size: 14px; - font-weight: 500; - display: none; - - @media (max-width: 992px) { - display: unset !important; - } - } - - .item-rate { - font-size: 13px; - color: var(--text-muted); - } - - .free-tag { - padding: 4px 8px; - border-radius: 4px; - background-color: var(--dark-green-50); - } - - textarea { - width: 80%; - height: 60px; - font-size: 14px; - } - - } - - .cart-tax-items { - .item-grand-total { - font-size: 16px; - font-weight: 700; - color: var(--text-color); - } - } - - .column-sm-view { - @media (max-width: 992px) { - display: none !important; - } - } - - .item-column { - width: 50%; - @media (max-width: 992px) { - width: 70%; - } - } - - .remove-cart-item { - border-radius: 6px; - border: 1px solid var(--gray-100); - width: 28px; - height: 28px; - font-weight: 300; - color: var(--gray-700); - background-color: var(--gray-100); - float: right; - cursor: pointer; - margin-top: .25rem; - justify-content: center; - } - - .remove-cart-item-logo { - margin-top: 2px; - margin-left: 2.2px; - fill: var(--gray-700) !important; - } - } - - .cart-payment-addresses { - hr { - border-color: var(--border-color); - } - } - - .payment-summary { - h6 { - padding-bottom: 1rem; - border-bottom: solid 1px var(--gray-200); - } - - table { - font-size: 14px; - td { - padding: 0; - padding-top: 0.35rem !important; - border: none !important; - } - - &.grand-total { - border-top: solid 1px var(--gray-200); - } - } - - .bill-label { - color: var(--gray-600); - } - - .bill-content { - font-weight: 500; - &.net-total { - font-size: 16px; - font-weight: 600; - } - } - - .btn-coupon-code { - font-size: 14px; - border: dashed 1px var(--gray-400); - box-shadow: none; - } - } - - .number-spinner { - width: 75%; - min-width: 105px; - .cart-btn { - border: none; - background: var(--gray-100); - box-shadow: none; - width: 24px; - height: 28px; - align-items: center; - justify-content: center; - display: flex; - font-size: 20px; - font-weight: 300; - color: var(--gray-700); - } - - .cart-qty { - height: 28px; - font-size: 13px; - &:disabled { - background: var(--gray-100); - opacity: 0.65; - } - } - } - - .place-order-container { - .btn-place-order { - float: right; - } - } - } - - .t-and-c-container { - padding: 1.5rem; - } - - .t-and-c-terms { - font-size: 14px; - } -} - -.no-image-cart-item { - max-height: 112px; - display: flex; justify-content: center; - background-color: var(--gray-200); - align-items: center; - color: var(--gray-400); - margin-top: .15rem; - border-radius: 6px; - height: 100%; - font-size: 24px; -} - -.cart-empty.frappe-card { - min-height: 76vh; - @include flex(flex, center, center, column); - - .cart-empty-message { - font-size: 18px; - color: var(--text-color); - font-weight: bold; - } -} - -.address-card { - .card-title { - font-size: 14px; - font-weight: 500; - } - - .card-body { - max-width: 80%; - } - - .card-text { - font-size: 13px; - color: var(--gray-700); - } - - .card-link { - font-size: 13px; - - svg use { - stroke: var(--primary-color); - } - } - - .btn-change-address { - border: 1px solid var(--primary-color); - color: var(--primary-color); - box-shadow: none; - } -} - -.address-header { - margin-top: .15rem;padding: 0; -} - -.btn-new-address { - float: right; - font-size: 15px !important; - color: var(--primary-color) !important; -} - -.btn-new-address:hover, .btn-change-address:hover { - color: var(--primary-color) !important; -} - -.modal .address-card { - .card-body { - padding: var(--padding-sm); - border-radius: var(--border-radius); - border: 1px solid var(--dark-border-color); - } -} - -.cart-indicator { - position: absolute; - text-align: center; - width: 22px; - height: 22px; - left: calc(100% - 40px); - top: 22px; - - border-radius: 66px; - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); - background: white; - color: var(--primary-color); - font-size: 14px; - - &.list-indicator { - position: unset; - margin-left: auto; - } -} - - -.like-action { - visibility: hidden; - text-align: center; - position: absolute; - cursor: pointer; - width: 28px; - height: 28px; - left: 20px; - top: 20px; - - /* White */ - background: white; - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); - border-radius: 66px; - - &.like-action-wished { - visibility: visible !important; - } - - @media (max-width: 992px) { - visibility: visible !important; - } -} - -.like-action-list { - visibility: hidden; - text-align: center; - position: absolute; - cursor: pointer; - width: 28px; - height: 28px; - left: 20px; - top: 0; - - /* White */ - background: white; - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); - border-radius: 66px; - - &.like-action-wished { - visibility: visible !important; - } - - @media (max-width: 992px) { - visibility: visible !important; - } -} - -.like-action-item-fp { - visibility: visible !important; - position: unset; - float: right; -} - -.like-animate { - animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1; -} - -@keyframes expand { - 30% { - transform: scale(1.3); - } - 50% { - transform: scale(0.8); - } - 70% { - transform: scale(1.1); - } - 100% { - transform: scale(1); - } - } - -.not-wished { - cursor: pointer; - --icon-stroke: #F47A7A !important; - - &:hover { - fill: #F47A7A; - } -} - -.wished { - --icon-stroke: none; - fill: #F47A7A !important; -} - -.list-row-checkbox { - &:before { - display: none; - } - - &:checked:before { - display: block; - z-index: 1; - } -} - -#pay-for-order { - padding: .5rem 1rem; // Pay button in SO -} - -.btn-explore-variants { - visibility: hidden; - box-shadow: none; - margin: var(--margin-sm) 0; - width: 90px; - max-height: 50px; // to avoid resizing on window resize - flex: none; - transition: 0.3s ease; - - color: white; - background-color: var(--orange-500); - border: 1px solid var(--orange-500); - font-size: 13px; - - &:hover { - color: white; - } -} - -.btn-add-to-cart-list{ - visibility: hidden; - box-shadow: none; - margin: var(--margin-sm) 0; - // margin-top: auto !important; - max-height: 50px; // to avoid resizing on window resize - flex: none; - transition: 0.3s ease; - - font-size: 13px; - - &:hover { - color: white; - } - - @media (max-width: 992px) { - visibility: visible !important; - } -} - -.go-to-cart-grid { - max-height: 30px; - margin-top: 1rem !important; -} - -.go-to-cart { - max-height: 30px; - float: right; -} - -.remove-wish { - background-color: white; - position: absolute; - cursor: pointer; - top:10px; - right: 20px; - width: 32px; - height: 32px; - - border-radius: 50%; - border: 1px solid var(--gray-100); - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); -} - -.wish-removed { - display: none; -} - -.item-website-specification { - font-size: .875rem; - .product-title { - font-size: 18px; - } - - .table { - width: 70%; - } - - td { - border: none !important; - } - - .spec-label { - color: var(--gray-600); - } - - .spec-content { - color: var(--gray-800); - } -} - -.reviews-full-page { - padding: 1rem 2rem; -} - -.ratings-reviews-section { - border-top: 1px solid #E2E6E9; - padding: .5rem 1rem; -} - -.reviews-header { - font-size: 20px; - font-weight: 600; - color: var(--gray-800); - display: flex; - align-items: center; - padding: 0; -} - -.btn-write-review { - float: right; - padding: .5rem 1rem; - font-size: 14px; - font-weight: 400; - border: none !important; - box-shadow: none; - - color: var(--gray-900); - background-color: var(--gray-100); - - &:hover { - box-shadow: var(--btn-shadow); - } -} - -.btn-view-more { - font-size: 14px; -} - -.rating-summary-section { - display: flex; -} - -.rating-summary-title { - margin-top: 0.15rem; - font-size: 18px; -} - -.rating-summary-numbers { - display: flex; - flex-direction: column; - align-items: center; - - border-right: solid 1px var(--gray-100); -} - -.user-review-title { - margin-top: 0.15rem; - font-size: 15px; - font-weight: 600; -} - -.rating { - --star-fill: var(--gray-300); - .star-hover { - --star-fill: var(--yellow-100); - } - .star-click { - --star-fill: var(--yellow-300); - } -} - -.ratings-pill { - background-color: var(--gray-100); - padding: .5rem 1rem; - border-radius: 66px; -} - -.review { - max-width: 80%; - line-height: 1.6; - padding-bottom: 0.5rem; - border-bottom: 1px solid #E2E6E9; -} - -.review-signature { - display: flex; - font-size: 13px; - color: var(--gray-500); - font-weight: 400; - - .reviewer { - padding-right: 8px; - color: var(--gray-600); - } -} - -.rating-progress-bar-section { - padding-bottom: 2rem; - - .rating-bar-title { - margin-left: -15px; - } - - .rating-progress-bar { - margin-bottom: 4px; - height: 7px; - margin-top: 6px; - - .progress-bar-cosmetic { - background-color: var(--gray-600); - border-radius: var(--border-radius); - } - } -} - -.offer-container { - font-size: 14px; -} - -#search-results-container { - border: 1px solid var(--gray-200); - padding: .25rem 1rem; - - .category-chip { - background-color: var(--gray-100); - border: none !important; - box-shadow: none; - } - - .recent-search { - padding: .5rem .5rem; - border-radius: var(--border-radius); - - &:hover { - background-color: var(--gray-100); - } - } -} - -#search-box { - background-color: white; - height: 100%; - padding-left: 2.5rem; - border: 1px solid var(--gray-200); -} - -.search-icon { - position: absolute; - left: 0; - top: 0; - width: 2.5rem; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - padding-bottom: 1px; -} - -#toggle-view { - float: right; - - .btn-primary { - background-color: var(--gray-600); - box-shadow: 0 0 0 0.2rem var(--gray-400); - } -} - -.placeholder-div { - height:80%; - width: -webkit-fill-available; - padding: 50px; - text-align: center; - background-color: #F9FAFA; - border-top-left-radius: calc(0.75rem - 1px); - border-top-right-radius: calc(0.75rem - 1px); -} -.placeholder { - font-size: 72px; -} - -[data-path="cart"] { - .modal-backdrop { - background-color: var(--gray-50); // lighter backdrop only on cart freeze - } -} - -.item-thumb { - height: 50px; - max-width: 80px; - min-width: 80px; - object-fit: cover; -} - -.brand-line { - color: gray; -} - -.btn-next, .btn-prev { - font-size: 14px; -} - -.alert-error { - color: #e27a84; - background-color: #fff6f7; - border-color: #f5c6cb; -} - -.font-md { - font-size: 14px !important; -} - -.in-green { - color: var(--green-info) !important; - font-weight: 500; -} - -.has-stock { - font-weight: 400 !important; -} - -.out-of-stock { - font-weight: 400; - font-size: 14px; - line-height: 20px; - color: #F47A7A; -} - -.mt-minus-2 { - margin-top: -2rem; -} - -.mt-minus-1 { - margin-top: -1rem; -} \ No newline at end of file diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 8ff681b048..95d2d2c577 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -26,7 +26,6 @@ class Quotation(SellingController): self.set_status() self.validate_uom_is_integer("stock_uom", "qty") self.validate_valid_till() - self.validate_shopping_cart_items() self.set_customer_name() if self.items: self.with_items = 1 @@ -42,26 +41,6 @@ class Quotation(SellingController): if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date): frappe.throw(_("Valid till date cannot be before transaction date")) - def validate_shopping_cart_items(self): - if self.order_type != "Shopping Cart": - return - - for item in self.items: - has_web_item = frappe.db.exists("Website Item", {"item_code": item.item_code}) - - # If variant is unpublished but template is published: valid - template = frappe.get_cached_value("Item", item.item_code, "variant_of") - if template and not has_web_item: - has_web_item = frappe.db.exists("Website Item", {"item_code": template}) - - if not has_web_item: - frappe.throw( - _("Row #{0}: Item {1} must have a Website Item for Shopping Cart Quotations").format( - item.idx, frappe.bold(item.item_code) - ), - title=_("Unpublished Item"), - ) - def set_has_alternative_item(self): """Mark 'Has Alternative Item' for rows.""" if not any(row.is_alternative for row in self.get("items")): @@ -263,8 +242,8 @@ def make_sales_order(source_name: str, target_doc=None): return _make_sales_order(source_name, target_doc) -def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): - customer = _make_customer(source_name, ignore_permissions) +def _make_sales_order(source_name, target_doc=None, customer_group=None, ignore_permissions=False): + customer = _make_customer(source_name, ignore_permissions, customer_group) ordered_items = frappe._dict( frappe.db.get_all( "Sales Order Item", @@ -428,7 +407,7 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): return doclist -def _make_customer(source_name, ignore_permissions=False): +def _make_customer(source_name, ignore_permissions=False, customer_group=None): quotation = frappe.db.get_value( "Quotation", source_name, ["order_type", "party_name", "customer_name"], as_dict=1 ) @@ -445,10 +424,7 @@ def _make_customer(source_name, ignore_permissions=False): customer_doclist = _make_customer(lead_name, ignore_permissions=ignore_permissions) customer = frappe.get_doc(customer_doclist) customer.flags.ignore_permissions = ignore_permissions - if quotation.get("party_name") == "Shopping Cart": - customer.customer_group = frappe.db.get_value( - "E Commerce Settings", None, "default_customer_group" - ) + customer.customer_group = customer_group try: customer.insert() diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 5623a12cdd..590cd3d0cf 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -161,15 +161,6 @@ class TestQuotation(FrappeTestCase): make_sales_order(quotation.name) - def test_shopping_cart_without_website_item(self): - if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}): - frappe.get_last_doc("Website Item", {"item_code": "_Test Item Home Desktop 100"}).delete() - - quotation = frappe.copy_doc(test_records[0]) - quotation.order_type = "Shopping Cart" - quotation.valid_till = getdate() - self.assertRaises(frappe.ValidationError, quotation.validate) - def test_create_quotation_with_margin(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order from erpnext.selling.doctype.sales_order.sales_order import ( diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index 4b04ac1d5e..d6eb11f73d 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -71,20 +71,6 @@ frappe.ui.form.on("Item Group", { frappe.set_route("List", "Item", {"item_group": frm.doc.name}); }); } - - frappe.model.with_doctype('Website Item', () => { - const web_item_meta = frappe.get_meta('Website Item'); - - const valid_fields = web_item_meta.fields.filter(df => - ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden - ).map(df => - ({ label: df.label, value: df.fieldname }) - ); - - frm.get_field("filter_fields").grid.update_docfield_property( - 'fieldname', 'options', valid_fields - ); - }); }, set_root_readonly: function(frm) { diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index e0f5090474..dfa5a8ed0a 100644 --- a/erpnext/setup/doctype/item_group/item_group.json +++ b/erpnext/setup/doctype/item_group/item_group.json @@ -19,22 +19,9 @@ "item_group_defaults", "sec_break_taxes", "taxes", - "sb9", - "route", - "website_title", - "description", - "show_in_website", - "include_descendants", - "column_break_16", - "weightage", - "slideshow", - "website_specifications", - "website_filters_section", - "filter_fields", - "filter_attributes", "lft", - "rgt", - "old_parent" + "old_parent", + "rgt" ], "fields": [ { @@ -106,54 +93,6 @@ "label": "Taxes", "options": "Item Tax" }, - { - "fieldname": "sb9", - "fieldtype": "Section Break", - "label": "Website Settings" - }, - { - "default": "0", - "description": "Make Item Group visible in website", - "fieldname": "show_in_website", - "fieldtype": "Check", - "label": "Show in Website" - }, - { - "depends_on": "show_in_website", - "fieldname": "route", - "fieldtype": "Data", - "label": "Route", - "no_copy": 1, - "unique": 1 - }, - { - "depends_on": "show_in_website", - "fieldname": "weightage", - "fieldtype": "Int", - "label": "Weightage" - }, - { - "depends_on": "show_in_website", - "description": "Show this slideshow at the top of the page", - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Slideshow", - "options": "Website Slideshow" - }, - { - "depends_on": "show_in_website", - "description": "HTML / Banner that will show on the top of product list.", - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Description" - }, - { - "depends_on": "show_in_website", - "fieldname": "website_specifications", - "fieldtype": "Table", - "label": "Website Specifications", - "options": "Item Website Specification" - }, { "fieldname": "lft", "fieldtype": "Int", @@ -188,43 +127,6 @@ "options": "Item Group", "print_hide": 1, "report_hide": 1 - }, - { - "collapsible": 1, - "depends_on": "show_in_website", - "fieldname": "website_filters_section", - "fieldtype": "Section Break", - "label": "Website Filters" - }, - { - "fieldname": "filter_fields", - "fieldtype": "Table", - "label": "Item Fields", - "options": "Website Filter Field" - }, - { - "fieldname": "filter_attributes", - "fieldtype": "Table", - "label": "Attributes", - "options": "Website Attribute" - }, - { - "depends_on": "show_in_website", - "fieldname": "website_title", - "fieldtype": "Data", - "label": "Title" - }, - { - "fieldname": "column_break_16", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "show_in_website", - "description": "Include Website Items belonging to child Item Groups", - "fieldname": "include_descendants", - "fieldtype": "Check", - "label": "Include Descendants" } ], "icon": "fa fa-sitemap", @@ -233,7 +135,7 @@ "is_tree": 1, "links": [], "max_attachments": 3, - "modified": "2023-08-28 22:27:48.382985", + "modified": "2023-10-12 13:44:13.611287", "modified_by": "Administrator", "module": "Setup", "name": "Item Group", diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index cc67c696b4..fe7a241dc4 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -2,39 +2,19 @@ # License: GNU General Public License v3. See license.txt import copy -from urllib.parse import quote import frappe from frappe import _ -from frappe.utils import cint from frappe.utils.nestedset import NestedSet -from frappe.website.utils import clear_cache -from frappe.website.website_generator import WebsiteGenerator - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ECommerceSettings -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder -class ItemGroup(NestedSet, WebsiteGenerator): - nsm_parent_field = "parent_item_group" - website = frappe._dict( - condition_field="show_in_website", - template="templates/generators/item_group.html", - no_cache=1, - no_breadcrumbs=1, - ) - +class ItemGroup(NestedSet): def validate(self): - super(ItemGroup, self).validate() - if not self.parent_item_group and not frappe.flags.in_test: if frappe.db.exists("Item Group", _("All Item Groups")): self.parent_item_group = _("All Item Groups") - - self.make_route() self.validate_item_group_defaults() self.check_item_tax() - ECommerceSettings.validate_field_filters(self.filter_fields, enable_field_filters=True) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" @@ -53,66 +33,13 @@ class ItemGroup(NestedSet, WebsiteGenerator): def on_update(self): NestedSet.on_update(self) - invalidate_cache_for(self) self.validate_one_root() self.delete_child_item_groups_key() - def make_route(self): - """Make website route""" - if not self.route: - self.route = "" - if self.parent_item_group: - parent_item_group = frappe.get_doc("Item Group", self.parent_item_group) - - # make parent route only if not root - if parent_item_group.parent_item_group and parent_item_group.route: - self.route = parent_item_group.route + "/" - - self.route += self.scrub(self.item_group_name) - - return self.route - def on_trash(self): NestedSet.on_trash(self, allow_root_deletion=True) - WebsiteGenerator.on_trash(self) self.delete_child_item_groups_key() - def get_context(self, context): - context.show_search = True - context.body_class = "product-page" - context.page_length = ( - cint(frappe.db.get_single_value("E Commerce Settings", "products_per_page")) or 6 - ) - context.search_link = "/product_search" - - filter_engine = ProductFiltersBuilder(self.name) - - context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_filters() - - context.update({"parents": get_parent_item_groups(self.parent_item_group), "title": self.name}) - - if self.slideshow: - values = {"show_indicators": 1, "show_controls": 0, "rounded": 1, "slider_name": self.slideshow} - slideshow = frappe.get_doc("Website Slideshow", self.slideshow) - slides = slideshow.get({"doctype": "Website Slideshow Item"}) - for index, slide in enumerate(slides): - values[f"slide_{index + 1}_image"] = slide.image - values[f"slide_{index + 1}_title"] = slide.heading - values[f"slide_{index + 1}_subtitle"] = slide.description - values[f"slide_{index + 1}_theme"] = slide.get("theme") or "Light" - values[f"slide_{index + 1}_content_align"] = slide.get("content_align") or "Centre" - values[f"slide_{index + 1}_primary_action"] = slide.url - - context.slideshow = values - - context.no_breadcrumbs = False - context.title = self.website_title or self.name - context.name = self.name - context.item_group_name = self.item_group_name - - return context - def delete_child_item_groups_key(self): frappe.cache().hdel("child_item_groups", self.name) @@ -122,20 +49,6 @@ class ItemGroup(NestedSet, WebsiteGenerator): validate_item_default_company_links(self.item_group_defaults) -def get_child_groups_for_website(item_group_name, immediate=False, include_self=False): - """Returns child item groups *excluding* passed group.""" - item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) - filters = {"lft": [">", item_group.lft], "rgt": ["<", item_group.rgt], "show_in_website": 1} - - if immediate: - filters["parent_item_group"] = item_group_name - - if include_self: - filters.update({"lft": [">=", item_group.lft], "rgt": ["<=", item_group.rgt]}) - - return frappe.get_all("Item Group", filters=filters, fields=["name", "route"], order_by="name") - - def get_child_item_groups(item_group_name): item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) @@ -149,63 +62,6 @@ def get_child_item_groups(item_group_name): return child_item_groups or {} -def get_item_for_list_in_html(context): - # add missing absolute link in files - # user may forget it during upload - if (context.get("website_image") or "").startswith("files/"): - context["website_image"] = "/" + quote(context["website_image"]) - - products_template = "templates/includes/products_as_list.html" - - return frappe.get_template(products_template).render(context) - - -def get_parent_item_groups(item_group_name, from_item=False): - settings = frappe.get_cached_doc("E Commerce Settings") - - if settings.enable_field_filters: - base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"} - else: - base_nav_page = {"name": _("All Products"), "route": "/all-products"} - - if from_item and frappe.request.environ.get("HTTP_REFERER"): - # base page after 'Home' will vary on Item page - last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] - if last_page and last_page in ("shop-by-category", "all-products"): - base_nav_page_title = " ".join(last_page.split("-")).title() - base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} - - base_parents = [ - {"name": _("Home"), "route": "/"}, - base_nav_page, - ] - - if not item_group_name: - return base_parents - - item_group = frappe.db.get_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) - parent_groups = frappe.db.sql( - """select name, route from `tabItem Group` - where lft <= %s and rgt >= %s - and show_in_website=1 - order by lft asc""", - (item_group.lft, item_group.rgt), - as_dict=True, - ) - - return base_parents + parent_groups - - -def invalidate_cache_for(doc, item_group=None): - if not item_group: - item_group = doc.name - - for d in get_parent_item_groups(item_group): - item_group_name = frappe.db.get_value("Item Group", d.get("name")) - if item_group_name: - clear_cache(frappe.db.get_value("Item Group", item_group_name, "route")) - - def get_item_group_defaults(item, company): item = frappe.get_cached_doc("Item", item) item_group = frappe.get_cached_doc("Item Group", item.item_group) diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index ace5cca0b0..d4aac5ee46 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -33,20 +33,6 @@ def create_fiscal_year_and_company(args): ).insert() -def enable_shopping_cart(args): # nosemgrep - # Needs price_lists - frappe.get_doc( - { - "doctype": "E Commerce Settings", - "enabled": 1, - "company": args.get("company_name"), - "price_list": frappe.db.get_value("Price List", {"selling": 1}), - "default_customer_group": _("Individual"), - "quotation_series": "QTN-", - } - ).insert() - - def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year if start_year == getdate(fy_end_date).year: diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index ae6881b99e..2205924e50 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -454,7 +454,6 @@ def install_defaults(args=None): # nosemgrep set_global_defaults(args) update_stock_settings() - update_shopping_cart_settings(args) args.update({"set_default": 1}) create_bank_account(args) @@ -529,20 +528,6 @@ def create_bank_account(args): pass -def update_shopping_cart_settings(args): # nosemgrep - shopping_cart = frappe.get_doc("E Commerce Settings") - shopping_cart.update( - { - "enabled": 1, - "company": args.company_name, - "price_list": frappe.db.get_value("Price List", {"selling": 1}), - "default_customer_group": _("Individual"), - "quotation_series": "QTN-", - } - ) - shopping_cart.update_single(shopping_cart.get_valid_dict()) - - def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year if start_year == getdate(fy_end_date).year: diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 5806fd1f78..2f9cec40b0 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,500 +1,500 @@ { - "charts": [], - "content": "[{\"id\":\"NO5yYHJopc\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"col\":12}},{\"id\":\"CDxIM-WuZ9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"-Uh7DKJNJX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"id\":\"K9ST9xcDXh\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"id\":\"27IdVHVQMb\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"id\":\"Rwp5zff88b\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"id\":\"hkfnQ2sevf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Global Defaults\",\"col\":3}},{\"id\":\"jjxI_PDawD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"id\":\"R3CoYYFXye\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yynbm1J_VO\",\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"id\":\"KDCv2MvSg3\",\"type\":\"card\",\"data\":{\"card_name\":\"Module Settings\",\"col\":4}},{\"id\":\"Q0_bqT7cxQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"id\":\"UnqK5haBnh\",\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"id\":\"kp7u1H5hCd\",\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"id\":\"Ufc3jycgy9\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"89bSNzv3Yh\",\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", - "creation": "2022-01-27 13:14:47.349433", - "custom_blocks": [], - "docstatus": 0, - "doctype": "Workspace", - "for_user": "", - "hide_custom": 0, - "icon": "setting", - "idx": 0, - "is_hidden": 0, - "label": "ERPNext Settings", - "links": [ - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Import Data", - "link_count": 0, - "link_to": "Data Import", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Export Data", - "link_count": 0, - "link_to": "Data Export", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bulk Update", - "link_count": 0, - "link_to": "Bulk Update", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Download Backups", - "link_count": 0, - "link_to": "backups", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Deleted Documents", - "link_count": 0, - "link_to": "Deleted Document", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Email / Notifications", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Email Account", - "link_count": 0, - "link_to": "Email Account", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Email Domain", - "link_count": 0, - "link_to": "Email Domain", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Notification", - "link_count": 0, - "link_to": "Notification", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Email Template", - "link_count": 0, - "link_to": "Email Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Auto Email Report", - "link_count": 0, - "link_to": "Auto Email Report", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Newsletter", - "link_count": 0, - "link_to": "Newsletter", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Notification Settings", - "link_count": 0, - "link_to": "Notification Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Website", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Website Settings", - "link_count": 0, - "link_to": "Website Settings", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Website Theme", - "link_count": 0, - "link_to": "Website Theme", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Website Script", - "link_count": 0, - "link_to": "Website Script", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "About Us Settings", - "link_count": 0, - "link_to": "About Us Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Contact Us Settings", - "link_count": 0, - "link_to": "Contact Us Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Printing", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Format Builder", - "link_count": 0, - "link_to": "print-format-builder", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Settings", - "link_count": 0, - "link_to": "Print Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Format", - "link_count": 0, - "link_to": "Print Format", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Style", - "link_count": 0, - "link_to": "Print Style", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Workflow", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow", - "link_count": 0, - "link_to": "Workflow", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow State", - "link_count": 0, - "link_to": "Workflow State", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow Action", - "link_count": 0, - "link_to": "Workflow Action", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Core", - "link_count": 3, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "System Settings", - "link_count": 0, - "link_to": "System Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Domain Settings", - "link_count": 0, - "link_to": "Domain Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Global Defaults", - "link_count": 0, - "link_to": "Global Defaults", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Module Settings", - "link_count": 8, - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Accounts Settings", - "link_count": 0, - "link_to": "Accounts Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Stock Settings", - "link_count": 0, - "link_to": "Stock Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Selling Settings", - "link_count": 0, - "link_to": "Selling Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Buying Settings", - "link_count": 0, - "link_to": "Buying Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Manufacturing Settings", - "link_count": 0, - "link_to": "Manufacturing Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "CRM Settings", - "link_count": 0, - "link_to": "CRM Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Projects Settings", - "link_count": 0, - "link_to": "Projects Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Support Settings", - "link_count": 0, - "link_to": "Support Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - } - ], - "modified": "2023-05-24 14:47:25.356531", - "modified_by": "Administrator", - "module": "Setup", - "name": "ERPNext Settings", - "number_cards": [], - "owner": "Administrator", - "parent_page": "", - "public": 1, - "quick_lists": [], - "restrict_to_domain": "", - "roles": [], - "sequence_id": 19.0, - "shortcuts": [ - { - "color": "Grey", - "doc_view": "List", - "label": "Print Settings", - "link_to": "Print Settings", - "type": "DocType" - }, - { - "color": "Grey", - "doc_view": "List", - "label": "System Settings", - "link_to": "System Settings", - "type": "DocType" - }, - { - "icon": "accounting", - "label": "Accounts Settings", - "link_to": "Accounts Settings", - "type": "DocType" - }, - { - "color": "Grey", - "doc_view": "List", - "label": "Global Defaults", - "link_to": "Global Defaults", - "type": "DocType" - }, - { - "icon": "stock", - "label": "Stock Settings", - "link_to": "Stock Settings", - "type": "DocType" - }, - { - "icon": "sell", - "label": "Selling Settings", - "link_to": "Selling Settings", - "type": "DocType" - }, - { - "icon": "buying", - "label": "Buying Settings", - "link_to": "Buying Settings", - "type": "DocType" - } - ], - "title": "ERPNext Settings" -} \ No newline at end of file + "charts": [], + "content": "[{\"id\":\"NO5yYHJopc\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"col\":12}},{\"id\":\"CDxIM-WuZ9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"-Uh7DKJNJX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"id\":\"K9ST9xcDXh\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"id\":\"27IdVHVQMb\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"id\":\"Rwp5zff88b\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"id\":\"hkfnQ2sevf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Global Defaults\",\"col\":3}},{\"id\":\"jjxI_PDawD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"id\":\"R3CoYYFXye\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yynbm1J_VO\",\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"id\":\"KDCv2MvSg3\",\"type\":\"card\",\"data\":{\"card_name\":\"Module Settings\",\"col\":4}},{\"id\":\"Q0_bqT7cxQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"id\":\"UnqK5haBnh\",\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"id\":\"kp7u1H5hCd\",\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"id\":\"Ufc3jycgy9\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"89bSNzv3Yh\",\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", + "creation": "2022-01-27 13:14:47.349433", + "custom_blocks": [], + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "setting", + "idx": 0, + "is_hidden": 0, + "label": "ERPNext Settings", + "links": [ + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Import Data", + "link_count": 0, + "link_to": "Data Import", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Export Data", + "link_count": 0, + "link_to": "Data Export", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bulk Update", + "link_count": 0, + "link_to": "Bulk Update", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Download Backups", + "link_count": 0, + "link_to": "backups", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Deleted Documents", + "link_count": 0, + "link_to": "Deleted Document", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Email / Notifications", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Account", + "link_count": 0, + "link_to": "Email Account", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Domain", + "link_count": 0, + "link_to": "Email Domain", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Notification", + "link_count": 0, + "link_to": "Notification", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Template", + "link_count": 0, + "link_to": "Email Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Auto Email Report", + "link_count": 0, + "link_to": "Auto Email Report", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Newsletter", + "link_count": 0, + "link_to": "Newsletter", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Notification Settings", + "link_count": 0, + "link_to": "Notification Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Website", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Website Settings", + "link_count": 0, + "link_to": "Website Settings", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Website Theme", + "link_count": 0, + "link_to": "Website Theme", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Website Script", + "link_count": 0, + "link_to": "Website Script", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "About Us Settings", + "link_count": 0, + "link_to": "About Us Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Contact Us Settings", + "link_count": 0, + "link_to": "Contact Us Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Printing", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Format Builder", + "link_count": 0, + "link_to": "print-format-builder", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Settings", + "link_count": 0, + "link_to": "Print Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Format", + "link_count": 0, + "link_to": "Print Format", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Style", + "link_count": 0, + "link_to": "Print Style", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Workflow", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workflow", + "link_count": 0, + "link_to": "Workflow", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workflow State", + "link_count": 0, + "link_to": "Workflow State", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workflow Action", + "link_count": 0, + "link_to": "Workflow Action", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Core", + "link_count": 3, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "System Settings", + "link_count": 0, + "link_to": "System Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Domain Settings", + "link_count": 0, + "link_to": "Domain Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Global Defaults", + "link_count": 0, + "link_to": "Global Defaults", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Module Settings", + "link_count": 8, + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Accounts Settings", + "link_count": 0, + "link_to": "Accounts Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Stock Settings", + "link_count": 0, + "link_to": "Stock Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Selling Settings", + "link_count": 0, + "link_to": "Selling Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Buying Settings", + "link_count": 0, + "link_to": "Buying Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Manufacturing Settings", + "link_count": 0, + "link_to": "Manufacturing Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "CRM Settings", + "link_count": 0, + "link_to": "CRM Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Projects Settings", + "link_count": 0, + "link_to": "Projects Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Support Settings", + "link_count": 0, + "link_to": "Support Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2023-05-24 14:47:25.356531", + "modified_by": "Administrator", + "module": "Setup", + "name": "ERPNext Settings", + "number_cards": [], + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "restrict_to_domain": "", + "roles": [], + "sequence_id": 19.0, + "shortcuts": [ + { + "color": "Grey", + "doc_view": "List", + "label": "Print Settings", + "link_to": "Print Settings", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "List", + "label": "System Settings", + "link_to": "System Settings", + "type": "DocType" + }, + { + "icon": "accounting", + "label": "Accounts Settings", + "link_to": "Accounts Settings", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "List", + "label": "Global Defaults", + "link_to": "Global Defaults", + "type": "DocType" + }, + { + "icon": "stock", + "label": "Stock Settings", + "link_to": "Stock Settings", + "type": "DocType" + }, + { + "icon": "sell", + "label": "Selling Settings", + "link_to": "Selling Settings", + "type": "DocType" + }, + { + "icon": "buying", + "label": "Buying Settings", + "link_to": "Buying Settings", + "type": "DocType" + } + ], + "title": "ERPNext Settings" + } \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 4ae9bf5b2a..6e810e5987 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -125,36 +125,6 @@ frappe.ui.form.on("Item", { erpnext.toggle_naming_series(); } - if (!frm.doc.published_in_website) { - frm.add_custom_button(__("Publish in Website"), function() { - frappe.call({ - method: "erpnext.e_commerce.doctype.website_item.website_item.make_website_item", - args: {doc: frm.doc}, - freeze: true, - freeze_message: __("Publishing Item ..."), - callback: function(result) { - frappe.msgprint({ - message: __("Website Item {0} has been created.", - [repl('%(item)s', { - item_encoded: encodeURIComponent(result.message[0]), - item: result.message[1] - })] - ), - title: __("Published"), - indicator: "green" - }); - } - }); - }, __('Actions')); - } else { - frm.add_custom_button(__("Website Item"), function() { - frappe.db.get_value("Website Item", {item_code: frm.doc.name}, "name", (d) => { - if (!d.name) frappe.throw(__("Website Item not found")); - frappe.set_route("Form", "Website Item", d.name); - }); - }, __("View")); - } - erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 1bcddfa77e..54491bbee3 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -117,7 +117,6 @@ "customer_code", "default_item_manufacturer", "default_manufacturer_part_no", - "published_in_website", "total_projected_qty" ], "fields": [ @@ -815,14 +814,6 @@ "label": "Default Manufacturer Part No", "read_only": 1 }, - { - "default": "0", - "depends_on": "published_in_website", - "fieldname": "published_in_website", - "fieldtype": "Check", - "label": "Published in Website", - "read_only": 1 - }, { "default": "1", "fieldname": "grant_commission", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index aff958738a..9e281990b5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -32,7 +32,6 @@ from erpnext.controllers.item_variant import ( make_variant_item_code, validate_item_variant_attributes, ) -from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for from erpnext.stock.doctype.item_default.item_default import ItemDefault @@ -122,10 +121,8 @@ class Item(Document): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") def on_update(self): - invalidate_cache_for_item(self) self.update_variants() self.update_item_price() - self.update_website_item() def validate_description(self): """Clean HTML description if set""" @@ -248,29 +245,6 @@ class Item(Document): if self.stock_uom not in uoms_list: self.append("uoms", {"uom": self.stock_uom, "conversion_factor": 1}) - def update_website_item(self): - """Update Website Item if change in Item impacts it.""" - web_item = frappe.db.exists("Website Item", {"item_code": self.item_code}) - - if web_item: - changed = {} - editable_fields = ["item_name", "item_group", "stock_uom", "brand", "description", "disabled"] - doc_before_save = self.get_doc_before_save() - - for field in editable_fields: - if doc_before_save.get(field) != self.get(field): - if field == "disabled": - changed["published"] = not self.get(field) - else: - changed[field] = self.get(field) - - if not changed: - return - - web_item_doc = frappe.get_doc("Website Item", web_item) - web_item_doc.update(changed) - web_item_doc.save() - def validate_item_tax_net_rate_range(self): for tax in self.get("taxes"): if flt(tax.maximum_net_rate) < flt(tax.minimum_net_rate): @@ -454,7 +428,6 @@ class Item(Document): if merge: self.validate_properties_before_merge(new_name) self.validate_duplicate_product_bundles_before_merge(old_name, new_name) - self.validate_duplicate_website_item_before_merge(old_name, new_name) self.delete_old_bins(old_name) def after_rename(self, old_name, new_name, merge): @@ -466,9 +439,6 @@ class Item(Document): title=_("Note"), ) - if self.published_in_website: - invalidate_cache_for_item(self) - frappe.db.set_value("Item", new_name, "item_code", new_name) if merge: @@ -554,27 +524,6 @@ class Item(Document): ) frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError) - def validate_duplicate_website_item_before_merge(self, old_name, new_name): - """ - Block merge if both old and new items have website items against them. - This is to avoid duplicate website items after merging. - """ - web_items = frappe.get_all( - "Website Item", - filters={"item_code": ["in", [old_name, new_name]]}, - fields=["item_code", "name"], - ) - - if len(web_items) <= 1: - return - - old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0] - web_item_link = get_link_to_form("Website Item", old_web_item) - old_name, new_name = frappe.bold(old_name), frappe.bold(new_name) - - msg = f"Please delete linked Website Item {frappe.bold(web_item_link)} before merging {old_name} into {new_name}" - frappe.throw(_(msg), title=_("Cannot Merge"), exc=DataValidationError) - def set_last_purchase_rate(self, new_name): last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) @@ -1151,32 +1100,6 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): return out -def invalidate_cache_for_item(doc): - """Invalidate Item Group cache and rebuild ItemVariantsCacheManager.""" - invalidate_cache_for(doc, doc.item_group) - - if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group: - invalidate_cache_for(doc, doc.old_item_group) - - invalidate_item_variants_cache_for_website(doc) - - -def invalidate_item_variants_cache_for_website(doc): - """Rebuild ItemVariantsCacheManager via Item or Website Item.""" - from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager - - item_code = None - is_web_item = doc.get("published_in_website") or doc.get("published") - if doc.has_variants and is_web_item: - item_code = doc.item_code - elif doc.variant_of and frappe.db.get_value("Item", doc.variant_of, "published_in_website"): - item_code = doc.variant_of - - if item_code: - item_cache = ItemVariantsCacheManager(item_code) - item_cache.rebuild_cache() - - def check_stock_uom_with_bin(item, stock_uom): if stock_uom == frappe.db.get_value("Item", item, "stock_uom"): return diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py index 34bb4d1225..88ae34f228 100644 --- a/erpnext/stock/doctype/item/item_dashboard.py +++ b/erpnext/stock/doctype/item/item_dashboard.py @@ -32,6 +32,5 @@ def get_data(): {"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]}, {"label": _("Traceability"), "items": ["Serial No", "Batch"]}, {"label": _("Stock Movement"), "items": ["Stock Entry", "Stock Reconciliation"]}, - {"label": _("E-commerce"), "items": ["Website Item"]}, ], } diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index e77d53a367..21c0f18cc3 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -13,9 +13,6 @@ class PriceList(Document): if not cint(self.buying) and not cint(self.selling): throw(_("Price List must be applicable for Buying or Selling")) - if not self.is_new(): - self.check_impact_on_shopping_cart() - def on_update(self): self.set_default_if_missing() self.update_item_price() @@ -37,19 +34,6 @@ class PriceList(Document): (self.currency, cint(self.buying), cint(self.selling), self.name), ) - def check_impact_on_shopping_cart(self): - "Check if Price List currency change impacts E Commerce Cart." - from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - validate_cart_settings, - ) - - doc_before_save = self.get_doc_before_save() - currency_changed = self.currency != doc_before_save.currency - affects_cart = self.name == frappe.db.get_single_value("E Commerce Settings", "price_list") - - if currency_changed and affects_cart: - validate_cart_settings() - def on_trash(self): self.delete_price_list_details_key() diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html deleted file mode 100644 index 358c1c52e5..0000000000 --- a/erpnext/templates/generators/item/item.html +++ /dev/null @@ -1,80 +0,0 @@ -{% extends "templates/web.html" %} -{% from "erpnext/templates/includes/macros.html" import recommended_item_row %} - -{% block title %} {{ title }} {% endblock %} - -{% block breadcrumbs %} -
- {% include "templates/includes/breadcrumbs.html" %} -
-{% endblock %} - -{% block page_content %} -
- {% from "erpnext/templates/includes/macros.html" import product_image %} -
-
- -
- {% include "templates/generators/item/item_image.html" %} - {% include "templates/generators/item/item_details.html" %} -
-
-
-
- - -
- {% set show_recommended_items = recommended_items and shopping_cart.cart_settings.enable_recommendations %} - {% set info_col = 'col-9' if show_recommended_items else 'col-12' %} - - {% set padding_top = 'pt-0' if (show_tabs and tabs) else '' %} - -
-
-
- - {% if show_tabs and tabs %} -
- - {{ web_block("Section with Tabs", values=tabs, add_container=0, - add_top_padding=0, add_bottom_padding=0) - }} -
- {% elif website_specifications %} - {% include "templates/generators/item/item_specifications.html"%} - {% endif %} - - - {{ doc.website_content or '' }} - - - {% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %} - {% include "templates/generators/item/item_reviews.html"%} - {% endif %} -
-
-
- - - {% if show_recommended_items %} - - {% endif %} - -
-{% endblock %} - -{% block base_scripts %} - - -{{ include_script("frappe-web.bundle.js") }} -{{ include_script("controls.bundle.js") }} -{{ include_script("dialog.bundle.js") }} -{% endblock %} diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html deleted file mode 100644 index 9bd3f7514c..0000000000 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ /dev/null @@ -1,180 +0,0 @@ -{% if shopping_cart and shopping_cart.cart_settings.enabled %} - -{% set cart_settings = shopping_cart.cart_settings %} -{% set product_info = shopping_cart.product_info %} - -
-
- - {% if cart_settings.show_price and product_info.price %} - {% set price_info = product_info.price %} - -
- - - {{ price_info.formatted_price_sales_uom }} - {{ price_info.currency }} - - - - {% if price_info.formatted_mrp %} - - MRP {{ price_info.formatted_mrp }} - - - -{{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}} - - {% endif %} - - - - ({{ price_info.formatted_price }} / {{ product_info.uom }}) - -
- {% else %} - {{ _("UOM") }} : {{ product_info.uom }} - {% endif %} - - {% if cart_settings.show_stock_availability %} -
- {% if product_info.get("on_backorder") %} - - {{ _('Available on backorder') }} - - {% elif product_info.in_stock == 0 %} - - {{ _('Out of stock') }} - - {% elif product_info.in_stock == 1 %} - - {{ _('In stock') }} - {% if product_info.show_stock_qty and product_info.stock_qty %} - ({{ product_info.stock_qty }}) - {% endif %} - - {% endif %} -
- {% endif %} - - - {% if doc.offers %} -
-
- - - - Available Offers -
-
- {% for offer in doc.offers %} -
-
- - - - - - -
-

- {{ _(offer.offer_title) }}: - {{ _(offer.offer_subtitle) if offer.offer_subtitle else '' }} - - {{ _("More") }} - -

-
- {% endfor %} -
- {% endif %} - - -
-
- - {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} - - - {% endif %} - - - {% if cart_settings.show_contact_us_button %} - {% include "templates/generators/item/item_inquiry.html" %} - {% endif %} -
-
-
-
- - - -{% endif %} diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html deleted file mode 100644 index e97a275fbd..0000000000 --- a/erpnext/templates/generators/item/item_configure.html +++ /dev/null @@ -1,20 +0,0 @@ -{% if shopping_cart and shopping_cart.cart_settings.enabled %} -{% set cart_settings = shopping_cart.cart_settings %} - -
- {% if cart_settings.enable_variants | int %} - - {% endif %} - {% if cart_settings.show_contact_us_button %} - {% include "templates/generators/item/item_inquiry.html" %} - {% endif %} -
- -{% endif %} diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js deleted file mode 100644 index 9beba3fd01..0000000000 --- a/erpnext/templates/generators/item/item_configure.js +++ /dev/null @@ -1,343 +0,0 @@ -class ItemConfigure { - constructor(item_code, item_name) { - this.item_code = item_code; - this.item_name = item_name; - - this.get_attributes_and_values() - .then(attribute_data => { - this.attribute_data = attribute_data; - this.show_configure_dialog(); - }); - } - - show_configure_dialog() { - const fields = this.attribute_data.map(a => { - return { - fieldtype: 'Select', - label: a.attribute, - fieldname: a.attribute, - options: a.values.map(v => { - return { - label: v, - value: v - }; - }), - change: (e) => { - this.on_attribute_selection(e); - } - }; - }); - - this.dialog = new frappe.ui.Dialog({ - title: __('Select Variant for {0}', [this.item_name]), - fields, - on_hide: () => { - set_continue_configuration(); - } - }); - - this.attribute_data.forEach(a => { - const field = this.dialog.get_field(a.attribute); - const $a = $(`${__("Clear")}`); - $a.on('click', (e) => { - e.preventDefault(); - this.dialog.set_value(a.attribute, ''); - }); - field.$wrapper.find('.help-box').append($a); - }); - - this.append_status_area(); - this.dialog.show(); - - this.dialog.set_values(JSON.parse(localStorage.getItem(this.get_cache_key()))); - - $('.btn-configure').prop('disabled', false); - } - - on_attribute_selection(e) { - if (e) { - const changed_fieldname = $(e.target).data('fieldname'); - this.show_range_input_if_applicable(changed_fieldname); - } else { - this.show_range_input_for_all_fields(); - } - - const values = this.dialog.get_values(); - if (Object.keys(values).length === 0) { - this.clear_status(); - localStorage.removeItem(this.get_cache_key()); - return; - } - - // save state - localStorage.setItem(this.get_cache_key(), JSON.stringify(values)); - - // show - this.set_loading_status(); - - this.get_next_attribute_and_values(values) - .then(data => { - const { - valid_options_for_attributes, - } = data; - - this.set_item_found_status(data); - - for (let attribute in valid_options_for_attributes) { - const valid_options = valid_options_for_attributes[attribute]; - const options = this.dialog.get_field(attribute).df.options; - const new_options = options.map(o => { - o.disabled = !valid_options.includes(o.value); - return o; - }); - - this.dialog.set_df_property(attribute, 'options', new_options); - this.dialog.get_field(attribute).set_options(); - } - }); - } - - show_range_input_for_all_fields() { - this.dialog.fields.forEach(f => { - this.show_range_input_if_applicable(f.fieldname); - }); - } - - show_range_input_if_applicable(fieldname) { - const changed_field = this.dialog.get_field(fieldname); - const changed_value = changed_field.get_value(); - if (changed_value && changed_value.includes(' to ')) { - // possible range input - let numbers = changed_value.split(' to '); - numbers = numbers.map(number => parseFloat(number)); - - if (!numbers.some(n => isNaN(n))) { - numbers.sort((a, b) => a - b); - if (changed_field.$input_wrapper.find('.range-selector').length) { - return; - } - const parent = $('
') - .insertBefore(changed_field.$input_wrapper.find('.help-box')); - const control = frappe.ui.form.make_control({ - df: { - fieldtype: 'Int', - label: __('Enter value betweeen {0} and {1}', [numbers[0], numbers[1]]), - change: () => { - const value = control.get_value(); - if (value < numbers[0] || value > numbers[1]) { - control.$wrapper.addClass('was-validated'); - control.set_description( - __('Value must be between {0} and {1}', [numbers[0], numbers[1]])); - control.$input[0].setCustomValidity('error'); - } else { - control.$wrapper.removeClass('was-validated'); - control.set_description(''); - control.$input[0].setCustomValidity(''); - this.update_range_values(fieldname, value); - } - } - }, - render_input: true, - parent - }); - control.$wrapper.addClass('mt-3'); - } - } - } - - update_range_values(attribute, range_value) { - this.range_values = this.range_values || {}; - this.range_values[attribute] = range_value; - } - - show_remaining_optional_attributes() { - // show all attributes if remaining - // unselected attributes are all optional - const unselected_attributes = this.dialog.fields.filter(df => { - const value_selected = this.dialog.get_value(df.fieldname); - return !value_selected; - }); - const is_optional_attribute = df => { - const optional_attributes = this.attribute_data - .filter(a => a.optional).map(a => a.attribute); - return optional_attributes.includes(df.fieldname); - }; - if (unselected_attributes.every(is_optional_attribute)) { - unselected_attributes.forEach(df => { - this.dialog.fields_dict[df.fieldname].$wrapper.show(); - }); - } - } - - set_loading_status() { - this.dialog.$status_area.html(` - - `); - } - - set_item_found_status(data) { - const html = this.get_html_for_item_found(data); - this.dialog.$status_area.html(html); - } - - clear_status() { - this.dialog.$status_area.empty(); - } - - get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info, available_qty, settings }) { - const one_item = exact_match.length === 1 - ? exact_match[0] - : filtered_items_count === 1 - ? filtered_items[0] - : ''; - - let item_add_to_cart = one_item ? ` - - ` : ''; - - const items_found = filtered_items_count === 1 ? - __('{0} item found.', [filtered_items_count]) : - __('{0} items found.', [filtered_items_count]); - - /* eslint-disable indent */ - const item_found_status = exact_match.length === 1 - ? `` - : ``; - /* eslint-disable indent */ - - if (!product_info?.allow_items_not_in_stock && available_qty === 0 - && product_info && product_info?.is_stock_item) { - item_add_to_cart = ''; - } - - return ` - ${item_found_status} - ${item_add_to_cart} - `; - } - - btn_add_to_cart(e) { - if (frappe.session.user !== 'Guest') { - localStorage.removeItem(this.get_cache_key()); - } - const item_code = $(e.currentTarget).data('item-code'); - const additional_notes = Object.keys(this.range_values || {}).map(attribute => { - return `${attribute}: ${this.range_values[attribute]}`; - }).join('\n'); - erpnext.e_commerce.shopping_cart.update_cart({ - item_code, - additional_notes, - qty: 1 - }); - this.dialog.hide(); - } - - btn_clear_values() { - this.dialog.fields_list.forEach(f => { - if (f.df?.options) { - f.df.options = f.df.options.map(option => { - option.disabled = false; - return option; - }); - } - }); - this.dialog.clear(); - this.dialog.$status_area.empty(); - this.on_attribute_selection(); - } - - append_status_area() { - this.dialog.$status_area = $('
'); - this.dialog.$wrapper.find('.modal-body').append(this.dialog.$status_area); - this.dialog.$wrapper.on('click', '[data-action]', (e) => { - e.preventDefault(); - const $target = $(e.currentTarget); - const action = $target.data('action'); - const method = this[action]; - method.call(this, e); - }); - this.dialog.$wrapper.addClass('item-configurator-dialog'); - } - - get_next_attribute_and_values(selected_attributes) { - return this.call('erpnext.e_commerce.variant_selector.utils.get_next_attribute_and_values', { - item_code: this.item_code, - selected_attributes - }); - } - - get_attributes_and_values() { - return this.call('erpnext.e_commerce.variant_selector.utils.get_attributes_and_values', { - item_code: this.item_code - }); - } - - get_cache_key() { - return `configure:${this.item_code}`; - } - - call(method, args) { - // promisified frappe.call - return new Promise((resolve, reject) => { - frappe.call(method, args) - .then(r => resolve(r.message)) - .fail(reject); - }); - } -} - -function set_continue_configuration() { - const $btn_configure = $('.btn-configure'); - const { itemCode } = $btn_configure.data(); - - if (localStorage.getItem(`configure:${itemCode}`)) { - $btn_configure.text(__('Continue Selection')); - } else { - $btn_configure.text(__('Select Variant')); - } -} - -frappe.ready(() => { - const $btn_configure = $('.btn-configure'); - if (!$btn_configure.length) return; - const { itemCode, itemName } = $btn_configure.data(); - - set_continue_configuration(); - - $btn_configure.on('click', () => { - $btn_configure.prop('disabled', true); - new ItemConfigure(itemCode, itemName); - }); -}); diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html deleted file mode 100644 index 028936bf5f..0000000000 --- a/erpnext/templates/generators/item/item_details.html +++ /dev/null @@ -1,63 +0,0 @@ -{% set width_class = "expand" if not slides else "" %} -{% set cart_settings = shopping_cart.cart_settings %} -{% set product_info = shopping_cart.product_info %} -{% set price_info = product_info.get('price') or {} %} - -
-
- -
- {{ doc.web_item_name }} -
- - - {% if cart_settings.enable_wishlist %} - - {% endif %} -
- -

- - {{ _(doc.item_group) }} - - - {{ _("Item Code") }}: - - {{ doc.item_code }} -

- {% if has_variants %} - - {% include "templates/generators/item/item_configure.html" %} - {% else %} - - {% include "templates/generators/item/item_add_to_cart.html" %} - {% endif %} - -
- {% if frappe.utils.strip_html(doc.web_long_description or '') %} - {{ doc.web_long_description | safe }} - {% elif frappe.utils.strip_html(doc.description or '') %} - {{ doc.description | safe }} - {% else %} - {{ "" }} - {% endif %} -
-
- -{% block base_scripts %} - - -{% endblock %} - - \ No newline at end of file diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html deleted file mode 100644 index e1bb3b9865..0000000000 --- a/erpnext/templates/generators/item/item_image.html +++ /dev/null @@ -1,108 +0,0 @@ -{% set column_size = 5 if slides else 4 %} -
- {% if slides %} -
- {% for item in slides %} - {{ item.heading }} - {% endfor %} -
- {{ product_image(slides[0].image, 'product-image') }} - - - {% else %} - {{ product_image(doc.website_image, alt=doc.website_image_alt or doc.item_name) }} - {% endif %} - - - - -
- - diff --git a/erpnext/templates/generators/item/item_inquiry.html b/erpnext/templates/generators/item/item_inquiry.html deleted file mode 100644 index af636f1582..0000000000 --- a/erpnext/templates/generators/item/item_inquiry.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if shopping_cart and shopping_cart.cart_settings.enabled %} -{% set cart_settings = shopping_cart.cart_settings %} - {% if cart_settings.show_contact_us_button | int %} - - {% endif %} - -{% endif %} diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js deleted file mode 100644 index 0aee996672..0000000000 --- a/erpnext/templates/generators/item/item_inquiry.js +++ /dev/null @@ -1,77 +0,0 @@ -frappe.ready(() => { - const d = new frappe.ui.Dialog({ - title: __('Contact Us'), - fields: [ - { - fieldtype: 'Data', - label: __('Full Name'), - fieldname: 'lead_name', - reqd: 1 - }, - { - fieldtype: 'Data', - label: __('Organization Name'), - fieldname: 'company_name', - }, - { - fieldtype: 'Data', - label: __('Email'), - fieldname: 'email_id', - options: 'Email', - reqd: 1 - }, - { - fieldtype: 'Data', - label: __('Phone Number'), - fieldname: 'phone', - options: 'Phone', - reqd: 1 - }, - { - fieldtype: 'Data', - label: __('Subject'), - fieldname: 'subject', - reqd: 1 - }, - { - fieldtype: 'Text', - label: __('Message'), - fieldname: 'message', - reqd: 1 - } - ], - primary_action: send_inquiry, - primary_action_label: __('Send') - }); - - function send_inquiry() { - const values = d.get_values(); - const doc = Object.assign({}, values); - delete doc.subject; - delete doc.message; - - d.hide(); - - frappe.call('erpnext.e_commerce.shopping_cart.cart.create_lead_for_item_inquiry', { - lead: doc, - subject: values.subject, - message: values.message - }).then(r => { - if (r.message) { - d.clear(); - } - }); - } - - $('.btn-inquiry').click((e) => { - const $btn = $(e.target); - const item_code = $btn.data('item-code'); - d.set_value('subject', 'Inquiry about ' + item_code); - if (!['Administrator', 'Guest'].includes(frappe.session.user)) { - d.set_value('email_id', frappe.session.user); - d.set_value('lead_name', frappe.get_cookie('full_name')); - } - - d.show(); - }); -}); diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html deleted file mode 100644 index c62c6f7749..0000000000 --- a/erpnext/templates/generators/item/item_reviews.html +++ /dev/null @@ -1,88 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %} - -
- -
-
- {{ _("Customer Reviews") }} -
- -
- - {% if frappe.session.user != "Guest" and user_is_customer %} - - {% endif %} -
-
- - - {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }} - - - -
- {% if reviews %} - {{ user_review(reviews) }} - - {% if total_reviews > 4 %} - - {% endif %} - - {% else %} -
- {{ _("No Reviews") }} -
- {% endif %} -
-
- - diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html deleted file mode 100644 index 0814d81c8a..0000000000 --- a/erpnext/templates/generators/item/item_specifications.html +++ /dev/null @@ -1,20 +0,0 @@ - -{% if website_specifications %} -
-
- {% if not show_tabs %} -
- Product Details -
- {% endif %} - - {% for d in website_specifications -%} - - - - - {%- endfor %} -
{{ d.label }}{{ d.description }}
-
-
-{% endif %} diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html deleted file mode 100644 index 956c3c51e6..0000000000 --- a/erpnext/templates/generators/item_group.html +++ /dev/null @@ -1,72 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import field_filter_section, attribute_filter_section, discount_range_filters %} -{% extends "templates/web.html" %} - -{% block header %} -
{{ _(item_group_name) }}
-{% endblock header %} - -{% block script %} - -{% endblock %} - -{% block breadcrumbs %} -
- {% include "templates/includes/breadcrumbs.html" %} -
-{% endblock %} - -{% block page_content %} -
-
- {% if slideshow %} - {{ web_block( - "Hero Slider", - values=slideshow, - add_container=0, - add_top_padding=0, - add_bottom_padding=0, - ) }} - {% endif %} - - {% if description %} -
{{ description or ""}}
- {% endif %} -
-
-
- -
- -
-
-
-
{{ _('Filters') }}
- {{ _('Clear All') }} -
- - {{ field_filter_section(field_filters) }} - - - {{ attribute_filter_section(attribute_filters) }} - -
- -
-
-
- - -{% endblock %} diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html deleted file mode 100644 index 830ed649f5..0000000000 --- a/erpnext/templates/includes/cart/address_card.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
- {{ _('Change') }} -
-
-
{{ address.title }}
-
- {{ address.display }} -
- - - - - {{ _('Edit') }} - -
-
diff --git a/erpnext/templates/includes/cart/address_picker_card.html b/erpnext/templates/includes/cart/address_picker_card.html deleted file mode 100644 index 646210e65f..0000000000 --- a/erpnext/templates/includes/cart/address_picker_card.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
- -
-
-
{{ address.title }}
-

- {{ address.display }} -

- {{ _('Edit') }} -
-
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html deleted file mode 100644 index a8188ec825..0000000000 --- a/erpnext/templates/includes/cart/cart_address.html +++ /dev/null @@ -1,189 +0,0 @@ -{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %} - -{% if addresses | length == 1%} - {% set select_address = True %} -{% endif %} - -
-
-
{{ _("Shipping Address") }}
- -
- -
- {% for address in shipping_addresses %} - {% if doc.shipping_address_name == address.name %} -
-
- {% include "templates/includes/cart/address_card.html" %} -
-
- {% endif %} - {% endfor %} -
- - -
- -
- -{% if billing_addresses %} -
-
-
{{ _("Billing Address") }}
- -
- -
- {% for address in billing_addresses %} - {% if doc.customer_address == address.name %} -
-
- {% include "templates/includes/cart/address_card.html" %} -
-
- {% endif %} - {% endfor %} -
-{% endif %} - - diff --git a/erpnext/templates/includes/cart/cart_address_picker.html b/erpnext/templates/includes/cart/cart_address_picker.html deleted file mode 100644 index 66a50ecc9f..0000000000 --- a/erpnext/templates/includes/cart/cart_address_picker.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
{{ _("Shipping Address") }}
-
diff --git a/erpnext/templates/includes/cart/cart_dropdown.html b/erpnext/templates/includes/cart/cart_dropdown.html deleted file mode 100644 index 38ad183916..0000000000 --- a/erpnext/templates/includes/cart/cart_dropdown.html +++ /dev/null @@ -1,27 +0,0 @@ -
- - -
-
- {{ _("Item") }} -
-
- {{ _("Price") }} -
-
- - {% if doc.items %} -
-
- {% include "templates/includes/cart/cart_items_dropdown.html" %} -
-
- {% else %} -

{{ _("Cart is Empty") }}

- {% endif %} -
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html deleted file mode 100644 index 428b36e9b3..0000000000 --- a/erpnext/templates/includes/cart/cart_items.html +++ /dev/null @@ -1,113 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import product_image %} - -{% macro item_subtotal(item) %} -
- {{ item.get_formatted('amount') }} -
- - {% if item.is_free_item %} -
- - {{ _('FREE') }} - -
- {% else %} - - {{ _('Rate:') }} {{ item.get_formatted('rate') }} - - {% endif %} -{% endmacro %} - -{% for d in doc.items %} - - -
-
- {% if d.thumbnail %} - {{ product_image(d.thumbnail, alt="d.web_item_name", no_border=True) }} - {% else %} -
- {{ frappe.utils.get_abbr(d.web_item_name) or "NA" }} -
- {% endif %} -
- -
-
- {{ d.get("web_item_name") or d.item_name }} -
-
- {{ d.item_code }} -
- {%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %} - {% if variant_of %} - - {{ _('Variant of') }} - - {{ variant_of }} - - - {% endif %} - -
- -
-
-
- - - - -
- {% set disabled = 'disabled' if d.is_free_item else '' %} -
- - - - - - - - - -
- -
- {% if not d.is_free_item %} -
- - - -
- {% endif %} -
-
- - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} -
- {{ item_subtotal(d) }} -
- {% endif %} - - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - {{ item_subtotal(d) }} - - {% endif %} - -{% endfor %} diff --git a/erpnext/templates/includes/cart/cart_items_dropdown.html b/erpnext/templates/includes/cart/cart_items_dropdown.html deleted file mode 100644 index 5d107fc0d0..0000000000 --- a/erpnext/templates/includes/cart/cart_items_dropdown.html +++ /dev/null @@ -1,12 +0,0 @@ -{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %} - -{% for d in doc.items %} -
-
- {{ item_name_and_description_cart(d) }} -
-
- {{ d.get_formatted("amount") }} -
-
-{% endfor %} diff --git a/erpnext/templates/includes/cart/cart_items_total.html b/erpnext/templates/includes/cart/cart_items_total.html deleted file mode 100644 index c94fde462b..0000000000 --- a/erpnext/templates/includes/cart/cart_items_total.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{ _("Total") }} - - - {{ doc.get_formatted("total") }} - - \ No newline at end of file diff --git a/erpnext/templates/includes/cart/cart_macros.html b/erpnext/templates/includes/cart/cart_macros.html deleted file mode 100644 index fd95dba424..0000000000 --- a/erpnext/templates/includes/cart/cart_macros.html +++ /dev/null @@ -1,22 +0,0 @@ -{% macro show_address(address, doc, fieldname, select_address=False) %} -{% set selected=address.name==doc.get(fieldname) %} - -
-
-
-
- {{ address.name }}
-
-
-
-
-
-
{{ address.display }}
-
-
-{% endmacro %} diff --git a/erpnext/templates/includes/cart/cart_payment_summary.html b/erpnext/templates/includes/cart/cart_payment_summary.html deleted file mode 100644 index b5655a237b..0000000000 --- a/erpnext/templates/includes/cart/cart_payment_summary.html +++ /dev/null @@ -1,84 +0,0 @@ - -{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} -
- {{ _("Payment Summary") }} -
-{% endif %} - -
-
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - - {% set total_items = frappe.utils.cstr(frappe.utils.flt(doc.total_qty, 0)) %} - - - - - - {% for d in doc.taxes %} - {% if d.base_tax_amount %} - - - - - {% endif %} - {% endfor %} -
{{ _("Net Total (") + total_items + _(" Items)") }}{{ doc.get_formatted("net_total") }}
- {{ d.description }} - - {{ d.get_formatted("base_tax_amount") }} -
- - - - - - - - - -
{{ _("Grand Total") }}{{ doc.get_formatted("grand_total") }}
- {% endif %} - - {% if cart_settings.enable_checkout %} - - {% else %} - - {% endif %} -
-
- - - \ No newline at end of file diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html deleted file mode 100644 index d7adae562e..0000000000 --- a/erpnext/templates/includes/navbar/navbar_items.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'frappe/templates/includes/navbar/navbar_items.html' %} - -{% block navbar_right_extension %} - - {% if frappe.db.get_single_value("E Commerce Settings", "enable_wishlist") %} - - {% endif %} -{% endblock %} diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html deleted file mode 100644 index d95b28961c..0000000000 --- a/erpnext/templates/includes/order/order_macros.html +++ /dev/null @@ -1,52 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import product_image %} - -{% macro item_name_and_description(d) %} -
-
-
- {% if d.thumbnail or d.image %} - {{ product_image(d.thumbnail or d.image, no_border=True) }} - {% else %} -
- {{ frappe.utils.get_abbr(d.item_name) or "NA" }} -
- {% endif %} -
-
-
- {{ d.item_code }} -
- {{ html2text(d.description) | truncate(140) }} -
- - {{ _("Qty ") }}({{ d.get_formatted("qty") }}) - -
-
-{% endmacro %} - -{% macro item_name_and_description_cart(d) %} -
-
-
- {{ product_image_square(d.thumbnail or d.image) }} -
-
-
- {{ d.item_name|truncate(25) }} -
- - - - - - - -
-
-
-{% endmacro %} diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js deleted file mode 100644 index a3979d037b..0000000000 --- a/erpnext/templates/includes/product_page.js +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.ready(function() { - window.item_code = $('[itemscope] [itemprop="productID"]').text().trim(); - var qty = 0; - - frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.product_info.get_product_info_for_website", - args: { - item_code: get_item_code() - }, - callback: function(r) { - if(r.message) { - if(r.message.cart_settings.enabled) { - let hide_add_to_cart = !r.message.product_info.price - || (!r.message.product_info.in_stock && !r.message.cart_settings.allow_items_not_in_stock); - $(".item-cart, .item-price, .item-stock").toggleClass('hide', hide_add_to_cart); - } - if(r.message.cart_settings.show_price) { - $(".item-price").toggleClass("hide", false); - } - if(r.message.cart_settings.show_stock_availability) { - $(".item-stock").toggleClass("hide", false); - } - if(r.message.product_info.price) { - $(".item-price") - .html(r.message.product_info.price.formatted_price_sales_uom + "
\ - (" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")
"); - - if(r.message.product_info.in_stock===0) { - $(".item-stock").html("
{{ _("Not in stock") }}
"); - } - else if(r.message.product_info.in_stock===1 && r.message.cart_settings.show_stock_availability) { - var qty_display = "{{ _("In stock") }}"; - if (r.message.product_info.show_stock_qty) { - qty_display += " ("+r.message.product_info.stock_qty+")"; - } - $(".item-stock").html("
\ - "+qty_display+"
"); - } - - if(r.message.product_info.qty) { - qty = r.message.product_info.qty; - toggle_update_cart(r.message.product_info.qty); - } else { - toggle_update_cart(0); - } - } - } - } - }) - - $("#item-add-to-cart button").on("click", function() { - frappe.provide('erpnext.shopping_cart'); - - erpnext.shopping_cart.update_cart({ - item_code: get_item_code(), - qty: $("#item-spinner .cart-qty").val(), - callback: function(r) { - if(!r.exc) { - toggle_update_cart(1); - qty = 1; - } - }, - btn: this, - }); - }); - - $("#item-spinner").on('click', '.number-spinner button', function () { - var btn = $(this), - input = btn.closest('.number-spinner').find('input'), - oldValue = input.val().trim(), - newVal = 0; - - if (btn.attr('data-dir') == 'up') { - newVal = Number.parseInt(oldValue) + 1; - } else if (btn.attr('data-dir') == 'dwn') { - if (Number.parseInt(oldValue) > 1) { - newVal = Number.parseInt(oldValue) - 1; - } - else { - newVal = Number.parseInt(oldValue); - } - } - input.val(newVal); - }); - - $("[itemscope] .item-view-attribute .form-control").on("change", function() { - try { - var item_code = encodeURIComponent(get_item_code()); - - } catch(e) { - // unable to find variant - // then chose the closest available one - - var attribute = $(this).attr("data-attribute"); - var attribute_value = $(this).val(); - var item_code = find_closest_match(attribute, attribute_value); - - if (!item_code) { - frappe.msgprint(__("Cannot find a matching Item. Please select some other value for {0}.", [attribute])) - throw e; - } - } - - if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) { - return; - } - - window.location.href = window.location.pathname + "?variant=" + item_code; - }); - - // change the item image src when alternate images are hovered - $(document.body).on('mouseover', '.item-alternative-image', (e) => { - const $alternative_image = $(e.currentTarget); - const src = $alternative_image.find('img').prop('src'); - $('.item-image img').prop('src', src); - }); -}); - -var toggle_update_cart = function(qty) { - $("#item-add-to-cart").toggle(qty ? false : true); - $("#item-update-cart") - .toggle(qty ? true : false) - .find("input").val(qty); - $("#item-spinner").toggle(qty ? false : true); -} - -function get_item_code() { - var variant_info = window.variant_info; - if(variant_info) { - var attributes = get_selected_attributes(); - var no_of_attributes = Object.keys(attributes).length; - - for(var i in variant_info) { - var variant = variant_info[i]; - - if (variant.attributes.length < no_of_attributes) { - // the case when variant has less attributes than template - continue; - } - - var match = true; - for(var j in variant.attributes) { - if(attributes[variant.attributes[j].attribute] - != variant.attributes[j].attribute_value - ) { - match = false; - break; - } - } - if(match) { - return variant.name; - } - } - throw "Unable to match variant"; - } else { - return window.item_code; - } -} - -function find_closest_match(selected_attribute, selected_attribute_value) { - // find the closest match keeping the selected attribute in focus and get the item code - - var attributes = get_selected_attributes(); - - var previous_match_score = 0; - var previous_no_of_attributes = 0; - var matched; - - var variant_info = window.variant_info; - for(var i in variant_info) { - var variant = variant_info[i]; - var match_score = 0; - var has_selected_attribute = false; - - for(var j in variant.attributes) { - if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) { - match_score = match_score + 1; - - if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) { - has_selected_attribute = true; - } - } - } - - if (has_selected_attribute - && ((match_score > previous_match_score) || (match_score==previous_match_score && previous_no_of_attributes < variant.attributes.length))) { - previous_match_score = match_score; - matched = variant; - previous_no_of_attributes = variant.attributes.length; - - - } - } - - if (matched) { - for (var j in matched.attributes) { - var attr = matched.attributes[j]; - $('[itemscope]') - .find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr)) - .val(attr.attribute_value); - } - - return matched.name; - } -} - -function get_selected_attributes() { - var attributes = {}; - $('[itemscope]').find(".item-view-attribute .form-control").each(function() { - attributes[$(this).attr('data-attribute')] = $(this).val(); - }); - return attributes; -} diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html deleted file mode 100644 index 2b7d9e3523..0000000000 --- a/erpnext/templates/pages/cart.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ _("Shopping Cart") }} {% endblock %} - -{% block header %}

{{ _("Shopping Cart") }}

{% endblock %} - -{% block header_actions %} -{% endblock %} - -{% block page_content %} - -{% from "templates/includes/macros.html" import item_name_and_description %} - -{% if doc.items %} -
-
- -
-
- -
- {{ _('Items') }} -
- - - - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - {% endif %} - - - - - {% include "templates/includes/cart/cart_items.html" %} - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - {% include "templates/includes/cart/cart_items_total.html" %} - - {% endif %} -
{{ _('Item') }}{{ _('Quantity') }}{{ _('Subtotal') }}
- -
-
- {% if cart_settings.enable_checkout %} - - {{ _('Past Orders') }} - - {% else %} - - {{ _('Past Quotes') }} - - {% endif %} -
-
- {% if doc.items %} - - {% endif %} -
-
-
- - - {% if doc.items %} - {% if doc.terms %} -
-
{{ _("Terms and Conditions") }}
-
- {{ doc.terms }} -
-
- {% endif %} -
- - -
-
- - {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %} - {% if show_coupon_code == 1%} -
-
- - - -
-
- {% endif %} - -
- {% include "templates/includes/cart/cart_payment_summary.html" %} -
- - {% include "templates/includes/cart/cart_address.html" %} -
-
- {% endif %} -
-
-{% else %} -
-
- Empty State -
-
{{ _('Your cart is Empty') }}

- {% if cart_settings.enable_checkout %} - - {{ _('See past orders') }} - - {% else %} - - {{ _('See past quotations') }} - - {% endif %} -
-{% endif %} - -{% endblock %} - -{% block base_scripts %} - -{{ include_script("frappe-web.bundle.js") }} -{{ include_script("controls.bundle.js") }} -{{ include_script("dialog.bundle.js") }} -{% endblock %} diff --git a/erpnext/templates/pages/cart.js b/erpnext/templates/pages/cart.js deleted file mode 100644 index fb2d159dcf..0000000000 --- a/erpnext/templates/pages/cart.js +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -// JS exclusive to /cart page -frappe.provide("erpnext.e_commerce.shopping_cart"); -var shopping_cart = erpnext.e_commerce.shopping_cart; - -$.extend(shopping_cart, { - show_error: function(title, text) { - $("#cart-container").html('

' + - title + '

' + text + '

'); - }, - - bind_events: function() { - shopping_cart.bind_address_picker_dialog(); - shopping_cart.bind_place_order(); - shopping_cart.bind_request_quotation(); - shopping_cart.bind_change_qty(); - shopping_cart.bind_remove_cart_item(); - shopping_cart.bind_change_notes(); - shopping_cart.bind_coupon_code(); - }, - - bind_address_picker_dialog: function() { - const d = this.get_update_address_dialog(); - this.parent.find('.btn-change-address').on('click', (e) => { - const type = $(e.currentTarget).parents('.address-container').attr('data-address-type'); - $(d.get_field('address_picker').wrapper).html( - this.get_address_template(type) - ); - d.show(); - }); - }, - - get_update_address_dialog() { - let d = new frappe.ui.Dialog({ - title: "Select Address", - fields: [{ - 'fieldtype': 'HTML', - 'fieldname': 'address_picker', - }], - primary_action_label: __('Set Address'), - primary_action: () => { - const $card = d.$wrapper.find('.address-card.active'); - const address_type = $card.closest('[data-address-type]').attr('data-address-type'); - const address_name = $card.closest('[data-address-name]').attr('data-address-name'); - frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address", - freeze: true, - args: { - address_type, - address_name - }, - callback: function(r) { - d.hide(); - if (!r.exc) { - $(".cart-tax-items").html(r.message.total); - shopping_cart.parent.find( - `.address-container[data-address-type="${address_type}"]` - ).html(r.message.address); - } - } - }); - } - }); - - return d; - }, - - get_address_template(type) { - return { - shipping: `
-
- {% for address in shipping_addresses %} -
- {% include "templates/includes/cart/address_picker_card.html" %} -
- {% endfor %} -
-
`, - billing: `
-
- {% for address in billing_addresses %} -
- {% include "templates/includes/cart/address_picker_card.html" %} -
- {% endfor %} -
-
`, - }[type]; - }, - - bind_place_order: function() { - $(".btn-place-order").on("click", function() { - shopping_cart.place_order(this); - }); - }, - - bind_request_quotation: function() { - $('.btn-request-for-quotation').on('click', function() { - shopping_cart.request_quotation(this); - }); - }, - - bind_change_qty: function() { - // bind update button - $(".cart-items").on("change", ".cart-qty", function() { - var item_code = $(this).attr("data-item-code"); - var newVal = $(this).val(); - shopping_cart.shopping_cart_update({item_code, qty: newVal}); - }); - - $(".cart-items").on('click', '.number-spinner button', function () { - var btn = $(this), - input = btn.closest('.number-spinner').find('input'), - oldValue = input.val().trim(), - newVal = 0; - - if (btn.attr('data-dir') == 'up') { - newVal = parseInt(oldValue) + 1; - } else { - if (oldValue > 1) { - newVal = parseInt(oldValue) - 1; - } - } - input.val(newVal); - - let notes = input.closest("td").siblings().find(".notes").text().trim(); - var item_code = input.attr("data-item-code"); - shopping_cart.shopping_cart_update({ - item_code, - qty: newVal, - additional_notes: notes - }); - }); - }, - - bind_change_notes: function() { - $('.cart-items').on('change', 'textarea', function() { - const $textarea = $(this); - const item_code = $textarea.attr('data-item-code'); - const qty = $textarea.closest('tr').find('.cart-qty').val(); - const notes = $textarea.val(); - shopping_cart.shopping_cart_update({ - item_code, - qty, - additional_notes: notes - }); - }); - }, - - bind_remove_cart_item: function() { - $(".cart-items").on("click", ".remove-cart-item", (e) => { - const $remove_cart_item_btn = $(e.currentTarget); - var item_code = $remove_cart_item_btn.data("item-code"); - - shopping_cart.shopping_cart_update({ - item_code: item_code, - qty: 0 - }); - }); - }, - - render_tax_row: function($cart_taxes, doc, shipping_rules) { - var shipping_selector; - if(shipping_rules) { - shipping_selector = ''; - } - - var $tax_row = $(repl('
\ -
\ -
\ -
' + - (shipping_selector || '

%(description)s

') + - '
\ -
\ -
\ -
\ - %(formatted_tax_amount)s

\ -
\ -
', doc)).appendTo($cart_taxes); - - if(shipping_selector) { - $tax_row.find('select option').each(function(i, opt) { - if($(opt).html() == doc.description) { - $(opt).attr("selected", "selected"); - } - }); - $tax_row.find('select').on("change", function() { - shopping_cart.apply_shipping_rule($(this).val(), this); - }); - } - }, - - apply_shipping_rule: function(rule, btn) { - return frappe.call({ - btn: btn, - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.apply_shipping_rule", - args: { shipping_rule: rule }, - callback: function(r) { - if(!r.exc) { - shopping_cart.render(r.message); - } - } - }); - }, - - place_order: function(btn) { - shopping_cart.freeze(); - - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.place_order", - btn: btn, - callback: function(r) { - if(r.exc) { - shopping_cart.unfreeze(); - var msg = ""; - if(r._server_messages) { - msg = JSON.parse(r._server_messages || []).join("
"); - } - - $("#cart-error") - .empty() - .html(msg || frappe._("Something went wrong!")) - .toggle(true); - } else { - $(btn).hide(); - window.location.href = '/orders/' + encodeURIComponent(r.message); - } - } - }); - }, - - request_quotation: function(btn) { - shopping_cart.freeze(); - - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.request_for_quotation", - btn: btn, - callback: function(r) { - if(r.exc) { - shopping_cart.unfreeze(); - var msg = ""; - if(r._server_messages) { - msg = JSON.parse(r._server_messages || []).join("
"); - } - - $("#cart-error") - .empty() - .html(msg || frappe._("Something went wrong!")) - .toggle(true); - } else { - $(btn).hide(); - window.location.href = '/quotations/' + encodeURIComponent(r.message); - } - } - }); - }, - - bind_coupon_code: function() { - $(".bt-coupon").on("click", function() { - shopping_cart.apply_coupon_code(this); - }); - }, - - apply_coupon_code: function(btn) { - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.apply_coupon_code", - btn: btn, - args : { - applied_code : $('.txtcoupon').val(), - applied_referral_sales_partner: $('.txtreferral_sales_partner').val() - }, - callback: function(r) { - if (r && r.message){ - location.reload(); - } - } - }); - } -}); - -frappe.ready(function() { - if (window.location.pathname === "/cart") { - $(".cart-icon").hide(); - } - shopping_cart.parent = $(".cart-container"); - shopping_cart.bind_events(); -}); - -function show_terms() { - var html = $(".cart-terms").html(); - frappe.msgprint(html); -} diff --git a/erpnext/templates/pages/cart.py b/erpnext/templates/pages/cart.py deleted file mode 100644 index cadb46f265..0000000000 --- a/erpnext/templates/pages/cart.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -no_cache = 1 - -from erpnext.e_commerce.shopping_cart.cart import get_cart_quotation - - -def get_context(context): - context.body_class = "product-page" - context.update(get_cart_quotation()) diff --git a/erpnext/templates/pages/customer_reviews.html b/erpnext/templates/pages/customer_reviews.html deleted file mode 100644 index 121bec378c..0000000000 --- a/erpnext/templates/pages/customer_reviews.html +++ /dev/null @@ -1,67 +0,0 @@ -{% extends "templates/web.html" %} -{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %} - -{% block title %} {{ _("Customer Reviews") }} {% endblock %} - -{% block page_content %} -
- {% if enable_reviews %} - -
-
- {{ _("Customer Reviews") }} -
- -
- - {% if frappe.session.user != "Guest" and user_is_customer %} - - {% endif %} -
-
- - - {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }} - - - -
- {% if reviews %} - {{ user_review(reviews) }} - - {% if not reviews | len >= total_reviews %} - - {% endif %} - - {% else %} -
- {{ _("No Reviews") }} -
- {% endif %} -
- {% else %} - -
-

- {{ _("No Reviews") }} -

-
- {% endif %} -
- -{% endblock %} - -{% block base_scripts %} - - - - - - -{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/customer_reviews.py b/erpnext/templates/pages/customer_reviews.py deleted file mode 100644 index c1f0c93f1a..0000000000 --- a/erpnext/templates/pages/customer_reviews.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews -from erpnext.e_commerce.doctype.website_item.website_item import check_if_user_is_customer - - -def get_context(context): - context.body_class = "product-page" - context.no_cache = 1 - context.full_page = True - context.reviews = None - - if frappe.form_dict and frappe.form_dict.get("web_item"): - context.web_item = frappe.form_dict.get("web_item") - context.user_is_customer = check_if_user_is_customer() - context.enable_reviews = get_shopping_cart_settings().enable_reviews - - if context.enable_reviews: - reviews_data = get_item_reviews(context.web_item) - context.update(reviews_data) diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html index 27d966ad42..08e0432dcf 100644 --- a/erpnext/templates/pages/home.html +++ b/erpnext/templates/pages/home.html @@ -17,9 +17,6 @@

{{ homepage.description }}

- {% elif homepage.hero_section_based_on == 'Slideshow' and slideshow %}
@@ -29,26 +26,6 @@ {{ render_homepage_section(homepage.hero_section_doc) }} {% endif %} - {% if homepage.products %} -
-

{{ _('Products') }}

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

{{ _('Publications') }}

diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py index 47fb89dea3..751a5b0b50 100644 --- a/erpnext/templates/pages/home.py +++ b/erpnext/templates/pages/home.py @@ -10,11 +10,6 @@ no_cache = 1 def get_context(context): homepage = frappe.get_cached_doc("Homepage") - for item in homepage.products: - route = frappe.db.get_value("Website Item", {"item_code": item.item_code}, "route") - if route: - item.route = "/" + route - homepage.title = homepage.title or homepage.company context.title = homepage.title context.homepage = homepage @@ -52,5 +47,3 @@ def get_context(context): context.metatags = context.metatags or frappe._dict({}) context.metatags.image = homepage.hero_image or None context.metatags.description = homepage.description or None - - context.explore_link = "/all-products" diff --git a/erpnext/templates/pages/order.js b/erpnext/templates/pages/order.js deleted file mode 100644 index 0574cdedc0..0000000000 --- a/erpnext/templates/pages/order.js +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ready(function(){ - - var loyalty_points_input = document.getElementById("loyalty-point-to-redeem"); - var loyalty_points_status = document.getElementById("loyalty-points-status"); - if (loyalty_points_input) { - loyalty_points_input.onblur = apply_loyalty_points; - } - - function apply_loyalty_points() { - var loyalty_points = parseInt(loyalty_points_input.value); - if (loyalty_points) { - frappe.call({ - method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", - args: { - "customer": doc_info.customer - }, - callback: function(r) { - if (r) { - var message = "" - let loyalty_amount = flt(r.message*loyalty_points); - if (doc_info.grand_total && doc_info.grand_total < loyalty_amount) { - let redeemable_amount = parseInt(doc_info.grand_total/r.message); - message = "You can only redeem max " + redeemable_amount + " points in this order."; - frappe.msgprint(__(message)); - } else { - message = loyalty_points + " Loyalty Points of amount "+ loyalty_amount + " is applied." - frappe.msgprint(__(message)); - var remaining_amount = flt(doc_info.grand_total) - flt(loyalty_amount); - var payment_button = document.getElementById("pay-for-order"); - payment_button.innerHTML = __("Pay Remaining"); - payment_button.href = "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn="+doc_info.doctype_name+"&dt="+doc_info.doctype+"&loyalty_points="+loyalty_points+"&submit_doc=1&order_type=Shopping Cart"; - } - loyalty_points_status.innerHTML = message; - } - } - }); - } - } -}) diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index 13772d3129..d0968bf88a 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -4,8 +4,6 @@ import frappe from frappe import _ -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import show_attachments - def get_context(context): context.no_cache = 1 @@ -14,17 +12,12 @@ def get_context(context): if hasattr(context.doc, "set_indicator"): context.doc.set_indicator() - if show_attachments(): - context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name) - context.parents = frappe.form_dict.parents context.title = frappe.form_dict.name context.payment_ref = frappe.db.get_value( "Payment Request", {"reference_name": frappe.form_dict.name}, "name" ) - context.enabled_checkout = frappe.get_doc("E Commerce Settings").enable_checkout - default_print_format = frappe.db.get_value( "Property Setter", dict(property="default_print_format", doc_type=frappe.form_dict.doctype), diff --git a/erpnext/templates/pages/product_search.html b/erpnext/templates/pages/product_search.html deleted file mode 100644 index 6a5425bbf8..0000000000 --- a/erpnext/templates/pages/product_search.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ _("Product Search") }} {% endblock %} - -{% block header %}

{{ _("Product Search") }}

{% endblock %} - -{% block page_content %} - - - - -
-

{{ _("Search Results") }}

-
- -
-
- -
-
-{% endblock %} diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py deleted file mode 100644 index f40fd479f4..0000000000 --- a/erpnext/templates/pages/product_search.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import json - -import frappe -from frappe.utils import cint, cstr -from redis.commands.search.query import Query - -from erpnext.e_commerce.redisearch_utils import ( - WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, - WEBSITE_ITEM_INDEX, - WEBSITE_ITEM_NAME_AUTOCOMPLETE, - is_redisearch_enabled, -) -from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website -from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html - -no_cache = 1 - - -def get_context(context): - context.show_search = True - - -@frappe.whitelist(allow_guest=True) -def get_product_list(search=None, start=0, limit=12): - data = get_product_data(search, start, limit) - - for item in data: - set_product_info_for_website(item) - - return [get_item_for_list_in_html(r) for r in data] - - -def get_product_data(search=None, start=0, limit=12): - # limit = 12 because we show 12 items in the grid view - # base query - query = """ - SELECT - web_item_name, item_name, item_code, brand, route, - website_image, thumbnail, item_group, - description, web_long_description as website_description, - website_warehouse, ranking - FROM `tabWebsite Item` - WHERE published = 1 - """ - - # search term condition - if search: - query += """ and (item_name like %(search)s - or web_item_name like %(search)s - or brand like %(search)s - or web_long_description like %(search)s)""" - search = "%" + cstr(search) + "%" - - # order by - query += """ ORDER BY ranking desc, modified desc limit %s offset %s""" % ( - cint(limit), - cint(start), - ) - - return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep - - -@frappe.whitelist(allow_guest=True) -def search(query): - product_results = product_search(query) - category_results = get_category_suggestions(query) - - return { - "product_results": product_results.get("results") or [], - "category_results": category_results.get("results") or [], - } - - -@frappe.whitelist(allow_guest=True) -def product_search(query, limit=10, fuzzy_search=True): - search_results = {"from_redisearch": True, "results": []} - - if not is_redisearch_enabled(): - # Redisearch module not enabled - search_results["from_redisearch"] = False - search_results["results"] = get_product_data(query, 0, limit) - return search_results - - if not query: - return search_results - - redis = frappe.cache() - query = clean_up_query(query) - - # TODO: Check perf/correctness with Suggestions & Query vs only Query - # TODO: Use Levenshtein Distance in Query (max=3) - redisearch = redis.ft(WEBSITE_ITEM_INDEX) - suggestions = redisearch.sugget( - WEBSITE_ITEM_NAME_AUTOCOMPLETE, - query, - num=limit, - fuzzy=fuzzy_search and len(query) > 3, - ) - - # Build a query - query_string = query - - for s in suggestions: - query_string += f"|('{clean_up_query(s.string)}')" - - q = Query(query_string) - results = redisearch.search(q) - - search_results["results"] = list(map(convert_to_dict, results.docs)) - search_results["results"] = sorted( - search_results["results"], key=lambda k: frappe.utils.cint(k["ranking"]), reverse=True - ) - - return search_results - - -def clean_up_query(query): - return "".join(c for c in query if c.isalnum() or c.isspace()) - - -def convert_to_dict(redis_search_doc): - return redis_search_doc.__dict__ - - -@frappe.whitelist(allow_guest=True) -def get_category_suggestions(query): - search_results = {"results": []} - - if not is_redisearch_enabled(): - # Redisearch module not enabled, query db - categories = frappe.db.get_all( - "Item Group", - filters={"name": ["like", "%{0}%".format(query)], "show_in_website": 1}, - fields=["name", "route"], - ) - search_results["results"] = categories - return search_results - - if not query: - return search_results - - ac = frappe.cache().ft() - suggestions = ac.sugget(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, query, num=10, with_payloads=True) - - results = [json.loads(s.payload) for s in suggestions] - - search_results["results"] = results - - return search_results diff --git a/erpnext/templates/pages/wishlist.html b/erpnext/templates/pages/wishlist.html deleted file mode 100644 index 7a81dedb49..0000000000 --- a/erpnext/templates/pages/wishlist.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ _("Wishlist") }} {% endblock %} - -{% block header %}

{{ _("Wishlist") }}

{% endblock %} - -{% block page_content %} -{% if items %} -
-
-
- {% from "erpnext/templates/includes/macros.html" import wishlist_card %} - {% for item in items %} - {{ wishlist_card(item, settings) }} - {% endfor %} -
-
-
-{% else %} -
-
- Empty Cart -
-
{{ _('Wishlist is empty!') }}

-
-{% endif %} - -{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py deleted file mode 100644 index 17607e45f9..0000000000 --- a/erpnext/templates/pages/wishlist.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.shopping_cart.cart import _set_price_list -from erpnext.utilities.product import get_price - - -def get_context(context): - is_guest = frappe.session.user == "Guest" - - settings = get_shopping_cart_settings() - items = get_wishlist_items() if not is_guest else [] - selling_price_list = _set_price_list(settings) if not is_guest else None - - items = set_stock_price_details(items, settings, selling_price_list) - - context.body_class = "product-page" - context.items = items - context.settings = settings - context.no_cache = 1 - - -def get_stock_availability(item_code, warehouse): - from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses - - if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: - warehouses = get_child_warehouses(warehouse) - else: - warehouses = [warehouse] if warehouse else [] - - stock_qty = 0.0 - for warehouse in warehouses: - stock_qty += frappe.utils.flt( - frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") - ) - - return bool(stock_qty) - - -def get_wishlist_items(): - if not frappe.db.exists("Wishlist", frappe.session.user): - return [] - - return frappe.db.get_all( - "Wishlist Item", - filters={"parent": frappe.session.user}, - fields=[ - "web_item_name", - "item_code", - "item_name", - "website_item", - "warehouse", - "image", - "item_group", - "route", - ], - ) - - -def set_stock_price_details(items, settings, selling_price_list): - for item in items: - if settings.show_stock_availability: - item.available = get_stock_availability(item.item_code, item.get("warehouse")) - - price_details = get_price( - item.item_code, selling_price_list, settings.default_customer_group, settings.company - ) - - if price_details: - item.formatted_price = price_details.get("formatted_price") - item.formatted_mrp = price_details.get("formatted_mrp") - if item.formatted_mrp: - item.discount = price_details.get("formatted_discount_percent") or price_details.get( - "formatted_discount_rate" - ) - - return items diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index e967f7061b..7897c15e94 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -2,93 +2,12 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.utils import cint, flt, fmt_money, getdate, nowdate +from frappe.utils import cint, flt, fmt_money from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item -from erpnext.stock.doctype.batch.batch import get_batch_qty -from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses -def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None): - in_stock, stock_qty = 0, "" - template_item_code, is_stock_item = frappe.db.get_value( - "Item", item_code, ["variant_of", "is_stock_item"] - ) - - if not warehouse: - warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field) - - if not warehouse and template_item_code and template_item_code != item_code: - warehouse = frappe.db.get_value( - "Website Item", {"item_code": template_item_code}, item_warehouse_field - ) - - if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: - warehouses = get_child_warehouses(warehouse) - else: - warehouses = [warehouse] if warehouse else [] - - total_stock = 0.0 - if warehouses: - for warehouse in warehouses: - stock_qty = frappe.db.sql( - """ - select GREATEST(S.actual_qty - S.reserved_qty - S.reserved_qty_for_production - S.reserved_qty_for_sub_contract, 0) / IFNULL(C.conversion_factor, 1) - from tabBin S - inner join `tabItem` I on S.item_code = I.Item_code - left join `tabUOM Conversion Detail` C on I.sales_uom = C.uom and C.parent = I.Item_code - where S.item_code=%s and S.warehouse=%s""", - (item_code, warehouse), - ) - - if stock_qty: - total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse) - - in_stock = total_stock > 0 and 1 or 0 - - return frappe._dict( - {"in_stock": in_stock, "stock_qty": total_stock, "is_stock_item": is_stock_item} - ) - - -def adjust_qty_for_expired_items(item_code, stock_qty, warehouse): - batches = frappe.get_all("Batch", filters=[{"item": item_code}], fields=["expiry_date", "name"]) - expired_batches = get_expired_batches(batches) - stock_qty = [list(item) for item in stock_qty] - - for batch in expired_batches: - if warehouse: - stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse)) - else: - stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch))) - - if not stock_qty[0][0]: - break - - return stock_qty[0][0] if stock_qty else 0 - - -def get_expired_batches(batches): - """ - :param batches: A list of dict in the form [{'expiry_date': datetime.date(20XX, 1, 1), 'name': 'batch_id'}, ...] - """ - return [b.name for b in batches if b.expiry_date and b.expiry_date <= getdate(nowdate())] - - -def qty_from_all_warehouses(batch_info): - """ - :param batch_info: A list of dict in the form [{u'warehouse': u'Stores - I', u'qty': 0.8}, ...] - """ - qty = 0 - for batch in batch_info: - qty = qty + batch.qty - - return qty - - -def get_price(item_code, price_list, customer_group, company, qty=1): - from erpnext.e_commerce.shopping_cart.cart import get_party - +def get_price(item_code, price_list, customer_group, company, qty=1, party=None): template_item_code = frappe.db.get_value("Item", item_code, "variant_of") if price_list: @@ -106,7 +25,6 @@ def get_price(item_code, price_list, customer_group, company, qty=1): ) if price: - party = get_party() pricing_rule_dict = frappe._dict( { "item_code": item_code, @@ -187,16 +105,62 @@ def get_price(item_code, price_list, customer_group, company, qty=1): return price_obj -def get_non_stock_item_status(item_code, item_warehouse_field): - # if item is a product bundle, check if its bundle items are in stock - if frappe.db.exists("Product Bundle", item_code): - items = frappe.get_doc("Product Bundle", item_code).get_all_children() - bundle_warehouse = frappe.db.get_value( - "Website Item", {"item_code": item_code}, item_warehouse_field +def get_item_codes_by_attributes(attribute_filters, template_item_code=None): + items = [] + + for attribute, values in attribute_filters.items(): + attribute_values = values + + if not isinstance(attribute_values, list): + attribute_values = [attribute_values] + + if not attribute_values: + continue + + wheres = [] + query_values = [] + for attribute_value in attribute_values: + wheres.append("( attribute = %s and attribute_value = %s )") + query_values += [attribute, attribute_value] + + attribute_query = " or ".join(wheres) + + if template_item_code: + variant_of_query = "AND t2.variant_of = %s" + query_values.append(template_item_code) + else: + variant_of_query = "" + + query = """ + SELECT + t1.parent + FROM + `tabItem Variant Attribute` t1 + WHERE + 1 = 1 + AND ( + {attribute_query} + ) + AND EXISTS ( + SELECT + 1 + FROM + `tabItem` t2 + WHERE + t2.name = t1.parent + {variant_of_query} + ) + GROUP BY + t1.parent + ORDER BY + NULL + """.format( + attribute_query=attribute_query, variant_of_query=variant_of_query ) - return all( - get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock - for d in items - ) - else: - return 1 + + item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) + items.append(item_codes) + + res = list(set.intersection(*items)) + + return res diff --git a/erpnext/www/all-products/__init__.py b/erpnext/www/all-products/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html deleted file mode 100644 index 04fc74c08f..0000000000 --- a/erpnext/www/all-products/index.html +++ /dev/null @@ -1,51 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import attribute_filter_section, field_filter_section, discount_range_filters %} -{% extends "templates/web.html" %} - -{% block title %}{{ _('All Products') }}{% endblock %} -{% block header %} -
{{ _('All Products') }}
-{% endblock header %} - -{% block page_content %} -
- -
- -
- - -
-
-
-
{{ _('Filters') }}
- {{ _('Clear All') }} -
- - {% if field_filters %} - {{ field_filter_section(field_filters) }} - {% endif %} - - - {% if attribute_filters %} - {{ attribute_filter_section(attribute_filters) }} - {% endif %} -
- -
-
- - - -{% endblock %} diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js deleted file mode 100644 index 98a8441525..0000000000 --- a/erpnext/www/all-products/index.js +++ /dev/null @@ -1,27 +0,0 @@ -$(() => { - class ProductListing { - constructor() { - let me = this; - let is_item_group_page = $(".item-group-content").data("item-group"); - this.item_group = is_item_group_page || null; - - let view_type = localStorage.getItem("product_view") || "List View"; - - // Render Product Views, Filters & Search - new erpnext.ProductView({ - view_type: view_type, - products_section: $('#product-listing'), - item_group: me.item_group - }); - - this.bind_card_actions(); - } - - bind_card_actions() { - erpnext.e_commerce.shopping_cart.bind_add_to_cart_action(); - erpnext.e_commerce.wishlist.bind_wishlist_action(); - } - } - - new ProductListing(); -}); diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py deleted file mode 100644 index fbf0dce059..0000000000 --- a/erpnext/www/all-products/index.py +++ /dev/null @@ -1,22 +0,0 @@ -import frappe -from frappe.utils import cint - -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder - -sitemap = 1 - - -def get_context(context): - # Add homepage as parent - context.body_class = "product-page" - context.parents = [{"name": frappe._("Home"), "route": "/"}] - - filter_engine = ProductFiltersBuilder() - context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_filters() - - context.page_length = ( - cint(frappe.db.get_single_value("E Commerce Settings", "products_per_page")) or 20 - ) - - context.no_cache = 1 diff --git a/erpnext/www/all-products/not_found.html b/erpnext/www/all-products/not_found.html deleted file mode 100644 index 91989a9ef4..0000000000 --- a/erpnext/www/all-products/not_found.html +++ /dev/null @@ -1 +0,0 @@ -
{{ _('No products found') }}
diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/www/shop-by-category/category_card_section.html b/erpnext/www/shop-by-category/category_card_section.html deleted file mode 100644 index 56cb63a5b6..0000000000 --- a/erpnext/www/shop-by-category/category_card_section.html +++ /dev/null @@ -1,30 +0,0 @@ -{%- macro card(title, image, type, url=None, text_primary=False) -%} - -
- {% if image %} - {{ title }} - {% else %} -
- - {{ frappe.utils.get_abbr(title) }} - -
- {% endif %} -
- {{ title or '' }} -
- -
-{%- endmacro -%} - -
-
- {%- for row in data -%} - {%- set title = row.name -%} - {%- set image = row.get("image") -%} - {%- if title -%} - {{ card(title, image, type, row.get("route")) }} - {%- endif -%} - {%- endfor -%} -
-
\ No newline at end of file diff --git a/erpnext/www/shop-by-category/index.html b/erpnext/www/shop-by-category/index.html deleted file mode 100644 index 04d2d578cb..0000000000 --- a/erpnext/www/shop-by-category/index.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends "templates/web.html" %} -{% block title %}{{ _('Shop by Category') }}{% endblock %} - -{% block head_include %} - -{% endblock %} - -{% block script %} - -{% endblock %} - -{% block page_content %} -
-
- {% if slideshow %} - - {{ web_block( - "Hero Slider", - values=slideshow, - add_container=0, - add_top_padding=0, - add_bottom_padding=0, - ) }} - {% endif %} -
-
- {% if tabs %} - - {{ web_block( - "Section with Tabs", - values=tabs, - add_container=0, - add_top_padding=0, - add_bottom_padding=0 - ) }} - {% endif %} -
-
-{% endblock %} \ No newline at end of file diff --git a/erpnext/www/shop-by-category/index.js b/erpnext/www/shop-by-category/index.js deleted file mode 100644 index 1b3116f5ba..0000000000 --- a/erpnext/www/shop-by-category/index.js +++ /dev/null @@ -1,12 +0,0 @@ -$(() => { - $('.category-card').on('click', (e) => { - let category_type = e.currentTarget.dataset.type; - let category_name = e.currentTarget.dataset.name; - - if (category_type != "item_group") { - let filters = {}; - filters[category_type] = [category_name]; - window.location.href = "/all-products?field_filters=" + JSON.stringify(filters); - } - }); -}); \ No newline at end of file diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py deleted file mode 100644 index 913c1836ac..0000000000 --- a/erpnext/www/shop-by-category/index.py +++ /dev/null @@ -1,91 +0,0 @@ -import frappe -from frappe import _ - -sitemap = 1 - - -def get_context(context): - context.body_class = "product-page" - - settings = frappe.get_cached_doc("E Commerce Settings") - context.categories_enabled = settings.enable_field_filters - - if context.categories_enabled: - categories = [row.fieldname for row in settings.filter_fields] - context.tabs = get_tabs(categories) - - if settings.slideshow: - context.slideshow = get_slideshow(settings.slideshow) - - context.no_cache = 1 - - -def get_slideshow(slideshow): - values = {"show_indicators": 1, "show_controls": 1, "rounded": 1, "slider_name": "Categories"} - slideshow = frappe.get_cached_doc("Website Slideshow", slideshow) - slides = slideshow.get({"doctype": "Website Slideshow Item"}) - for index, slide in enumerate(slides, start=1): - values[f"slide_{index}_image"] = slide.image - values[f"slide_{index}_title"] = slide.heading - values[f"slide_{index}_subtitle"] = slide.description - values[f"slide_{index}_theme"] = slide.get("theme") or "Light" - values[f"slide_{index}_content_align"] = slide.get("content_align") or "Centre" - values[f"slide_{index}_primary_action"] = slide.url - - return values - - -def get_tabs(categories): - tab_values = { - "title": _("Shop by Category"), - } - - categorical_data = get_category_records(categories) - for index, tab in enumerate(categorical_data, start=1): - tab_values[f"tab_{index + 1}_title"] = frappe.unscrub(tab) - # pre-render cards for each tab - tab_values[f"tab_{index + 1}_content"] = frappe.render_template( - "erpnext/www/shop-by-category/category_card_section.html", - {"data": categorical_data[tab], "type": tab}, - ) - return tab_values - - -def get_category_records(categories: list): - categorical_data = {} - website_item_meta = frappe.get_meta("Website Item", cached=True) - - for c in categories: - if c == "item_group": - categorical_data["item_group"] = frappe.db.get_all( - "Item Group", - filters={"parent_item_group": "All Item Groups", "show_in_website": 1}, - fields=["name", "parent_item_group", "is_group", "image", "route"], - ) - - continue - - field_type = website_item_meta.get_field(c).fieldtype - - if field_type == "Table MultiSelect": - child_doc = website_item_meta.get_field(c).options - for field in frappe.get_meta(child_doc, cached=True).fields: - if field.fieldtype == "Link" and field.reqd: - doctype = field.options - else: - doctype = website_item_meta.get_field(c).options - - fields = ["name"] - - try: - meta = frappe.get_meta(doctype, cached=True) - if meta.get_field("image"): - fields += ["image"] - - data = frappe.db.get_all(doctype, fields=fields) - categorical_data[c] = data - except BaseException: - frappe.throw(_("DocType {} not found").format(doctype)) - continue - - return categorical_data From 7f865492d0ba41c9e7d31d8dbecb2411f0574ed9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Oct 2023 17:14:37 +0530 Subject: [PATCH 136/280] chore: pass stock value diff --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index b26a95ba8b..6440f8013f 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -351,7 +351,7 @@ class PurchaseReceipt(BuyingController): ) exchange_rate_map, net_rate_map = get_purchase_document_details(self) - def make_item_asset_inward_entries(item): + def make_item_asset_inward_entries(item, stock_value_diff): if d.is_fixed_asset: stock_asset_account_name = ( get_asset_category_account( @@ -596,7 +596,7 @@ class PurchaseReceipt(BuyingController): ) outgoing_amount = d.base_net_amount + d.item_tax_amount - make_item_asset_inward_entries(d) + make_item_asset_inward_entries(d, stock_value_diff) make_stock_received_but_not_billed_entry(d, outgoing_amount) make_landed_cost_gl_entries(d) make_rate_difference_entry(d) From 601ab4567ea7062b78448235fc2fc62b15dce6a6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 17 Oct 2023 16:50:20 +0530 Subject: [PATCH 137/280] refactor: use account in key while grouping voucher in ar/ap report --- .../report/accounts_receivable/accounts_receivable.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e3b671f397..b9c7a0bfb8 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -116,7 +116,7 @@ class ReceivablePayableReport(object): # build all keys, since we want to exclude vouchers beyond the report date for ple in self.ple_entries: # get the balance object for voucher_type - key = (ple.voucher_type, ple.voucher_no, ple.party) + key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) if not key in self.voucher_balance: self.voucher_balance[key] = frappe._dict( voucher_type=ple.voucher_type, @@ -183,7 +183,7 @@ class ReceivablePayableReport(object): ): return - key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) + key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party) # If payment is made against credit note # and credit note is made against a Sales Invoice @@ -192,13 +192,13 @@ class ReceivablePayableReport(object): if ple.against_voucher_no in self.return_entries: return_against = self.return_entries.get(ple.against_voucher_no) if return_against: - key = (ple.against_voucher_type, return_against, ple.party) + key = (ple.account, ple.against_voucher_type, return_against, ple.party) row = self.voucher_balance.get(key) if not row: # no invoice, this is an invoice / stand-alone payment / credit note - row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party)) + row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party)) row.party_type = ple.party_type return row From e31db1891237aa22c19d71929f69d9db5596ae3c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Oct 2023 18:19:47 +0530 Subject: [PATCH 138/280] chore: Add accounting dimensions to Sales Order Item table --- .../accounting_dimension.py | 27 +++++++++++++++++++ erpnext/hooks.py | 1 + erpnext/patches.txt | 1 + ...counting_dimensions_in_sales_order_item.py | 7 +++++ .../sales_order_item/sales_order_item.json | 11 ++++++-- 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v14_0/create_accounting_dimensions_in_sales_order_item.py diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 3a2c3cbeeb..8f76492d4e 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -302,3 +302,30 @@ def get_dimensions(with_cost_center_and_project=False): default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension return dimension_filters, default_dimensions_map + + +def create_accounting_dimensions_for_doctype(doctype): + accounting_dimensions = frappe.db.get_all( + "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"] + ) + + if not accounting_dimensions: + return + + for d in accounting_dimensions: + field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) + + if field: + continue + + df = { + "fieldname": d.fieldname, + "label": d.label, + "fieldtype": "Link", + "options": d.document_type, + "insert_after": "accounting_dimensions_section", + } + + create_custom_field(doctype, df, ignore_validate=True) + + frappe.clear_cache(doctype=doctype) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2155699a4c..33b82846b7 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -519,6 +519,7 @@ accounting_dimension_doctypes = [ "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", + "Sales Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8f2d076b53..01fdcc2c6e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -345,5 +345,6 @@ erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults erpnext.patches.v14_0.update_invoicing_period_in_subscription execute:frappe.delete_doc("Page", "welcome-to-erpnext") erpnext.patches.v15_0.delete_payment_gateway_doctypes +erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_sales_order_item.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_sales_order_item.py new file mode 100644 index 0000000000..8f77c35b12 --- /dev/null +++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_sales_order_item.py @@ -0,0 +1,7 @@ +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + create_accounting_dimensions_for_doctype, +) + + +def execute(): + create_accounting_dimensions_for_doctype(doctype="Sales Order Item") diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index e6f7456620..f82047f511 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -68,6 +68,7 @@ "total_weight", "column_break_21", "weight_uom", + "accounting_dimensions_section", "warehouse_and_reference", "warehouse", "target_warehouse", @@ -889,12 +890,18 @@ "label": "Production Plan Qty", "no_copy": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-28 14:56:42.031636", + "modified": "2023-10-17 18:18:26.475259", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", @@ -905,4 +912,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file From 7b9cedebf62545669dc2530de9575d06d2c28702 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 17 Oct 2023 19:00:52 +0530 Subject: [PATCH 139/280] fix: Ignore addr permission in internal code --- erpnext/controllers/selling_controller.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 418a56f5fe..c01ac8115a 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -4,7 +4,6 @@ import frappe from frappe import _, bold, throw -from frappe.contacts.doctype.address.address import get_address_display from frappe.utils import cint, flt, get_link_to_form, nowtime from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -593,6 +592,12 @@ class SellingController(StockController): ) def set_customer_address(self): + try: + from frappe.contacts.doctype.address.address import render_address + except ImportError: + # Older frappe versions where this function is not available + from frappe.contacts.doctype.address.address import get_address_display as render_address + address_dict = { "customer_address": "address_display", "shipping_address_name": "shipping_address", @@ -602,7 +607,8 @@ class SellingController(StockController): for address_field, address_display_field in address_dict.items(): if self.get(address_field): - self.set(address_display_field, get_address_display(self.get(address_field))) + address = frappe.call(render_address, self.get(address_field), ignore_permissions=True) + self.set(address_display_field, address) def validate_for_duplicate_items(self): check_list, chk_dupl_itm = [], [] From 8c61fe2ca5cbee253784917ec90c5e01c6c0b8a7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Oct 2023 20:36:09 +0530 Subject: [PATCH 140/280] fix: rearrange functions --- erpnext/controllers/stock_controller.py | 5 - .../purchase_receipt/purchase_receipt.py | 114 +++++++----------- .../templates/pages/integrations/__init__.py | 0 3 files changed, 46 insertions(+), 73 deletions(-) create mode 100644 erpnext/templates/pages/integrations/__init__.py diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ae54b801f1..4b2c922cb7 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -73,11 +73,6 @@ class StockController(AccountsController): gl_entries = self.get_gl_entries(warehouse_account) make_gl_entries(gl_entries, from_repost=from_repost) - elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1: - gl_entries = [] - gl_entries = self.get_asset_gl_entry(gl_entries) - make_gl_entries(gl_entries, from_repost=from_repost) - def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6440f8013f..bd3aeac2e9 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -313,7 +313,6 @@ class PurchaseReceipt(BuyingController): self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) self.make_tax_gl_entries(gl_entries) - # self.get_asset_gl_entry(gl_entries) return process_gl_map(gl_entries) @@ -351,34 +350,7 @@ class PurchaseReceipt(BuyingController): ) exchange_rate_map, net_rate_map = get_purchase_document_details(self) - def make_item_asset_inward_entries(item, stock_value_diff): - if d.is_fixed_asset: - stock_asset_account_name = ( - get_asset_category_account( - asset_category=item.asset_category, - fieldname="capital_work_in_progress_account", - company=self.company, - ) - if is_cwip_accounting_enabled(d.asset_category) - else get_asset_category_account( - asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company - ) - ) - - stock_value_diff = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate) - elif (flt(item.valuation_rate) or self.is_return) and flt(item.qty): - # If PR is sub-contracted and fg item rate is zero - # in that case if account for source and target warehouse are same, - # then GL entries should not be posted - if ( - flt(stock_value_diff) == flt(d.rm_supp_cost) - and warehouse_account.get(self.supplier_warehouse) - and stock_asset_account_name == supplier_warehouse_account - ): - return - - stock_asset_account_name = warehouse_account[item.warehouse]["account"] - + def make_item_asset_inward_entries(item, stock_value_diff, stock_asset_account_name): account_currency = get_account_currency(stock_asset_account_name) self.add_gl_entry( gl_entries=gl_entries, @@ -402,20 +374,7 @@ class PurchaseReceipt(BuyingController): ) if self.is_internal_transfer() and item.valuation_rate: - outgoing_amount = abs( - frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": "Purchase Receipt", - "voucher_no": self.name, - "voucher_detail_no": item.name, - "warehouse": item.from_warehouse, - "is_cancelled": 0, - }, - "stock_value_difference", - ) - ) - credit_amount = outgoing_amount + credit_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse) or 0) if credit_amount: account = ( @@ -583,20 +542,37 @@ class PurchaseReceipt(BuyingController): ) if warehouse_account.get(d.warehouse): - stock_value_diff = frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": "Purchase Receipt", - "voucher_no": self.name, - "voucher_detail_no": d.name, - "warehouse": d.warehouse, - "is_cancelled": 0, - }, - "stock_value_difference", - ) - + stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) outgoing_amount = d.base_net_amount + d.item_tax_amount - make_item_asset_inward_entries(d, stock_value_diff) + + if d.is_fixed_asset: + stock_asset_account_name = ( + get_asset_category_account( + asset_category=d.asset_category, + fieldname="capital_work_in_progress_account", + company=self.company, + ) + if is_cwip_accounting_enabled(d.asset_category) + else get_asset_category_account( + asset_category=d.asset_category, fieldname="fixed_asset_account", company=self.company + ) + ) + + stock_value_diff = flt(d.net_amount) + flt(d.item_tax_amount / self.conversion_rate) + elif (flt(d.valuation_rate) or self.is_return) and flt(d.qty): + stock_asset_account_name = warehouse_account[d.warehouse]["account"] + + # If PR is sub-contracted and fg item rate is zero + # in that case if account for source and target warehouse are same, + # then GL entries should not be posted + if ( + flt(stock_value_diff) == flt(d.rm_supp_cost) + and warehouse_account.get(self.supplier_warehouse) + and stock_asset_account_name == supplier_warehouse_account + ): + continue + + make_item_asset_inward_entries(d, stock_value_diff, stock_asset_account_name) make_stock_received_but_not_billed_entry(d, outgoing_amount) make_landed_cost_gl_entries(d) make_rate_difference_entry(d) @@ -745,18 +721,6 @@ class PurchaseReceipt(BuyingController): i += 1 - def get_asset_gl_entry(self, gl_entries): - for item in self.get("items"): - if item.is_fixed_asset: - if is_cwip_accounting_enabled(item.asset_category): - self.add_asset_gl_entries(item, gl_entries) - if flt(item.landed_cost_voucher_amount): - self.add_lcv_gl_entries(item, gl_entries) - # update assets gross amount by its valuation rate - # valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item - - return gl_entries - def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code} @@ -790,6 +754,20 @@ class PurchaseReceipt(BuyingController): self.load_from_db() +def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse): + frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": voucher_no, + "voucher_detail_no": voucher_detail_no, + "warehouse": warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + + def update_billed_amount_based_on_po(po_details, update_modified=True): po_billed_amt_details = get_billed_amount_against_po(po_details) diff --git a/erpnext/templates/pages/integrations/__init__.py b/erpnext/templates/pages/integrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 244cec64b2641c50bd6102e6dba65a481d24da0d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 17 Oct 2023 20:37:48 +0530 Subject: [PATCH 141/280] test: report output if party is missing --- .../test_accounts_receivable.py | 115 ++++++++++++++---- 1 file changed, 92 insertions(+), 23 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 4307689158..cbeb6d3106 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,6 +1,7 @@ import unittest import frappe +from frappe import qb from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, today @@ -23,29 +24,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): def tearDown(self): frappe.db.rollback() - def create_usd_account(self): - name = "Debtors USD" - exists = frappe.db.get_list( - "Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"} - ) - if exists: - self.debtors_usd = exists[0].name - else: - debtors = frappe.get_doc( - "Account", - frappe.db.get_list( - "Account", filters={"company": "_Test Company 2", "account_name": "Debtors"} - )[0].name, - ) - - debtors_usd = frappe.new_doc("Account") - debtors_usd.company = debtors.company - debtors_usd.account_name = "Debtors USD" - debtors_usd.account_currency = "USD" - debtors_usd.parent_account = debtors.parent_account - debtors_usd.account_type = debtors.account_type - self.debtors_usd = debtors_usd.save().name - def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") si = create_sales_invoice( @@ -643,3 +621,94 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): self.assertEqual(len(report[1]), 2) output_for = set([x.party for x in report[1]]) self.assertEqual(output_for, expected_output) + + def test_report_output_if_party_is_missing(self): + acc_name = "Additional Debtors" + if not frappe.db.get_value( + "Account", filters={"account_name": acc_name, "company": self.company} + ): + additional_receivable_acc = frappe.get_doc( + { + "doctype": "Account", + "account_name": acc_name, + "parent_account": "Accounts Receivable - " + self.company_abbr, + "company": self.company, + "account_type": "Receivable", + } + ).save() + self.debtors2 = additional_receivable_acc.name + + je = frappe.new_doc("Journal Entry") + je.company = self.company + je.posting_date = today() + je.append( + "accounts", + { + "account": self.debit_to, + "party_type": "Customer", + "party": self.customer, + "debit_in_account_currency": 150, + "credit_in_account_currency": 0, + "cost_center": self.cost_center, + }, + ) + je.append( + "accounts", + { + "account": self.debtors2, + "party_type": "Customer", + "party": self.customer, + "debit_in_account_currency": 200, + "credit_in_account_currency": 0, + "cost_center": self.cost_center, + }, + ) + je.append( + "accounts", + { + "account": self.cash, + "debit_in_account_currency": 0, + "credit_in_account_currency": 350, + "cost_center": self.cost_center, + }, + ) + je.save().submit() + + # manually remove party from Payment Ledger + ple = qb.DocType("Payment Ledger Entry") + qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run() + + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + report_ouput = execute(filters)[1] + expected_data = [ + [self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0], + [self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0], + ] + self.assertEqual(len(report_ouput), 2) + # fetch only required fields + report_output = [ + [ + x.party_account, + x.voucher_type, + x.voucher_no, + "Customer", + self.customer, + x.invoiced, + x.paid, + x.credit_note, + x.outstanding, + ] + for x in report_ouput + ] + # use account name to sort + # post sorting output should be [[Additional Debtors, ...], [Debtors, ...]] + report_output = sorted(report_output, key=lambda x: x[0]) + self.assertEqual(expected_data, report_output) From e15546b42f0bba2e859f447484fc420c31bc51d3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Oct 2023 21:28:59 +0530 Subject: [PATCH 142/280] chore: rearrange functions --- .../purchase_receipt/purchase_receipt.py | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index bd3aeac2e9..72808db873 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -344,10 +344,6 @@ class PurchaseReceipt(BuyingController): ) ) - supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") - supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( - "account_currency" - ) exchange_rate_map, net_rate_map = get_purchase_document_details(self) def make_item_asset_inward_entries(item, stock_value_diff, stock_asset_account_name): @@ -365,22 +361,26 @@ class PurchaseReceipt(BuyingController): ) def make_stock_received_but_not_billed_entry(item, outgoing_amount): + account = ( + warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb + ) + account_currency = get_account_currency(account) + # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation credit_amount = ( flt(item.base_net_amount, item.precision("base_net_amount")) - if credit_currency == self.company_currency + if account_currency == self.company_currency else flt(item.net_amount, item.precision("net_amount")) ) if self.is_internal_transfer() and item.valuation_rate: - credit_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse) or 0) + outgoing_amount = abs( + get_stock_value_difference(self.name, item.name, item.from_warehouse) or 0 + ) + credit_amount = outgoing_amount if credit_amount: - account = ( - warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb - ) - self.add_gl_entry( gl_entries=gl_entries, account=account, @@ -390,7 +390,7 @@ class PurchaseReceipt(BuyingController): remarks=remarks, against_account=stock_asset_account_name, debit_in_account_currency=-1 * credit_amount, - account_currency=credit_currency, + account_currency=account_currency, item=item, ) @@ -415,7 +415,7 @@ class PurchaseReceipt(BuyingController): remarks=remarks, against_account=self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, + account_currency=account_currency, item=item, ) @@ -428,7 +428,7 @@ class PurchaseReceipt(BuyingController): remarks=remarks, against_account=self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, + account_currency=account_currency, item=item, ) @@ -519,7 +519,7 @@ class PurchaseReceipt(BuyingController): cost_center = item.cost_center or frappe.get_cached_value( "Company", self.company, "cost_center" ) - + account_currency = get_account_currency(loss_account) self.add_gl_entry( gl_entries=gl_entries, account=loss_account, @@ -528,23 +528,32 @@ class PurchaseReceipt(BuyingController): credit=0.0, remarks=remarks, against_account=stock_asset_account_name, - account_currency=credit_currency, + account_currency=account_currency, project=item.project, item=item, ) for d in self.get("items"): if d.item_code in stock_items or d.is_fixed_asset: - credit_currency = ( - get_account_currency(warehouse_account[d.from_warehouse]["account"]) - if d.from_warehouse - else get_account_currency(stock_asset_rbnb) - ) - if warehouse_account.get(d.warehouse): stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) outgoing_amount = d.base_net_amount + d.item_tax_amount + supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") + supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( + "account_currency" + ) + + # If PR is sub-contracted and fg item rate is zero + # in that case if account for source and target warehouse are same, + # then GL entries should not be posted + if ( + flt(stock_value_diff) == flt(d.rm_supp_cost) + and warehouse_account.get(self.supplier_warehouse) + and stock_asset_account_name == supplier_warehouse_account + ): + continue + if d.is_fixed_asset: stock_asset_account_name = ( get_asset_category_account( @@ -562,16 +571,6 @@ class PurchaseReceipt(BuyingController): elif (flt(d.valuation_rate) or self.is_return) and flt(d.qty): stock_asset_account_name = warehouse_account[d.warehouse]["account"] - # If PR is sub-contracted and fg item rate is zero - # in that case if account for source and target warehouse are same, - # then GL entries should not be posted - if ( - flt(stock_value_diff) == flt(d.rm_supp_cost) - and warehouse_account.get(self.supplier_warehouse) - and stock_asset_account_name == supplier_warehouse_account - ): - continue - make_item_asset_inward_entries(d, stock_value_diff, stock_asset_account_name) make_stock_received_but_not_billed_entry(d, outgoing_amount) make_landed_cost_gl_entries(d) From f4d74990fe1cc2abda56359ce8d09644526c62a6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 17 Oct 2023 22:11:56 +0530 Subject: [PATCH 143/280] fix: E-commerce permissions --- erpnext/accounts/party.py | 32 ++++++++++++++++------- erpnext/controllers/selling_controller.py | 12 +++------ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index b99bb83c5b..365aa7f2a3 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -6,11 +6,7 @@ from typing import Optional import frappe from frappe import _, msgprint, scrub -from frappe.contacts.doctype.address.address import ( - get_address_display, - get_company_address, - get_default_address, -) +from frappe.contacts.doctype.address.address import get_company_address, get_default_address from frappe.contacts.doctype.contact.contact import get_contact_details from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values @@ -133,6 +129,7 @@ def _get_party_details( party_address, company_address, shipping_address, + ignore_permissions=ignore_permissions, ) set_contact_details(party_details, party, party_type) set_other_values(party_details, party, party_type) @@ -193,6 +190,8 @@ def set_address_details( party_address=None, company_address=None, shipping_address=None, + *, + ignore_permissions=False ): billing_address_field = ( "customer_address" if party_type == "Lead" else party_type.lower() + "_address" @@ -205,13 +204,17 @@ def set_address_details( get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]) ) # address display - party_details.address_display = get_address_display(party_details[billing_address_field]) + party_details.address_display = render_address( + party_details[billing_address_field], check_permissions=not ignore_permissions + ) # shipping address if party_type in ["Customer", "Lead"]: party_details.shipping_address_name = shipping_address or get_party_shipping_address( party_type, party.name ) - party_details.shipping_address = get_address_display(party_details["shipping_address_name"]) + party_details.shipping_address = render_address( + party_details["shipping_address_name"], check_permissions=not ignore_permissions + ) if doctype: party_details.update( get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name) @@ -229,7 +232,7 @@ def set_address_details( if shipping_address: party_details.update( shipping_address=shipping_address, - shipping_address_display=get_address_display(shipping_address), + shipping_address_display=render_address(shipping_address), **get_fetch_values(doctype, "shipping_address", shipping_address) ) @@ -238,7 +241,8 @@ def set_address_details( party_details.update( billing_address=party_details.company_address, billing_address_display=( - party_details.company_address_display or get_address_display(party_details.company_address) + party_details.company_address_display + or render_address(party_details.company_address, check_permissions=False) ), **get_fetch_values(doctype, "billing_address", party_details.company_address) ) @@ -995,3 +999,13 @@ def add_party_account(party_type, party, company, account): doc.append("accounts", accounts) doc.save() + + +def render_address(address, check_permissions=True): + try: + from frappe.contacts.doctype.address.address import render_address as _render + except ImportError: + # Older frappe versions where this function is not available + from frappe.contacts.doctype.address.address import get_address_display as _render + + return frappe.call(_render, address, check_permissions=check_permissions) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c01ac8115a..d34fbeb0da 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -6,6 +6,7 @@ import frappe from frappe import _, bold, throw from frappe.utils import cint, flt, get_link_to_form, nowtime +from erpnext.accounts.party import render_address from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.stock_controller import StockController @@ -592,12 +593,6 @@ class SellingController(StockController): ) def set_customer_address(self): - try: - from frappe.contacts.doctype.address.address import render_address - except ImportError: - # Older frappe versions where this function is not available - from frappe.contacts.doctype.address.address import get_address_display as render_address - address_dict = { "customer_address": "address_display", "shipping_address_name": "shipping_address", @@ -607,8 +602,9 @@ class SellingController(StockController): for address_field, address_display_field in address_dict.items(): if self.get(address_field): - address = frappe.call(render_address, self.get(address_field), ignore_permissions=True) - self.set(address_display_field, address) + self.set( + address_display_field, render_address(self.get(address_field), check_permissions=False) + ) def validate_for_duplicate_items(self): check_list, chk_dupl_itm = [], [] From 7f1d916f04e0129136137cd0dc52b142a5ba0f51 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 18 Oct 2023 08:59:28 +0530 Subject: [PATCH 144/280] chore: rearrange functions --- .../purchase_receipt/purchase_receipt.py | 91 +++++++++---------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 72808db873..c2c4d0f539 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -323,7 +323,6 @@ class PurchaseReceipt(BuyingController): is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) stock_asset_rbnb = None - stock_asset_account_name = None remarks = self.get("remarks") or _("Accounting Entry for {0}").format( "Asset" if is_asset_pr else "Stock" ) @@ -360,12 +359,13 @@ class PurchaseReceipt(BuyingController): item=item, ) - def make_stock_received_but_not_billed_entry(item, outgoing_amount): + def make_stock_received_but_not_billed_entry(item): account = ( warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb ) account_currency = get_account_currency(account) + outgoing_amount = item.base_net_amount # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation credit_amount = ( @@ -375,9 +375,7 @@ class PurchaseReceipt(BuyingController): ) if self.is_internal_transfer() and item.valuation_rate: - outgoing_amount = abs( - get_stock_value_difference(self.name, item.name, item.from_warehouse) or 0 - ) + outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse)) credit_amount = outgoing_amount if credit_amount: @@ -496,7 +494,7 @@ class PurchaseReceipt(BuyingController): # divisional loss adjustment expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") valuation_amount_as_per_doc = ( - flt(outgoing_amount, d.precision("base_net_amount")) + flt(item.base_net_amount, d.precision("base_net_amount")) + flt(item.landed_cost_voucher_amount) + flt(item.rm_supp_cost) + flt(item.item_tax_amount) @@ -534,11 +532,37 @@ class PurchaseReceipt(BuyingController): ) for d in self.get("items"): - if d.item_code in stock_items or d.is_fixed_asset: - if warehouse_account.get(d.warehouse): - stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) - outgoing_amount = d.base_net_amount + d.item_tax_amount + if ( + d.item_code not in stock_items + and flt(d.qty) + and provisional_accounting_for_non_stock_items + and d.get("provisional_expense_account") + ): + self.add_provisional_gl_entry( + d, gl_entries, self.posting_date, d.get("provisional_expense_account") + ) + elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return): + if d.is_fixed_asset: + stock_asset_account_name = ( + get_asset_category_account( + asset_category=d.asset_category, + fieldname="capital_work_in_progress_account", + company=self.company, + ) + if is_cwip_accounting_enabled(d.asset_category) + else get_asset_category_account( + asset_category=d.asset_category, fieldname="fixed_asset_account", company=self.company + ) + ) + stock_value_diff = flt(d.net_amount) + flt(d.item_tax_amount / self.conversion_rate) + elif ( + (flt(d.valuation_rate) or self.is_return) + and flt(d.qty) + and warehouse_account.get(d.warehouse) + ): + stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) + stock_asset_account_name = warehouse_account[d.warehouse]["account"] supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( "account_currency" @@ -554,44 +578,17 @@ class PurchaseReceipt(BuyingController): ): continue - if d.is_fixed_asset: - stock_asset_account_name = ( - get_asset_category_account( - asset_category=d.asset_category, - fieldname="capital_work_in_progress_account", - company=self.company, - ) - if is_cwip_accounting_enabled(d.asset_category) - else get_asset_category_account( - asset_category=d.asset_category, fieldname="fixed_asset_account", company=self.company - ) - ) - - stock_value_diff = flt(d.net_amount) + flt(d.item_tax_amount / self.conversion_rate) - elif (flt(d.valuation_rate) or self.is_return) and flt(d.qty): - stock_asset_account_name = warehouse_account[d.warehouse]["account"] - - make_item_asset_inward_entries(d, stock_value_diff, stock_asset_account_name) - make_stock_received_but_not_billed_entry(d, outgoing_amount) - make_landed_cost_gl_entries(d) - make_rate_difference_entry(d) - make_sub_contracting_gl_entries(d) - make_divisional_loss_gl_entry(d) - elif ( - d.warehouse not in warehouse_with_no_account - or d.rejected_warehouse not in warehouse_with_no_account - ): - warehouse_with_no_account.append(d.warehouse) + make_item_asset_inward_entries(d, stock_value_diff, stock_asset_account_name) + make_stock_received_but_not_billed_entry(d) + make_landed_cost_gl_entries(d) + make_rate_difference_entry(d) + make_sub_contracting_gl_entries(d) + make_divisional_loss_gl_entry(d) elif ( - d.item_code not in stock_items - and not d.is_fixed_asset - and flt(d.qty) - and provisional_accounting_for_non_stock_items - and d.get("provisional_expense_account") + d.warehouse not in warehouse_with_no_account + or d.rejected_warehouse not in warehouse_with_no_account ): - self.add_provisional_gl_entry( - d, gl_entries, self.posting_date, d.get("provisional_expense_account") - ) + warehouse_with_no_account.append(d.warehouse) if d.is_fixed_asset: self.update_assets(d, d.valuation_rate) @@ -754,7 +751,7 @@ class PurchaseReceipt(BuyingController): def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse): - frappe.db.get_value( + return frappe.db.get_value( "Stock Ledger Entry", { "voucher_type": "Purchase Receipt", From 1a2f659de2a06bea513ced0a5b8ff007ebec6437 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 18 Oct 2023 11:42:19 +0530 Subject: [PATCH 145/280] fix: filter tax template based on company --- erpnext/accounts/doctype/subscription/subscription.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/subscription.js b/erpnext/accounts/doctype/subscription/subscription.js index ae789b5424..92f8a3a097 100644 --- a/erpnext/accounts/doctype/subscription/subscription.js +++ b/erpnext/accounts/doctype/subscription/subscription.js @@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', { } }; }); + + frm.set_query('sales_tax_template', function () { + return { + filters: { + company: frm.doc.company + } + }; + }); }, refresh: function (frm) { From 36a996d70499a8bc4cf9c28853c2b5606d73f81d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 18 Oct 2023 12:01:22 +0530 Subject: [PATCH 146/280] chore: make `Reserve Stock` checkbox visible in SO --- erpnext/selling/doctype/sales_order/sales_order.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index a74084d21f..01d047cead 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1631,10 +1631,9 @@ { "default": "0", "depends_on": "eval: (doc.docstatus == 0 || doc.reserve_stock)", - "description": "If checked, Stock Reservation Entries will be created on Submit", + "description": "If checked, Stock will be reserved on Submit", "fieldname": "reserve_stock", "fieldtype": "Check", - "hidden": 1, "label": "Reserve Stock", "no_copy": 1, "print_hide": 1, @@ -1645,7 +1644,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-07-24 08:59:11.599875", + "modified": "2023-10-18 12:41:54.813462", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", From 2851a41310a050afee753c8ac396eb808d0d123c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 18 Oct 2023 16:31:35 +0530 Subject: [PATCH 147/280] fix: Issues related to RFQ and Supplier Quotation on Portal (#37565) fix: RFQ and Supplier Quotation for Portal --- erpnext/accounts/party.py | 17 +++- erpnext/templates/includes/rfq.js | 2 + .../templates/includes/rfq/rfq_macros.html | 24 ++++-- erpnext/templates/pages/order.html | 86 ++++++------------- erpnext/templates/pages/rfq.html | 4 +- 5 files changed, 59 insertions(+), 74 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 365aa7f2a3..310e41208f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -7,7 +7,6 @@ from typing import Optional import frappe from frappe import _, msgprint, scrub from frappe.contacts.doctype.address.address import get_company_address, get_default_address -from frappe.contacts.doctype.contact.contact import get_contact_details from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import Abs, Date, Sum @@ -294,7 +293,21 @@ def set_contact_details(party_details, party, party_type): } ) else: - party_details.update(get_contact_details(party_details.contact_person)) + fields = [ + "name as contact_person", + "full_name as contact_display", + "email_id as contact_email", + "mobile_no as contact_mobile", + "phone as contact_phone", + "designation as contact_designation", + "department as contact_department", + ] + + contact_details = frappe.db.get_value( + "Contact", party_details.contact_person, fields, as_dict=True + ) + + party_details.update(contact_details) def set_other_values(party_details, party, party_type): diff --git a/erpnext/templates/includes/rfq.js b/erpnext/templates/includes/rfq.js index 37beb5a584..ed0f1b1ff3 100644 --- a/erpnext/templates/includes/rfq.js +++ b/erpnext/templates/includes/rfq.js @@ -73,6 +73,7 @@ rfq = class rfq { submit_rfq(){ $('.btn-sm').click(function(){ + debugger frappe.freeze(); frappe.call({ type: "POST", @@ -82,6 +83,7 @@ rfq = class rfq { }, btn: this, callback: function(r){ + debugger frappe.unfreeze(); if(r.message){ $('.btn-sm').hide() diff --git a/erpnext/templates/includes/rfq/rfq_macros.html b/erpnext/templates/includes/rfq/rfq_macros.html index 88724c30de..78ec6ff5f8 100644 --- a/erpnext/templates/includes/rfq/rfq_macros.html +++ b/erpnext/templates/includes/rfq/rfq_macros.html @@ -1,19 +1,25 @@ {% from "erpnext/templates/includes/macros.html" import product_image_square, product_image %} {% macro item_name_and_description(d, doc) %} -
-
- {{ product_image(d.image) }} -
-
- {{ d.item_code }} -

{{ d.description }}

+
+
+ {% if d.image %} + {{ product_image(d.image) }} + {% else %} +
+ {{ frappe.utils.get_abbr(d.item_name)}} +
+ {% endif %} +
+
+ {{ d.item_code }} +

{{ d.description }}

{% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %}

{% if supplier_part_no %} {{_("Supplier Part No") + ": "+ supplier_part_no}} {% endif %}

-
-
+
+
{% endmacro %} diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index bc34ad5ac5..97bf48727c 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -1,5 +1,5 @@ {% extends "templates/web.html" %} -{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %} +{% from "erpnext/templates/includes/macros.html" import product_image %} {% block breadcrumbs %} {% include "templates/includes/breadcrumbs.html" %} @@ -34,18 +34,6 @@
- {% if show_pay_button %} - - {% endif %}
{% endblock %} @@ -130,42 +118,6 @@
- {% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0) - or (doc.doctype=="Sales Invoice" and doc.outstanding_amount> 0)) %} -
-
-
-
-
- {% if available_loyalty_points %} -
-
-
- Loyalty Points -
-
-
- -
-
Enter Loyalty Points
-
-
- -
-

Available Points: {{ - available_loyalty_points }}

-
-
- {% endif %} -
-
-
-
-
- {% endif %} - - {% if attachments %}
@@ -193,15 +145,27 @@ {% endif %} {% endblock %} -{% block script %} - - -{% endblock %} \ No newline at end of file +{% macro item_name_and_description(d) %} +
+
+
+ {% if d.thumbnail or d.image %} + {{ product_image(d.thumbnail or d.image, no_border=True) }} + {% else %} +
+ {{ frappe.utils.get_abbr(d.item_name) or "NA" }} +
+ {% endif %} +
+
+
+ {{ d.item_code }} +
+ {{ html2text(d.description) | truncate(140) }} +
+ + {{ _("Qty ") }}({{ d.get_formatted("qty") }}) + +
+
+{% endmacro %} \ No newline at end of file diff --git a/erpnext/templates/pages/rfq.html b/erpnext/templates/pages/rfq.html index 6516482c23..d371bf2161 100644 --- a/erpnext/templates/pages/rfq.html +++ b/erpnext/templates/pages/rfq.html @@ -1,7 +1,7 @@ {% extends "templates/web.html" %} {% block header %} -

{{ doc.name }}

+

{{ doc.name }}

{% endblock %} {% block script %} @@ -16,7 +16,7 @@ {% if doc.items %} + {{ _("Make Quotation") }} {% endif %} {% endblock %} From 10311ff114b7513b47df9be993fa568d6e586f1d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 18 Oct 2023 17:42:35 +0530 Subject: [PATCH 148/280] fix: payment entry count on supplier dashboard (#37571) --- erpnext/buying/doctype/supplier/supplier_dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py index 11bb06e0ca..3bd306e659 100644 --- a/erpnext/buying/doctype/supplier/supplier_dashboard.py +++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py @@ -8,7 +8,7 @@ def get_data(): "This is based on transactions against this Supplier. See timeline below for details" ), "fieldname": "supplier", - "non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"}, + "non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"}, "transactions": [ {"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]}, {"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]}, From d09618bf05ef1c9ef6d1c4fd9605aef19190aa84 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 19 Oct 2023 12:35:36 +0530 Subject: [PATCH 149/280] chore: remove debugger (#37584) --- erpnext/templates/includes/rfq.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/templates/includes/rfq.js b/erpnext/templates/includes/rfq.js index ed0f1b1ff3..37beb5a584 100644 --- a/erpnext/templates/includes/rfq.js +++ b/erpnext/templates/includes/rfq.js @@ -73,7 +73,6 @@ rfq = class rfq { submit_rfq(){ $('.btn-sm').click(function(){ - debugger frappe.freeze(); frappe.call({ type: "POST", @@ -83,7 +82,6 @@ rfq = class rfq { }, btn: this, callback: function(r){ - debugger frappe.unfreeze(); if(r.message){ $('.btn-sm').hide() From 2b4fa98941817966b8a936e4076be84406ad035a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 18 Oct 2023 12:38:42 +0530 Subject: [PATCH 150/280] refactor: rename field `Auto Reserve Stock for Sales Order` --- .../doctype/sales_order/sales_order.js | 12 +++----- .../stock_settings/stock_settings.json | 29 +++++++++---------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index ba8bc339f3..3ad18daf19 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -87,17 +87,13 @@ frappe.ui.form.on("Sales Order", { frm.events.get_items_from_internal_purchase_order(frm); } - if (frm.is_new()) { + if (frm.doc.docstatus === 0) { frappe.db.get_single_value("Stock Settings", "enable_stock_reservation").then((value) => { - if (value) { - frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order").then((value) => { - // If `Reserve Stock on Sales Order Submission` is enabled in Stock Settings, set Reserve Stock to 1 else 0. - frm.set_value("reserve_stock", value ? 1 : 0); - }) - } else { - // If `Stock Reservation` is disabled in Stock Settings, set Reserve Stock to 0 and read only. + if (!value) { + // If `Stock Reservation` is disabled in Stock Settings, set Reserve Stock to 0 and make the field read-only and hidden. frm.set_value("reserve_stock", 0); frm.set_df_property("reserve_stock", "read_only", 1); + frm.set_df_property("reserve_stock", "hidden", 1); } }) } diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 2052daafed..122829032d 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -38,8 +38,8 @@ "stock_reservation_tab", "enable_stock_reservation", "column_break_rx3e", - "auto_reserve_stock_for_sales_order", "allow_partial_reservation", + "auto_reserve_stock_for_sales_order_on_purchase", "serial_and_batch_reservation_section", "auto_reserve_serial_and_batch", "serial_and_batch_item_settings_tab", @@ -65,8 +65,7 @@ "stock_frozen_upto_days", "column_break_26", "role_allowed_to_create_edit_back_dated_transactions", - "stock_auth_role", - "section_break_plhx" + "stock_auth_role" ], "fields": [ { @@ -356,7 +355,7 @@ { "default": "1", "depends_on": "eval: doc.enable_stock_reservation", - "description": "If enabled, Partial Stock Reservation Entries can be created. For example, If you have a Sales Order of 100 units and the Available Stock is 90 units then a Stock Reservation Entry will be created for 90 units. ", + "description": "Partial stock can be reserved. For example, If you have a Sales Order of 100 units and the Available Stock is 90 units then a Stock Reservation Entry will be created for 90 units. ", "fieldname": "allow_partial_reservation", "fieldtype": "Check", "label": "Allow Partial Reservation" @@ -383,7 +382,7 @@ { "default": "1", "depends_on": "eval: doc.enable_stock_reservation", - "description": "If enabled, Serial and Batch Nos will be auto-reserved based on Pick Serial / Batch Based On", + "description": "Serial and Batch Nos will be auto-reserved based on Pick Serial / Batch Based On", "fieldname": "auto_reserve_serial_and_batch", "fieldtype": "Check", "label": "Auto Reserve Serial and Batch Nos" @@ -393,14 +392,6 @@ "fieldtype": "Section Break", "label": "Serial and Batch Reservation" }, - { - "default": "0", - "depends_on": "eval: doc.enable_stock_reservation", - "description": "If enabled, Stock Reservation Entries will be created on submission of Sales Order", - "fieldname": "auto_reserve_stock_for_sales_order", - "fieldtype": "Check", - "label": "Auto Reserve Stock for Sales Order" - }, { "fieldname": "conversion_factor_section", "fieldtype": "Section Break", @@ -421,6 +412,14 @@ "fieldname": "allow_to_edit_stock_uom_qty_for_purchase", "fieldtype": "Check", "label": "Allow to Edit Stock UOM Qty for Purchase Documents" + }, + { + "default": "0", + "depends_on": "eval: doc.enable_stock_reservation", + "description": "Stock will be reserved on submission of Purchase Receipt created against Material Receipt for Sales Order.", + "fieldname": "auto_reserve_stock_for_sales_order_on_purchase", + "fieldtype": "Check", + "label": "Auto Reserve Stock for Sales Order on Purchase" } ], "icon": "icon-cog", @@ -428,7 +427,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-01 14:22:36.136111", + "modified": "2023-10-18 12:35:30.068799", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -453,4 +452,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} +} \ No newline at end of file From 188175be84b5eaa0be73face69f4f2b58b80dd64 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 19 Oct 2023 14:43:29 +0530 Subject: [PATCH 151/280] feat: add fields to hold SO and SO Item ref in PR Item --- .../doctype/purchase_order/purchase_order.py | 2 ++ .../purchase_receipt_item.json | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 465fe96b58..7c40aafbe0 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -556,6 +556,8 @@ def make_purchase_receipt(source_name, target_doc=None): "bom": "bom", "material_request": "material_request", "material_request_item": "material_request_item", + "sales_order": "sales_order", + "sales_order_item": "sales_order_item", }, "postprocess": update_item, "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index d93d21c1f2..f5240a6094 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -125,7 +125,9 @@ "dimension_col_break", "cost_center", "section_break_80", - "page_break" + "page_break", + "sales_order", + "sales_order_item" ], "fields": [ { @@ -1062,12 +1064,32 @@ "fieldtype": "Link", "label": "WIP Composite Asset", "options": "Asset" + }, + { + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "no_copy": 1, + "options": "Sales Order", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "sales_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Sales Order Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "search_index": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-03 21:11:50.547261", + "modified": "2023-10-19 10:50:58.071735", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", @@ -1078,4 +1100,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file From 64497c922892d19ca378b5da75cf6b528f5e52d9 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 19 Oct 2023 14:48:13 +0530 Subject: [PATCH 152/280] feat: reserve stock for SO on PR submission --- .../doctype/sales_order/sales_order.py | 2 +- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- .../purchase_receipt/purchase_receipt.py | 26 +++++++ .../stock_reservation_entry.py | 67 ++++++++++--------- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b91002eb86..d38216242e 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -541,7 +541,7 @@ class SalesOrder(SellingController): create_stock_reservation_entries_for_so_items as create_stock_reservation_entries, ) - create_stock_reservation_entries(so=self, items_details=items_details, notify=notify) + create_stock_reservation_entries(sales_order=self, items_details=items_details, notify=notify) @frappe.whitelist() def cancel_stock_reservation_entries(self, sre_list=None, notify=True) -> None: diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 2fcd1025a0..8c9d03c1bd 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -242,7 +242,7 @@ class PickList(Document): for so, locations in so_details.items(): so_doc = frappe.get_doc("Sales Order", so) create_stock_reservation_entries_for_so_items( - so=so_doc, items_details=locations, against_pick_list=True, notify=notify + sales_order=so_doc, items_details=locations, against_pick_list=True, notify=notify ) @frappe.whitelist() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index de0db1aa8f..fc88dd8d5f 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -264,6 +264,7 @@ class PurchaseReceipt(BuyingController): self.make_gl_entries() self.repost_future_sle_and_gle() self.set_consumed_qty_in_subcontract_order() + self.reserve_stock_for_sales_order() def check_next_docstatus(self): submit_rv = frappe.db.sql( @@ -829,6 +830,31 @@ class PurchaseReceipt(BuyingController): self.load_from_db() + def reserve_stock_for_sales_order(self): + if self.is_return or not cint( + frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order_on_purchase") + ): + return + + self.reload() # reload to get the Serial and Batch Bundle Details + + so_items_details_map = {} + for item in self.items: + if item.sales_order and item.sales_order_item: + item_details = { + "name": item.sales_order_item, + "item_code": item.item_code, + "warehouse": item.warehouse, + "qty_to_reserve": item.stock_qty, + "serial_and_batch_bundle": item.get("serial_and_batch_bundle"), + } + so_items_details_map.setdefault(item.sales_order, []).append(item_details) + + if so_items_details_map: + for so, items_details in so_items_details_map.items(): + so_doc = frappe.get_doc("Sales Order", so) + so_doc.create_stock_reservation_entries(items_details) + def update_billed_amount_based_on_po(po_details, update_modified=True): po_billed_amt_details = get_billed_amount_against_po(po_details) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 936be3f73b..effad7df98 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -761,7 +761,7 @@ def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: st def create_stock_reservation_entries_for_so_items( - so: object, + sales_order: object, items_details: list[dict] = None, against_pick_list: bool = False, notify=True, @@ -771,15 +771,17 @@ def create_stock_reservation_entries_for_so_items( from erpnext.selling.doctype.sales_order.sales_order import get_unreserved_qty if not against_pick_list and ( - so.get("_action") == "submit" - and so.set_warehouse - and cint(frappe.get_cached_value("Warehouse", so.set_warehouse, "is_group")) + sales_order.get("_action") == "submit" + and sales_order.set_warehouse + and cint(frappe.get_cached_value("Warehouse", sales_order.set_warehouse, "is_group")) ): return frappe.msgprint( - _("Stock cannot be reserved in the group warehouse {0}.").format(frappe.bold(so.set_warehouse)) + _("Stock cannot be reserved in the group warehouse {0}.").format( + frappe.bold(sales_order.set_warehouse) + ) ) - validate_stock_reservation_settings(so) + validate_stock_reservation_settings(sales_order) allow_partial_reservation = frappe.db.get_single_value( "Stock Settings", "allow_partial_reservation" @@ -787,29 +789,28 @@ def create_stock_reservation_entries_for_so_items( items = [] if items_details: + item_field = "sales_order_item" if against_pick_list else "name" + for item in items_details: - so_item = frappe.get_doc( - "Sales Order Item", item.get("sales_order_item") if against_pick_list else item.get("name") - ) - so_item.reserve_stock = 1 + so_item = frappe.get_doc("Sales Order Item", item.get(item_field)) so_item.warehouse = item.get("warehouse") so_item.qty_to_reserve = ( item.get("picked_qty") - item.get("stock_reserved_qty", 0) if against_pick_list else (flt(item.get("qty_to_reserve")) * flt(so_item.conversion_factor, 1)) ) + so_item.serial_and_batch_bundle = item.get("serial_and_batch_bundle") if against_pick_list: so_item.pick_list = item.get("parent") so_item.pick_list_item = item.get("name") - so_item.pick_list_sbb = item.get("serial_and_batch_bundle") items.append(so_item) sre_count = 0 - reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", so.name) + reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", sales_order.name) - for item in items if items_details else so.get("items"): + for item in items if items_details else sales_order.get("items"): # Skip if `Reserved Stock` is not checked for the item. if not item.get("reserve_stock"): continue @@ -817,9 +818,9 @@ def create_stock_reservation_entries_for_so_items( # Stock should be reserved from the Pick List if has Picked Qty. if not against_pick_list and flt(item.picked_qty) > 0: frappe.throw( - _( - "Row #{0}: Item {1} has been picked, please create a Stock Reservation from the Pick List." - ).format(item.idx, frappe.bold(item.item_code)) + _("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format( + item.idx, frappe.bold(item.item_code) + ) ) is_stock_item, has_serial_no, has_batch_no = frappe.get_cached_value( @@ -915,33 +916,33 @@ def create_stock_reservation_entries_for_so_items( sre.warehouse = item.warehouse sre.has_serial_no = has_serial_no sre.has_batch_no = has_batch_no - sre.voucher_type = so.doctype - sre.voucher_no = so.name + sre.voucher_type = sales_order.doctype + sre.voucher_no = sales_order.name sre.voucher_detail_no = item.name sre.available_qty = available_qty_to_reserve sre.voucher_qty = item.stock_qty sre.reserved_qty = qty_to_be_reserved - sre.company = so.company + sre.company = sales_order.company sre.stock_uom = item.stock_uom - sre.project = so.project + sre.project = sales_order.project if against_pick_list: sre.against_pick_list = item.pick_list sre.against_pick_list_item = item.pick_list_item - if item.pick_list_sbb: - sbb = frappe.get_doc("Serial and Batch Bundle", item.pick_list_sbb) - sre.reservation_based_on = "Serial and Batch" - for entry in sbb.entries: - sre.append( - "sb_entries", - { - "serial_no": entry.serial_no, - "batch_no": entry.batch_no, - "qty": 1 if has_serial_no else abs(entry.qty), - "warehouse": entry.warehouse, - }, - ) + if item.serial_and_batch_bundle: + sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle) + sre.reservation_based_on = "Serial and Batch" + for entry in sbb.entries: + sre.append( + "sb_entries", + { + "serial_no": entry.serial_no, + "batch_no": entry.batch_no, + "qty": 1 if has_serial_no else abs(entry.qty), + "warehouse": entry.warehouse, + }, + ) sre.save() sre.submit() From 40cdde88202b62464284477bed1589c8d51f0a1c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 19 Oct 2023 15:52:53 +0530 Subject: [PATCH 153/280] ci: seutp v15 config https://github.com/frappe/frappe/issues/22817 --- .github/workflows/patch.yml | 1 + .mergify.yml | 46 ++++--------------------------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 21dd3d4879..3514f0d2d9 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -134,6 +134,7 @@ jobs: } update_to_version 14 + update_to_version 15 echo "Updating to latest version" git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" diff --git a/.mergify.yml b/.mergify.yml index 804b27d435..53596060b1 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -17,6 +17,7 @@ pull_request_rules: - base=version-12 - base=version-14 - base=version-15 + - base=version-16 actions: close: comment: @@ -24,16 +25,6 @@ pull_request_rules: @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch. https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch - - name: Auto-close PRs on pre-release branch - conditions: - - base=version-13-pre-release - actions: - close: - comment: - message: | - @{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches. - - - name: backport to develop conditions: - label="backport develop" @@ -54,13 +45,13 @@ pull_request_rules: assignees: - "{{ author }}" - - name: backport to version-14-pre-release + - name: backport to version-15-hotfix conditions: - - label="backport version-14-pre-release" + - label="backport version-15-hotfix" actions: backport: branches: - - version-14-pre-release + - version-15-hotfix assignees: - "{{ author }}" @@ -74,35 +65,6 @@ pull_request_rules: assignees: - "{{ author }}" - - name: backport to version-13-pre-release - conditions: - - label="backport version-13-pre-release" - actions: - backport: - branches: - - version-13-pre-release - assignees: - - "{{ author }}" - - - name: backport to version-12-hotfix - conditions: - - label="backport version-12-hotfix" - actions: - backport: - branches: - - version-12-hotfix - assignees: - - "{{ author }}" - - - name: backport to version-12-pre-release - conditions: - - label="backport version-12-pre-release" - actions: - backport: - branches: - - version-12-pre-release - assignees: - - "{{ author }}" - name: Automatic merge on CI success and review conditions: From 705dadae8e3b49e751184b14323ba2686e5342cc Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Oct 2023 16:21:15 +0530 Subject: [PATCH 154/280] refactor: avoid relying only on against in tds docs query --- .../tax_withholding_details.py | 66 ++++++++++++------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index f2ec31c70e..e5aa6f52ee 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -70,7 +70,8 @@ def get_result( if net_total_map.get(name): if voucher_type == "Journal Entry": # back calcalute total amount from rate and tax_amount - total_amount = grand_total = base_total = tax_amount / (rate / 100) + if rate: + total_amount = grand_total = base_total = tax_amount / (rate / 100) else: total_amount, grand_total, base_total = net_total_map.get(name) else: @@ -253,27 +254,7 @@ def get_tds_docs(filters): "Tax Withholding Account", {"company": filters.get("company")}, pluck="account" ) - query_filters = { - "account": ("in", tds_accounts), - "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), - "is_cancelled": 0, - "against": ("not in", bank_accounts), - } - - party = frappe.get_all(filters.get("party_type"), pluck="name") - or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"}) - - if filters.get("party"): - del query_filters["account"] - del query_filters["against"] - or_filters = {"against": filters.get("party"), "party": filters.get("party")} - - tds_docs = frappe.get_all( - "GL Entry", - filters=query_filters, - or_filters=or_filters, - fields=["voucher_no", "voucher_type", "against", "party"], - ) + tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True) for d in tds_docs: if d.voucher_type == "Purchase Invoice": @@ -309,6 +290,47 @@ def get_tds_docs(filters): ) +def get_tds_docs_query(filters, bank_accounts, tds_accounts): + if not tds_accounts: + frappe.throw( + _("No {} Accounts found for this company.".format(frappe.bold("Tax Withholding"))), + title="Accounts Missing Error", + ) + gle = frappe.qb.DocType("GL Entry") + query = ( + frappe.qb.from_(gle) + .select("voucher_no", "voucher_type", "against", "party") + .where((gle.is_cancelled == 0)) + ) + + if filters.get("from_date"): + query = query.where(gle.posting_date >= filters.get("from_date")) + if filters.get("to_date"): + query = query.where(gle.posting_date <= filters.get("to_date")) + + if bank_accounts: + query = query.where(gle.against.notin(bank_accounts)) + + if filters.get("party"): + party = [filters.get("party")] + query = query.where( + ((gle.account.isin(tds_accounts) & gle.against.isin(party))) + | ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))) + | gle.party.isin(party) + ) + else: + party = frappe.get_all(filters.get("party_type"), pluck="name") + query = query.where( + ((gle.account.isin(tds_accounts) & gle.against.isin(party))) + | ( + (gle.voucher_type == "Journal Entry") + & ((gle.party_type == filters.get("party_type")) | (gle.party_type == "")) + ) + | gle.party.isin(party) + ) + return query + + def get_journal_entry_party_map(journal_entries): journal_entry_party_map = {} for d in frappe.db.get_all( From 5ae9c2f62b741d64da4ce74b3b251339711e8256 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 19 Oct 2023 16:21:32 +0530 Subject: [PATCH 155/280] feat: add field `From Voucher Type` in SRE --- .../stock_reservation_entry.json | 21 ++++++++++++++----- .../stock_reservation_entry.py | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json index 5c3018f734..f92bf49137 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json @@ -17,6 +17,7 @@ "voucher_no", "voucher_detail_no", "column_break_7dxj", + "from_voucher_type", "against_pick_list", "against_pick_list_item", "section_break_xt4m", @@ -272,10 +273,10 @@ }, { "fieldname": "against_pick_list", - "fieldtype": "Link", - "label": "Against Pick List", + "fieldtype": "Dynamic Link", + "label": "From Voucher No", "no_copy": 1, - "options": "Pick List", + "options": "from_voucher_type", "print_hide": 1, "read_only": 1, "report_hide": 1, @@ -284,7 +285,7 @@ { "fieldname": "against_pick_list_item", "fieldtype": "Data", - "label": "Against Pick List Item", + "label": "From Voucher Detail No", "no_copy": 1, "print_hide": 1, "read_only": 1, @@ -297,6 +298,16 @@ { "fieldname": "column_break_grdt", "fieldtype": "Column Break" + }, + { + "fieldname": "from_voucher_type", + "fieldtype": "Select", + "label": "From Voucher Type", + "no_copy": 1, + "options": "\nPick List\nPurchase Receipt", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "hide_toolbar": 1, @@ -304,7 +315,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-08 17:15:13.317706", + "modified": "2023-10-19 16:09:08.418544", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reservation Entry", diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index effad7df98..80f4c1e505 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -927,6 +927,7 @@ def create_stock_reservation_entries_for_so_items( sre.project = sales_order.project if against_pick_list: + sre.from_voucher_type = "Pick List" sre.against_pick_list = item.pick_list sre.against_pick_list_item = item.pick_list_item From 78fe56741931ad2c253bf9acca2896c2411f4ac6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 19 Oct 2023 16:38:43 +0530 Subject: [PATCH 156/280] refactor: rename field `against_pick_list_item` --- .../stock_reservation_entry.json | 22 +++++++++---------- .../stock_reservation_entry.py | 13 ++++++----- .../test_stock_reservation_entry.py | 3 ++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json index f92bf49137..1a518fa38a 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json @@ -19,7 +19,7 @@ "column_break_7dxj", "from_voucher_type", "against_pick_list", - "against_pick_list_item", + "from_voucher_detail_no", "section_break_xt4m", "stock_uom", "column_break_grdt", @@ -282,15 +282,6 @@ "report_hide": 1, "search_index": 1 }, - { - "fieldname": "against_pick_list_item", - "fieldtype": "Data", - "label": "From Voucher Detail No", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, { "fieldname": "column_break_7dxj", "fieldtype": "Column Break" @@ -308,6 +299,15 @@ "print_hide": 1, "read_only": 1, "report_hide": 1 + }, + { + "fieldname": "from_voucher_detail_no", + "fieldtype": "Data", + "label": "From Voucher Detail No", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "hide_toolbar": 1, @@ -315,7 +315,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-10-19 16:09:08.418544", + "modified": "2023-10-19 16:26:46.598043", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reservation Entry", diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 80f4c1e505..66e246a86a 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -316,21 +316,24 @@ class StockReservationEntry(Document): ) -> None: """Updates total reserved qty in the Pick List.""" - if self.against_pick_list and self.against_pick_list_item: + if ( + self.from_voucher_type == "Pick List" and self.against_pick_list and self.from_voucher_detail_no + ): sre = frappe.qb.DocType("Stock Reservation Entry") reserved_qty = ( frappe.qb.from_(sre) .select(Sum(sre.reserved_qty)) .where( (sre.docstatus == 1) + & (sre.from_voucher_type == "Pick List") & (sre.against_pick_list == self.against_pick_list) - & (sre.against_pick_list_item == self.against_pick_list_item) + & (sre.from_voucher_detail_no == self.from_voucher_detail_no) ) ).run(as_list=True)[0][0] or 0 frappe.db.set_value( "Pick List Item", - self.against_pick_list_item, + self.from_voucher_detail_no, reserved_qty_field, reserved_qty, update_modified=update_modified, @@ -803,7 +806,7 @@ def create_stock_reservation_entries_for_so_items( if against_pick_list: so_item.pick_list = item.get("parent") - so_item.pick_list_item = item.get("name") + so_item.from_voucher_detail_no = item.get("name") items.append(so_item) @@ -929,7 +932,7 @@ def create_stock_reservation_entries_for_so_items( if against_pick_list: sre.from_voucher_type = "Pick List" sre.against_pick_list = item.pick_list - sre.against_pick_list_item = item.pick_list_item + sre.from_voucher_detail_no = item.from_voucher_detail_no if item.serial_and_batch_bundle: sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle) diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 1168a4e1c6..27f43bf668 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -555,8 +555,9 @@ class TestStockReservationEntry(FrappeTestCase): (sre.voucher_type == "Sales Order") & (sre.voucher_no == location.sales_order) & (sre.voucher_detail_no == location.sales_order_item) + & (sre.from_voucher_type == "Pick List") & (sre.against_pick_list == pl.name) - & (sre.against_pick_list_item == location.name) + & (sre.from_voucher_detail_no == location.name) ) ).run(as_dict=True) reserved_sb_details: set[tuple] = { From 7ecc0d5a04b6c8cd8b97256d8c0e4bdb12494d5d Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Oct 2023 16:44:08 +0530 Subject: [PATCH 157/280] chore: change column order --- .../tax_withholding_details.py | 67 +++++++++---------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index e5aa6f52ee..611893bf7e 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -158,14 +158,14 @@ def get_gle_map(documents): def get_columns(filters): pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id" columns = [ - {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60}, { - "label": _(filters.get("party_type")), - "fieldname": "party", - "fieldtype": "Dynamic Link", - "options": "party_type", - "width": 180, + "label": _("Section Code"), + "options": "Tax Withholding Category", + "fieldname": "section_code", + "fieldtype": "Link", + "width": 90, }, + {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60}, ] if filters.naming_series == "Naming Series": @@ -180,51 +180,38 @@ def get_columns(filters): columns.extend( [ - { - "label": _("Date of Transaction"), - "fieldname": "transaction_date", - "fieldtype": "Date", - "width": 100, - }, - { - "label": _("Section Code"), - "options": "Tax Withholding Category", - "fieldname": "section_code", - "fieldtype": "Link", - "width": 90, - }, {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100}, - { - "label": _("Total Amount"), - "fieldname": "total_amount", - "fieldtype": "Float", - "width": 90, - }, { "label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"), "fieldname": "rate", "fieldtype": "Percent", - "width": 90, + "width": 60, }, { - "label": _("Tax Amount"), - "fieldname": "tax_amount", + "label": _("Total Amount"), + "fieldname": "total_amount", "fieldtype": "Float", - "width": 90, - }, - { - "label": _("Grand Total"), - "fieldname": "grand_total", - "fieldtype": "Float", - "width": 90, + "width": 120, }, { "label": _("Base Total"), "fieldname": "base_total", "fieldtype": "Float", - "width": 90, + "width": 120, }, - {"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100}, + { + "label": _("Tax Amount"), + "fieldname": "tax_amount", + "fieldtype": "Float", + "width": 120, + }, + { + "label": _("Grand Total"), + "fieldname": "grand_total", + "fieldtype": "Float", + "width": 120, + }, + {"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130}, { "label": _("Reference No."), "fieldname": "ref_no", @@ -232,6 +219,12 @@ def get_columns(filters): "options": "transaction_type", "width": 180, }, + { + "label": _("Date of Transaction"), + "fieldname": "transaction_date", + "fieldtype": "Date", + "width": 100, + }, ] ) From 4471ad581e1c3d220468f42170a09739a46dcc21 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Oct 2023 16:54:10 +0530 Subject: [PATCH 158/280] fix: sort by section code --- .../report/tax_withholding_details/tax_withholding_details.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 611893bf7e..6f2ec176f0 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -97,7 +97,7 @@ def get_result( row.update( { - "section_code": tax_withholding_category, + "section_code": tax_withholding_category or "", "entity_type": party_map.get(party, {}).get(party_type), "rate": rate, "total_amount": total_amount, @@ -111,6 +111,8 @@ def get_result( ) out.append(row) + out.sort(key=lambda x: x["section_code"]) + return out From ed2457bddfaedc83301a96f9d77b093b36d15a72 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Oct 2023 16:59:49 +0530 Subject: [PATCH 159/280] feat: proprietorship & partnership options in entity type --- erpnext/buying/doctype/supplier/supplier.json | 4 ++-- erpnext/selling/doctype/customer/customer.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index f37db5f115..60dd54c238 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -174,7 +174,7 @@ "fieldname": "supplier_type", "fieldtype": "Select", "label": "Supplier Type", - "options": "Company\nIndividual", + "options": "Company\nIndividual\nProprietorship\nPartnership", "reqd": 1 }, { @@ -485,7 +485,7 @@ "link_fieldname": "party" } ], - "modified": "2023-09-25 12:48:21.869563", + "modified": "2023-10-19 16:55:15.148325", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 40cab9f330..3b97123113 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -134,7 +134,7 @@ "label": "Customer Type", "oldfieldname": "customer_type", "oldfieldtype": "Select", - "options": "Company\nIndividual", + "options": "Company\nIndividual\nProprietorship\nPartnership", "reqd": 1 }, { @@ -584,7 +584,7 @@ "link_fieldname": "party" } ], - "modified": "2023-09-21 12:23:20.706020", + "modified": "2023-10-19 16:56:27.327035", "modified_by": "Administrator", "module": "Selling", "name": "Customer", From 75441017c6629f81104409b892db56bb9c1bf1dd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Oct 2023 17:13:58 +0530 Subject: [PATCH 160/280] chore: linting issues --- .../report/tax_withholding_details/tax_withholding_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 6f2ec176f0..69ca4d9707 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -288,7 +288,7 @@ def get_tds_docs(filters): def get_tds_docs_query(filters, bank_accounts, tds_accounts): if not tds_accounts: frappe.throw( - _("No {} Accounts found for this company.".format(frappe.bold("Tax Withholding"))), + _("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")), title="Accounts Missing Error", ) gle = frappe.qb.DocType("GL Entry") From 961d2d9926a1a9c0396c3292d431d3bad6865e62 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 19 Oct 2023 17:51:34 +0530 Subject: [PATCH 161/280] refactor: rename field `against_pick_list` --- erpnext/stock/doctype/pick_list/pick_list.js | 3 +- erpnext/stock/doctype/pick_list/pick_list.py | 6 +- .../doctype/pick_list/pick_list_dashboard.py | 2 +- .../stock_reservation_entry.js | 2 +- .../stock_reservation_entry.json | 30 ++++---- .../stock_reservation_entry.py | 70 +++++++++++-------- .../test_stock_reservation_entry.py | 2 +- .../report/reserved_stock/reserved_stock.js | 20 +++++- .../report/reserved_stock/reserved_stock.py | 23 +++--- 9 files changed, 96 insertions(+), 62 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index ae05b80727..7cd171ea92 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -265,7 +265,8 @@ frappe.ui.form.on('Pick List', { from_date: moment(frm.doc.creation).format('YYYY-MM-DD'), to_date: to_date, voucher_type: "Sales Order", - against_pick_list: frm.doc.name, + from_voucher_type: "Pick List", + from_voucher_no: frm.doc.name, } frappe.set_route("query-report", "Reserved Stock"); } diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 8c9d03c1bd..3503556f3e 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -242,7 +242,7 @@ class PickList(Document): for so, locations in so_details.items(): so_doc = frappe.get_doc("Sales Order", so) create_stock_reservation_entries_for_so_items( - sales_order=so_doc, items_details=locations, against_pick_list=True, notify=notify + sales_order=so_doc, items_details=locations, from_voucher_type="Pick List", notify=notify ) @frappe.whitelist() @@ -253,7 +253,9 @@ class PickList(Document): cancel_stock_reservation_entries, ) - cancel_stock_reservation_entries(against_pick_list=self.name, notify=notify) + cancel_stock_reservation_entries( + from_voucher_type="Pick List", from_voucher_no=self.name, notify=notify + ) def validate_picked_qty(self, data): over_delivery_receipt_allowance = 100 + flt( diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py index 0830fa2143..29571a5400 100644 --- a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py +++ b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py @@ -2,7 +2,7 @@ def get_data(): return { "fieldname": "pick_list", "non_standard_fieldnames": { - "Stock Reservation Entry": "against_pick_list", + "Stock Reservation Entry": "from_voucher_no", }, "internal_links": { "Sales Order": ["locations", "sales_order"], diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js index c5df319e22..f60a0378b6 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js @@ -92,7 +92,7 @@ frappe.ui.form.on('Stock Reservation Entry', { 'qty', 'read_only', frm.doc.has_serial_no ); - frm.set_df_property('sb_entries', 'allow_on_submit', frm.doc.against_pick_list ? 0 : 1); + frm.set_df_property('sb_entries', 'allow_on_submit', frm.doc.from_voucher_type == "Pick List" ? 0 : 1); }, hide_rate_related_fields(frm) { diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json index 1a518fa38a..76cedd4b1e 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json @@ -18,7 +18,7 @@ "voucher_detail_no", "column_break_7dxj", "from_voucher_type", - "against_pick_list", + "from_voucher_no", "from_voucher_detail_no", "section_break_xt4m", "stock_uom", @@ -159,7 +159,7 @@ "oldfieldname": "actual_qty", "oldfieldtype": "Currency", "print_width": "150px", - "read_only_depends_on": "eval: ((doc.reservation_based_on == \"Serial and Batch\") || (doc.against_pick_list) || (doc.delivered_qty > 0))", + "read_only_depends_on": "eval: ((doc.reservation_based_on == \"Serial and Batch\") || (doc.from_voucher_type == \"Pick List\") || (doc.delivered_qty > 0))", "width": "150px" }, { @@ -269,18 +269,7 @@ "label": "Reservation Based On", "no_copy": 1, "options": "Qty\nSerial and Batch", - "read_only_depends_on": "eval: (doc.delivered_qty > 0 || doc.against_pick_list)" - }, - { - "fieldname": "against_pick_list", - "fieldtype": "Dynamic Link", - "label": "From Voucher No", - "no_copy": 1, - "options": "from_voucher_type", - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "search_index": 1 + "read_only_depends_on": "eval: (doc.delivered_qty > 0 || doc.from_voucher_type == \"Pick List\")" }, { "fieldname": "column_break_7dxj", @@ -308,6 +297,17 @@ "print_hide": 1, "read_only": 1, "report_hide": 1 + }, + { + "fieldname": "from_voucher_no", + "fieldtype": "Dynamic Link", + "label": "From Voucher No", + "no_copy": 1, + "options": "from_voucher_type", + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "search_index": 1 } ], "hide_toolbar": 1, @@ -315,7 +315,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-10-19 16:26:46.598043", + "modified": "2023-10-19 16:41:16.545416", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reservation Entry", diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 66e246a86a..9097e621e6 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -1,6 +1,8 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from typing import Literal + import frappe from frappe import _ from frappe.model.document import Document @@ -113,7 +115,7 @@ class StockReservationEntry(Document): """Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`.""" if ( - not self.against_pick_list + not self.from_voucher_type and (self.get("_action") == "submit") and (self.has_serial_no or self.has_batch_no) and cint(frappe.db.get_single_value("Stock Settings", "auto_reserve_serial_and_batch")) @@ -317,7 +319,7 @@ class StockReservationEntry(Document): """Updates total reserved qty in the Pick List.""" if ( - self.from_voucher_type == "Pick List" and self.against_pick_list and self.from_voucher_detail_no + self.from_voucher_type == "Pick List" and self.from_voucher_no and self.from_voucher_detail_no ): sre = frappe.qb.DocType("Stock Reservation Entry") reserved_qty = ( @@ -326,7 +328,7 @@ class StockReservationEntry(Document): .where( (sre.docstatus == 1) & (sre.from_voucher_type == "Pick List") - & (sre.against_pick_list == self.against_pick_list) + & (sre.from_voucher_no == self.from_voucher_no) & (sre.from_voucher_detail_no == self.from_voucher_detail_no) ) ).run(as_list=True)[0][0] or 0 @@ -368,7 +370,7 @@ class StockReservationEntry(Document): ).format(self.status, self.doctype) frappe.throw(msg) - if self.against_pick_list: + if self.from_voucher_type == "Pick List": msg = _( "Stock Reservation Entry created against a Pick List cannot be updated. If you need to make changes, we recommend canceling the existing entry and creating a new one." ) @@ -766,14 +768,14 @@ def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: st def create_stock_reservation_entries_for_so_items( sales_order: object, items_details: list[dict] = None, - against_pick_list: bool = False, + from_voucher_type: Literal["Pick List", "Purchase Receipt"] = None, notify=True, ) -> None: """Creates Stock Reservation Entries for Sales Order Items.""" from erpnext.selling.doctype.sales_order.sales_order import get_unreserved_qty - if not against_pick_list and ( + if not from_voucher_type and ( sales_order.get("_action") == "submit" and sales_order.set_warehouse and cint(frappe.get_cached_value("Warehouse", sales_order.set_warehouse, "is_group")) @@ -792,20 +794,20 @@ def create_stock_reservation_entries_for_so_items( items = [] if items_details: - item_field = "sales_order_item" if against_pick_list else "name" + item_field = "sales_order_item" if from_voucher_type == "Pick List" else "name" for item in items_details: so_item = frappe.get_doc("Sales Order Item", item.get(item_field)) so_item.warehouse = item.get("warehouse") so_item.qty_to_reserve = ( item.get("picked_qty") - item.get("stock_reserved_qty", 0) - if against_pick_list + if from_voucher_type == "Pick List" else (flt(item.get("qty_to_reserve")) * flt(so_item.conversion_factor, 1)) ) so_item.serial_and_batch_bundle = item.get("serial_and_batch_bundle") - if against_pick_list: - so_item.pick_list = item.get("parent") + if from_voucher_type == "Pick List": + so_item.from_voucher_no = item.get("parent") so_item.from_voucher_detail_no = item.get("name") items.append(so_item) @@ -819,7 +821,7 @@ def create_stock_reservation_entries_for_so_items( continue # Stock should be reserved from the Pick List if has Picked Qty. - if not against_pick_list and flt(item.picked_qty) > 0: + if not from_voucher_type == "Pick List" and flt(item.picked_qty) > 0: frappe.throw( _("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format( item.idx, frappe.bold(item.item_code) @@ -929,9 +931,9 @@ def create_stock_reservation_entries_for_so_items( sre.stock_uom = item.stock_uom sre.project = sales_order.project - if against_pick_list: - sre.from_voucher_type = "Pick List" - sre.against_pick_list = item.pick_list + if from_voucher_type: + sre.from_voucher_type = from_voucher_type + sre.from_voucher_no = item.from_voucher_no sre.from_voucher_detail_no = item.from_voucher_detail_no if item.serial_and_batch_bundle: @@ -961,29 +963,37 @@ def cancel_stock_reservation_entries( voucher_type: str = None, voucher_no: str = None, voucher_detail_no: str = None, - against_pick_list: str = None, + from_voucher_type: Literal["Pick List", "Purchase Receipt"] = None, + from_voucher_no: str = None, + from_voucher_detail_no: str = None, sre_list: list[dict] = None, notify: bool = True, ) -> None: """Cancel Stock Reservation Entries.""" - if not sre_list and against_pick_list: - sre = frappe.qb.DocType("Stock Reservation Entry") - sre_list = ( - frappe.qb.from_(sre) - .select(sre.name) - .where( - (sre.docstatus == 1) - & (sre.against_pick_list == against_pick_list) - & (sre.status.notin(["Delivered", "Cancelled"])) + if not sre_list: + if voucher_type and voucher_no: + sre_list = get_stock_reservation_entries_for_voucher( + voucher_type, voucher_no, voucher_detail_no, fields=["name"] + ) + elif from_voucher_type and from_voucher_no: + sre = frappe.qb.DocType("Stock Reservation Entry") + query = ( + frappe.qb.from_(sre) + .select(sre.name) + .where( + (sre.docstatus == 1) + & (sre.from_voucher_type == from_voucher_type) + & (sre.from_voucher_no == from_voucher_no) + & (sre.status.notin(["Delivered", "Cancelled"])) + ) + .orderby(sre.creation) ) - .orderby(sre.creation) - ).run(as_dict=True) - elif not sre_list and (voucher_type and voucher_no): - sre_list = get_stock_reservation_entries_for_voucher( - voucher_type, voucher_no, voucher_detail_no, fields=["name"] - ) + if from_voucher_detail_no: + query = query.where(sre.from_voucher_detail_no == from_voucher_detail_no) + + sre_list = query.run(as_dict=True) if sre_list: for sre in sre_list: diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 27f43bf668..9ea35ecacb 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -556,7 +556,7 @@ class TestStockReservationEntry(FrappeTestCase): & (sre.voucher_no == location.sales_order) & (sre.voucher_detail_no == location.sales_order_item) & (sre.from_voucher_type == "Pick List") - & (sre.against_pick_list == pl.name) + & (sre.from_voucher_no == pl.name) & (sre.from_voucher_detail_no == location.name) ) ).run(as_dict=True) diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js index 2199f52df0..68727411d5 100644 --- a/erpnext/stock/report/reserved_stock/reserved_stock.js +++ b/erpnext/stock/report/reserved_stock/reserved_stock.js @@ -91,16 +91,30 @@ frappe.query_reports["Reserved Stock"] = { }, }, { - fieldname: "against_pick_list", - label: __("Against Pick List"), + fieldname: "from_voucher_type", + label: __("From Voucher Type"), fieldtype: "Link", - options: "Pick List", + options: "DocType", + get_query: () => ({ + filters: { + name: ["in", ["Pick List", "Purchase Receipt"]], + } + }), + }, + { + fieldname: "from_voucher_no", + label: __("From Voucher No"), + fieldtype: "Dynamic Link", + options: "from_voucher_type", get_query: () => ({ filters: { docstatus: 1, company: frappe.query_report.get_filter_value("company"), }, }), + get_options: function () { + return frappe.query_report.get_filter_value("from_voucher_type"); + }, }, { fieldname: "reservation_based_on", diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.py b/erpnext/stock/report/reserved_stock/reserved_stock.py index d93ee1c88f..21ce203ad6 100644 --- a/erpnext/stock/report/reserved_stock/reserved_stock.py +++ b/erpnext/stock/report/reserved_stock/reserved_stock.py @@ -44,7 +44,8 @@ def get_data(filters): (sre.available_qty - sre.reserved_qty).as_("available_qty"), sre.voucher_type, sre.voucher_no, - sre.against_pick_list, + sre.from_voucher_type, + sre.from_voucher_no, sre.name.as_("stock_reservation_entry"), sre.status, sre.project, @@ -65,7 +66,8 @@ def get_data(filters): "warehouse", "voucher_type", "voucher_no", - "against_pick_list", + "from_voucher_type", + "from_voucher_no", "reservation_based_on", "status", "project", @@ -142,7 +144,6 @@ def get_columns(): "fieldname": "voucher_type", "label": _("Voucher Type"), "fieldtype": "Data", - "options": "Warehouse", "width": 110, }, { @@ -153,11 +154,17 @@ def get_columns(): "width": 120, }, { - "fieldname": "against_pick_list", - "label": _("Against Pick List"), - "fieldtype": "Link", - "options": "Pick List", - "width": 130, + "fieldname": "from_voucher_type", + "label": _("From Voucher Type"), + "fieldtype": "Data", + "width": 110, + }, + { + "fieldname": "from_voucher_no", + "label": _("From Voucher No"), + "fieldtype": "Dynamic Link", + "options": "from_voucher_type", + "width": 120, }, { "fieldname": "stock_reservation_entry", From 6d5ccde864e373b787d781c583b85a26ed2d40e9 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Oct 2023 17:55:24 +0530 Subject: [PATCH 162/280] feat: add cols for supplier inv details --- .../tax_withholding_details.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 69ca4d9707..2ba5ce0210 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -47,6 +47,7 @@ def get_result( out = [] for name, details in gle_map.items(): tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0 + bill_no, bill_date = "", "" tax_withholding_category = tax_category_map.get(name) rate = tax_rate_map.get(tax_withholding_category) @@ -72,6 +73,8 @@ def get_result( # back calcalute total amount from rate and tax_amount if rate: total_amount = grand_total = base_total = tax_amount / (rate / 100) + elif voucher_type == "Purchase Invoice": + total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name) else: total_amount, grand_total, base_total = net_total_map.get(name) else: @@ -107,6 +110,8 @@ def get_result( "transaction_date": posting_date, "transaction_type": voucher_type, "ref_no": name, + "supplier_invoice_no": bill_no, + "supplier_invoice_date": bill_date, } ) out.append(row) @@ -183,6 +188,28 @@ def get_columns(filters): columns.extend( [ {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100}, + ] + ) + if filters.party_type == "Supplier": + columns.extend( + [ + { + "label": _("Supplier Invoice No"), + "fieldname": "supplier_invoice_no", + "fieldtype": "Data", + "width": 120, + }, + { + "label": _("Supplier Invoice Date"), + "fieldname": "supplier_invoice_date", + "fieldtype": "Date", + "width": 120, + }, + ] + ) + + columns.extend( + [ { "label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"), "fieldname": "rate", @@ -352,6 +379,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): "base_tax_withholding_net_total", "grand_total", "base_total", + "bill_no", + "bill_date", ], "Sales Invoice": ["base_net_total", "grand_total", "base_total"], "Payment Entry": [ @@ -370,7 +399,13 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): for entry in entries: tax_category_map.update({entry.name: entry.tax_withholding_category}) if doctype == "Purchase Invoice": - value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total] + value = [ + entry.base_tax_withholding_net_total, + entry.grand_total, + entry.base_total, + entry.bill_no, + entry.bill_date, + ] elif doctype == "Sales Invoice": value = [entry.base_net_total, entry.grand_total, entry.base_total] elif doctype == "Payment Entry": From 45395027d3b5c51ac3ccdbebb1f0d23d5ffd2ec1 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 19 Oct 2023 19:57:50 +0530 Subject: [PATCH 163/280] fix: incorrect serial and batch get reserved --- .../doctype/sales_order/sales_order.py | 15 +++++++++-- erpnext/stock/doctype/pick_list/pick_list.py | 27 ++++++++++++------- .../purchase_receipt/purchase_receipt.py | 10 +++++-- .../stock_reservation_entry.py | 19 +++++++------ 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d38216242e..94f9d6e37c 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -3,6 +3,7 @@ import json +from typing import Literal import frappe import frappe.utils @@ -534,14 +535,24 @@ class SalesOrder(SellingController): return False @frappe.whitelist() - def create_stock_reservation_entries(self, items_details=None, notify=True) -> None: + def create_stock_reservation_entries( + self, + items_details: list[dict] = None, + from_voucher_type: Literal["Pick List", "Purchase Receipt"] = None, + notify=True, + ) -> None: """Creates Stock Reservation Entries for Sales Order Items.""" from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( create_stock_reservation_entries_for_so_items as create_stock_reservation_entries, ) - create_stock_reservation_entries(sales_order=self, items_details=items_details, notify=notify) + create_stock_reservation_entries( + sales_order=self, + items_details=items_details, + from_voucher_type=from_voucher_type, + notify=notify, + ) @frappe.whitelist() def cancel_stock_reservation_entries(self, sre_list=None, notify=True) -> None: diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 3503556f3e..ed20209577 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -229,20 +229,27 @@ class PickList(Document): def create_stock_reservation_entries(self, notify=True) -> None: """Creates Stock Reservation Entries for Sales Order Items against Pick List.""" - from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( - create_stock_reservation_entries_for_so_items, - ) - - so_details = {} + so_items_details_map = {} for location in self.locations: if location.warehouse and location.sales_order and location.sales_order_item: - so_details.setdefault(location.sales_order, []).append(location) + item_details = { + "name": location.sales_order_item, + "item_code": location.item_code, + "warehouse": location.warehouse, + "qty_to_reserve": (flt(location.picked_qty) - flt(location.stock_reserved_qty)), + "from_voucher_no": location.parent, + "from_voucher_detail_no": location.name, + "serial_and_batch_bundle": location.serial_and_batch_bundle, + } + so_items_details_map.setdefault(location.sales_order, []).append(item_details) - if so_details: - for so, locations in so_details.items(): + if so_items_details_map: + for so, items_details in so_items_details_map.items(): so_doc = frappe.get_doc("Sales Order", so) - create_stock_reservation_entries_for_so_items( - sales_order=so_doc, items_details=locations, from_voucher_type="Pick List", notify=notify + so_doc.create_stock_reservation_entries( + items_details=items_details, + from_voucher_type="Pick List", + notify=notify, ) @frappe.whitelist() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index fc88dd8d5f..42d6b02dee 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -846,14 +846,20 @@ class PurchaseReceipt(BuyingController): "item_code": item.item_code, "warehouse": item.warehouse, "qty_to_reserve": item.stock_qty, - "serial_and_batch_bundle": item.get("serial_and_batch_bundle"), + "from_voucher_no": item.parent, + "from_voucher_detail_no": item.name, + "serial_and_batch_bundle": item.serial_and_batch_bundle, } so_items_details_map.setdefault(item.sales_order, []).append(item_details) if so_items_details_map: for so, items_details in so_items_details_map.items(): so_doc = frappe.get_doc("Sales Order", so) - so_doc.create_stock_reservation_entries(items_details) + so_doc.create_stock_reservation_entries( + items_details=items_details, + from_voucher_type="Purchase Receipt", + notify=True, + ) def update_billed_amount_based_on_po(po_details, update_modified=True): diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 9097e621e6..e589628c62 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -794,22 +794,21 @@ def create_stock_reservation_entries_for_so_items( items = [] if items_details: - item_field = "sales_order_item" if from_voucher_type == "Pick List" else "name" - for item in items_details: - so_item = frappe.get_doc("Sales Order Item", item.get(item_field)) + so_item = frappe.get_doc("Sales Order Item", item.get("name")) so_item.warehouse = item.get("warehouse") so_item.qty_to_reserve = ( - item.get("picked_qty") - item.get("stock_reserved_qty", 0) - if from_voucher_type == "Pick List" - else (flt(item.get("qty_to_reserve")) * flt(so_item.conversion_factor, 1)) + flt(item.get("qty_to_reserve")) + if from_voucher_type in ["Pick List", "Purchase Receipt"] + else ( + flt(item.get("qty_to_reserve")) + * (flt(item.get("conversion_factor")) or flt(so_item.conversion_factor) or 1) + ) ) + so_item.from_voucher_no = item.get("from_voucher_no") + so_item.from_voucher_detail_no = item.get("from_voucher_detail_no") so_item.serial_and_batch_bundle = item.get("serial_and_batch_bundle") - if from_voucher_type == "Pick List": - so_item.from_voucher_no = item.get("parent") - so_item.from_voucher_detail_no = item.get("name") - items.append(so_item) sre_count = 0 From 77cc91d06b9ea1e5758546a78bbcbb2ef97f549b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 19 Oct 2023 22:35:55 +0530 Subject: [PATCH 164/280] fix: add regional support to extend purchase gl entries --- .../doctype/purchase_invoice/purchase_invoice.py | 2 +- erpnext/controllers/stock_controller.py | 12 ++++++++++-- .../doctype/purchase_receipt/purchase_receipt.py | 12 ++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2433268627..e8fc445bc9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1066,7 +1066,7 @@ class PurchaseInvoice(BuyingController): "debit_in_account_currency": ( base_asset_amount if cwip_account_currency == self.company_currency else asset_amount ), - "cost_center": self.cost_center, + "cost_center": item.cost_center or self.cost_center, "project": item.project or self.project, }, item=item, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ae54b801f1..9eeffd8ea6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -76,6 +76,7 @@ class StockController(AccountsController): elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1: gl_entries = [] gl_entries = self.get_asset_gl_entry(gl_entries) + update_regional_gl_entries(gl_entries, self) make_gl_entries(gl_entries, from_repost=from_repost) def validate_serialized_batch(self): @@ -855,8 +856,9 @@ class StockController(AccountsController): @frappe.whitelist() def show_accounting_ledger_preview(company, doctype, docname): - filters = {"company": company, "include_dimensions": 1} + filters = frappe._dict(company=company, include_dimensions=1) doc = frappe.get_doc(doctype, docname) + doc.run_method("before_gl_preview") gl_columns, gl_data = get_accounting_ledger_preview(doc, filters) @@ -867,8 +869,9 @@ def show_accounting_ledger_preview(company, doctype, docname): @frappe.whitelist() def show_stock_ledger_preview(company, doctype, docname): - filters = {"company": company} + filters = frappe._dict(company=company) doc = frappe.get_doc(doctype, docname) + doc.run_method("before_sl_preview") sl_columns, sl_data = get_stock_ledger_preview(doc, filters) @@ -1216,3 +1219,8 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entries.append(repost_entry) return repost_entries + + +@erpnext.allow_regional +def update_regional_gl_entries(gl_list, doc): + return diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index de0db1aa8f..662ab6d423 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -314,6 +314,7 @@ class PurchaseReceipt(BuyingController): self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) self.make_tax_gl_entries(gl_entries) self.get_asset_gl_entry(gl_entries) + update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries) @@ -827,8 +828,6 @@ class PurchaseReceipt(BuyingController): pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) update_billing_percentage(pr_doc, update_modified=update_modified) - self.load_from_db() - def update_billed_amount_based_on_po(po_details, update_modified=True): po_billed_amt_details = get_billed_amount_against_po(po_details) @@ -941,9 +940,6 @@ def get_billed_amount_against_po(po_items): def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False): - # Reload as billed amount was set in db directly - pr_doc.load_from_db() - # Update Billing % based on pending accepted qty total_amount, total_billed_amount = 0, 0 item_wise_returned_qty = get_item_wise_returned_qty(pr_doc) @@ -969,7 +965,6 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6) pr_doc.db_set("per_billed", percent_billed) - pr_doc.load_from_db() if update_modified: pr_doc.set_status(update=True) @@ -1255,3 +1250,8 @@ def get_item_account_wise_additional_cost(purchase_document): def on_doctype_update(): frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"]) + + +@erpnext.allow_regional +def update_regional_gl_entries(gl_list, doc): + return From ff7108a3b139b2f019230be681147f1a1c90a681 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 20 Oct 2023 09:33:37 +0530 Subject: [PATCH 165/280] fix: update existing doc if possible --- .../purchase_receipt/purchase_receipt.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 662ab6d423..3734892f17 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -822,14 +822,14 @@ class PurchaseReceipt(BuyingController): po_details.append(d.purchase_order_item) if po_details: - updated_pr += update_billed_amount_based_on_po(po_details, update_modified) + updated_pr += update_billed_amount_based_on_po(po_details, update_modified, self) for pr in set(updated_pr): pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) update_billing_percentage(pr_doc, update_modified=update_modified) -def update_billed_amount_based_on_po(po_details, update_modified=True): +def update_billed_amount_based_on_po(po_details, update_modified=True, pr_doc=None): po_billed_amt_details = get_billed_amount_against_po(po_details) # Get all Purchase Receipt Item rows against the Purchase Order Items @@ -858,13 +858,19 @@ def update_billed_amount_based_on_po(po_details, update_modified=True): po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po if pr_item.billed_amt != billed_amt_agianst_pr: - frappe.db.set_value( - "Purchase Receipt Item", - pr_item.name, - "billed_amt", - billed_amt_agianst_pr, - update_modified=update_modified, - ) + # update existing doc if possible + if pr_doc and pr_item.parent == pr_doc.name: + pr_item = next((item for item in pr_doc.items if item.name == pr_item.name), None) + pr_item.db_set("billed_amt", billed_amt_agianst_pr, update_modified=update_modified) + + else: + frappe.db.set_value( + "Purchase Receipt Item", + pr_item.name, + "billed_amt", + billed_amt_agianst_pr, + update_modified=update_modified, + ) updated_pr.append(pr_item.parent) From 4f363f5bf3da286999966f10d0cca22264f42199 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 20 Oct 2023 11:44:14 +0530 Subject: [PATCH 166/280] fix: partial reservation against SBB --- .../stock_reservation_entry.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index e589628c62..c7a9e16d0e 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -935,20 +935,28 @@ def create_stock_reservation_entries_for_so_items( sre.from_voucher_no = item.from_voucher_no sre.from_voucher_detail_no = item.from_voucher_detail_no - if item.serial_and_batch_bundle: + if item.get("serial_and_batch_bundle"): sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle) sre.reservation_based_on = "Serial and Batch" - for entry in sbb.entries: + + index, picked_qty = 0, 0 + while index < len(sbb.entries) and picked_qty < qty_to_be_reserved: + entry = sbb.entries[index] + qty = 1 if has_serial_no else min(abs(entry.qty), qty_to_be_reserved - picked_qty) + sre.append( "sb_entries", { "serial_no": entry.serial_no, "batch_no": entry.batch_no, - "qty": 1 if has_serial_no else abs(entry.qty), + "qty": qty, "warehouse": entry.warehouse, }, ) + index += 1 + picked_qty += qty + sre.save() sre.submit() From ce7ac29d060833faefee24c7e1eaebacda983a20 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 20 Oct 2023 15:50:40 +0530 Subject: [PATCH 167/280] fix: Correctly extract last message (#37602) frappe.message_log now contains plain dictionary and not JSON strings, so no need to load them. --- .../pos_invoice_merge_log/pos_invoice_merge_log.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index d42b1e4cd1..c161dac33f 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -454,7 +454,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): except Exception as e: frappe.db.rollback() message_log = frappe.message_log.pop() if frappe.message_log else str(e) - error_message = safe_load_json(message_log) + error_message = get_error_message(message_log) if closing_entry: closing_entry.set_status(update=True, status="Failed") @@ -483,7 +483,7 @@ def cancel_merge_logs(merge_logs, closing_entry=None): except Exception as e: frappe.db.rollback() message_log = frappe.message_log.pop() if frappe.message_log else str(e) - error_message = safe_load_json(message_log) + error_message = get_error_message(message_log) if closing_entry: closing_entry.set_status(update=True, status="Submitted") @@ -525,10 +525,8 @@ def check_scheduler_status(): frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) -def safe_load_json(message): +def get_error_message(message) -> str: try: - json_message = json.loads(message).get("message") + return message["message"] except Exception: - json_message = message - - return json_message + return str(message) From 85488cd0dcac2efe02478ced44349a0cadb3b064 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Fri, 20 Oct 2023 12:22:55 +0200 Subject: [PATCH 168/280] feat(delivery): link to delivery notes list view from delivery trip --- erpnext/stock/doctype/delivery_trip/delivery_trip.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index de503dc73f..2d7a528ade 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -64,6 +64,11 @@ frappe.ui.form.on('Delivery Trip', { }) }, __("Get customers from")); } + frm.add_custom_button(__("Delivery Notes"), function () { + frappe.set_route("List", "Delivery Note", + {'name': ["in", frm.doc.delivery_stops.map((stop) => {return stop.delivery_note;})]} + ); + }, __("View")); }, calculate_arrival_time: function (frm) { From 79d51a0a0b685909371e9bda68d9702fb287c53e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Fri, 20 Oct 2023 12:31:46 +0200 Subject: [PATCH 169/280] fix(delivery): rename dt fetch stop action --- erpnext/stock/doctype/delivery_trip/delivery_trip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index de503dc73f..4a72d77956 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -62,7 +62,7 @@ frappe.ui.form.on('Delivery Trip', { company: frm.doc.company, } }) - }, __("Get customers from")); + }, __("Get stops from")); } }, From 14b009b09355f53b1dfcd05d0f7ba918b0b25210 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 20 Oct 2023 16:22:29 +0530 Subject: [PATCH 170/280] fix: incorrect cost center in the purchase invoice (#37591) --- .../purchase_invoice/test_purchase_invoice.py | 24 +++++++++++++++++++ erpnext/stock/doctype/item/test_item.py | 12 +++++++++- erpnext/stock/get_item_details.py | 6 +++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index e365d60f20..13593bcf9b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1949,6 +1949,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertEqual(po.docstatus, 1) self.assertEqual(pi.docstatus, 1) + def test_default_cost_center_for_purchase(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + + for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]: + create_cost_center(cost_center_name=c_center) + + item = create_item( + "_Test Cost Center Item For Purchase", + is_stock_item=1, + buying_cost_center="_Test Cost Center Buying - _TC", + selling_cost_center="_Test Cost Center Selling - _TC", + ) + + pi = make_purchase_invoice( + item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center="" + ) + + pi.items[0].cost_center = "" + pi.set_missing_values() + pi.calculate_taxes_and_totals() + pi.save() + + self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC") + def set_advance_flag(company, flag, default_account): frappe.db.set_value( diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 0c6dc77635..a942f58bd6 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -907,6 +907,8 @@ def create_item( opening_stock=0, is_fixed_asset=0, asset_category=None, + buying_cost_center=None, + selling_cost_center=None, company="_Test Company", ): if not frappe.db.exists("Item", item_code): @@ -924,7 +926,15 @@ def create_item( item.is_purchase_item = is_purchase_item item.is_customer_provided_item = is_customer_provided_item item.customer = customer or "" - item.append("item_defaults", {"default_warehouse": warehouse, "company": company}) + item.append( + "item_defaults", + { + "default_warehouse": warehouse, + "company": company, + "selling_cost_center": selling_cost_center, + "buying_cost_center": buying_cost_center, + }, + ) item.save() else: item = frappe.get_doc("Item", item_code) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index a6ab63bb59..595446228f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -737,6 +737,12 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan data = frappe.get_attr(path)(args.get("item_code"), company) if data and (data.selling_cost_center or data.buying_cost_center): + if args.get("customer") and data.selling_cost_center: + return data.selling_cost_center + + elif args.get("supplier") and data.buying_cost_center: + return data.buying_cost_center + return data.selling_cost_center or data.buying_cost_center if not cost_center and args.get("cost_center"): From fff97b1cd20305adf6bbd4478adf86d6847cc025 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 20 Oct 2023 17:34:57 +0530 Subject: [PATCH 171/280] chore: new erpnext logo as per espresso --- erpnext/public/images/erpnext-favicon.svg | 2 +- erpnext/public/images/erpnext-logo.png | Bin 2360 -> 4260 bytes erpnext/public/images/erpnext-logo.svg | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/images/erpnext-favicon.svg b/erpnext/public/images/erpnext-favicon.svg index 6bc6b2c2db..768e6e5514 100644 --- a/erpnext/public/images/erpnext-favicon.svg +++ b/erpnext/public/images/erpnext-favicon.svg @@ -1,5 +1,5 @@ - + diff --git a/erpnext/public/images/erpnext-logo.png b/erpnext/public/images/erpnext-logo.png index 3090727d8ff5f95e49d25278b24b10bf13747f4a..b4c27493e215b73cb61a5323131d6d2294c419ef 100644 GIT binary patch literal 4260 zcmd^DTU1kL7TzZz1c?e*6+{`J7p(&VEgBS*go>zxAQd$#LehFc3gM2RAtXv$#)4o= zA&4mHPyrPo6a;~U9AK292q-s8fS`azBnE_VNl4Bd`!rAUG%v26wa)tY-v8ySe_y|S zF8KNGTw-i#3;;`Z@ABLSK)^`?7#ZTjbV8Q~9~MRKIvfK)`QY7)KyID|E<|GX?fe2t z+t$3o8G~RC9}j@?49db+BmmPbyFESjCm_lJw~V|4=bNATcW0}1`LJ$&;Q1q)x;Eg) zj^mlnZ2Q74?{#{eb|I}|(WR#ss5bCh)cHOCKE82LZkRZqWqdfh@A2~AtNN;HIqk1* zykX0@@yx-S*wn5a2h^kh(>yt+?aW^FgEWh!DO2tFH0#pR(x8Zl-kzSpSL(A<$~bOo z1P9RY`z!$)H{XlHyvr~4Y^R* zdB2q|l}iJUvG%(b`Xf%O<|4WUsU$eryzlCYy93vKzP)M$L>kz7Wtz^_=9N1ydB^>d z-FDS2e#%~DvpExT`Wt6=aL*I~z1VeS689j-;KhUWs}~!xcVf&3($i`py`*+c$Nk2= zr9l$S^yF|e&#p+(brRMxbj%f$ zXu{g+S)IgU0U0W$I{YfjIpT|!J0f8LDwk0cC<~5ZqK?fH$Du>Sq1)AiwFipq^!tH7 z+%-e`*%F~;ABzsdb+JIR1GN0svGM0oUTC=izrh-O2XQi#{3qESwF3<$(Pb0CkMIdb zD!+HYB4H#3)>dnDe!zGBY|A>Q(jbSxUSL{wIzBH?yxgRb=1O>;#0y0Eks zc9=s-tO63=jC|@H2gQ`71%_JoYqEC%Qr~5tCWJy^*)g(xx{O zA@cdsJJv&}H)O`~l4%g@FEg3w_TbIYaf!*?db{h!$Swa|O`T z(-Xb|U%w}{YPm=ih;Z8AXx(Y$S*aZ3AW--qsmS2X7NQ>++b(AqL7v2w5*b>iQdvTr z63f(79s+(yR^Ib z+#^8i&H1dXtlN+HG6C$th-jFWHFfVGJk^;%1zW4+uSPoDXR7~X!|Sc)dV|6Y(Ek2@ zHNI2)=rCVDa#76#BR|0cuk)JAEG(vg)701w>}$^8W-`mkqQo3*8n1f`2FF!1TN(fj zJBxo-B(18VGJ$V#>(;GaZI&B=%|j8Jux2ILw7BWiYIVFZm=>h#B4#LX>4lXyz@^}N z62A^#mkPkAPt5?(Z4iKS26#4GhNrMye@#K#3dP-Oc~Tu(SmFedfYHZ)DM4&0 zy_u_3R+DNK4;`iUavsJ#7xFPE#Itgqlx{vy6H|m%{)Pv|dm0$R7}92Enr_;Eer`B} zf`&K~-`lJFU(?fn$8|1OKmT&IiDH@admh2otM7ot3b6Ty^dRa;f?Z$ZZ2&=MkI(JFacVC!Fi^t4 zt88d!=6hybA%`6k6SLY}Cwb(!*f6=uEV3R)A>T|-JZp-VrQXgUmR5j#1+Y$bmnVfx z#qngl0%LHh(CG9%_w)y$q@<*h3+}5-;bhnR)Jp-C89_ovwn97+M+2Jhjh5^{MFf*@ zs+y>;0dRgf>ZU&{lkZ6<>32rC0JQ#edjPuL00x5?C1Qh~D0Ky<@_v`~M@82F`@z2%ywWc@_FH(aqN0lmoT)EKYx98q{? zVQx}hdbH+k_=dFCBv5=?^;)`A&)eFnT9dGd)mahTuD|7e(HJ^N1*ztagImWPtz6-L z_wkDjNdTJz3aD!{yb88ww4^NM7jjEjIwBAE2Io$yLN)!RlpIyO4P@KSPM?ZZk99MD z@Hq#OE5m!@FzL@9xkkfAw*S!T2=U{#Wryqmy>TkP+nM@1|c_h=(Z-u5$JVe5$m+N_$@ou)f$L0 zTP7Rgzj-A-EDv&oORvLaqf9a;&GtV%<_xkxyc5viIM_TcgjEjof40P8%FMeG70OF_ zf>0=w{4S$Y4+%{KaK1?)Vuij2XyIwTAGh{|7w`{1xqgo4UqoQ}*BukOa}RDGO8y$} z<6d=9XSukR>jEw3T;e{5sw?&;iTjy@pW=;!QZdNE!;|9i@cAgfWYsWcB73b1>JuV?8Otn>c?9R`a0 literal 2360 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4Yzkn2Hfk$L90|U1(2s1Lwnj--e zWGoJHcVbv~PUa<$!;&U>cv7h@-A}a#}$5~Go$B+ufw>K9Sh9*k5UVLwOJ(!Va zX^Y;A2EGdl@_l>`i7y#^RP^{9(=r-dj@~ty==pE<-Z0VXbCzc-@3S*_R2nik^f53r zC@?ZGI09YN!~(R06KJv!1A~AH1A~GG1B1haQ3b;&94?q`*kP{E&iDFPT7oB2!_&XD z_5~5k&og@$F%HG3-qv`ucCQqJ+>Ok0zwHd6fknm8%iOovDvb93JGL=j-W!Kx!3@l;vG@68ZvEfg_;k**J@1(rmh2d - + From eec4057e8de4b6387303869d9ac3c5f953cbf5e6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 Oct 2023 19:42:14 +0530 Subject: [PATCH 172/280] fix: Purchase Invoice GL entires for assets --- .../purchase_receipt/purchase_receipt.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c2c4d0f539..8c2cbf7c5e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -543,16 +543,15 @@ class PurchaseReceipt(BuyingController): ) elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return): if d.is_fixed_asset: - stock_asset_account_name = ( - get_asset_category_account( - asset_category=d.asset_category, - fieldname="capital_work_in_progress_account", - company=self.company, - ) + account_type = ( + "capital_work_in_progress_account" if is_cwip_accounting_enabled(d.asset_category) - else get_asset_category_account( - asset_category=d.asset_category, fieldname="fixed_asset_account", company=self.company - ) + else "fixed_asset_account" + ) + stock_asset_account_name = get_asset_category_account( + asset_category=d.asset_category, + fieldname=account_type, + company=self.company, ) stock_value_diff = flt(d.net_amount) + flt(d.item_tax_amount / self.conversion_rate) From 94749084491d8d72a50622fafe9629d29e01e659 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 Oct 2023 19:49:41 +0530 Subject: [PATCH 173/280] fix: Purchase Invoice GL entires for assets --- .../purchase_invoice/purchase_invoice.py | 202 +++--------------- erpnext/accounts/general_ledger.py | 2 +- 2 files changed, 33 insertions(+), 171 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2433268627..2f08b65ac6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import ( ) from erpnext.accounts.party import get_due_date, get_party_account from erpnext.accounts.utils import get_account_currency, get_fiscal_year -from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled +from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.accounts_controller import validate_account_head @@ -281,9 +281,6 @@ class PurchaseInvoice(BuyingController): # in case of auto inventory accounting, # expense account is always "Stock Received But Not Billed" for a stock item # except opening entry, drop-ship entry and fixed asset items - if item.item_code: - asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - if ( auto_accounting_for_stock and item.item_code in stock_items @@ -350,22 +347,26 @@ class PurchaseInvoice(BuyingController): frappe.msgprint(msg, title=_("Expense Head Changed")) item.expense_account = stock_not_billed_account - - elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): + elif item.is_fixed_asset and item.pr_detail: + if not asset_received_but_not_billed: + asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") + item.expense_account = asset_received_but_not_billed + elif item.is_fixed_asset: + account_type = ( + "capital_work_in_progress_account" + if is_cwip_accounting_enabled(item.asset_category) + else "fixed_asset_account" + ) asset_category_account = get_asset_category_account( - "fixed_asset_account", item=item.item_code, company=self.company + account_type, item=item.item_code, company=self.company ) if not asset_category_account: - form_link = get_link_to_form("Asset Category", asset_category) + form_link = get_link_to_form("Asset Category", item.asset_category) throw( _("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company), title=_("Missing Account"), ) item.expense_account = asset_category_account - elif item.is_fixed_asset and item.pr_detail: - if not asset_received_but_not_billed: - asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") - item.expense_account = asset_received_but_not_billed elif not item.expense_account and for_validate: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -587,6 +588,7 @@ class PurchaseInvoice(BuyingController): if self.auto_accounting_for_stock: self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + self.asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") else: self.stock_received_but_not_billed = None self.expenses_included_in_valuation = None @@ -598,9 +600,6 @@ class PurchaseInvoice(BuyingController): self.make_item_gl_entries(gl_entries) self.make_precision_loss_gl_entry(gl_entries) - if self.check_asset_cwip_enabled(): - self.get_asset_gl_entry(gl_entries) - self.make_tax_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) @@ -702,7 +701,11 @@ class PurchaseInvoice(BuyingController): if item.item_code: asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items: + if ( + self.update_stock + and self.auto_accounting_for_stock + and (item.item_code in stock_items or item.is_fixed_asset) + ): # warehouse account warehouse_debit_amount = self.make_stock_adjustment_entry( gl_entries, item, voucher_wise_stock_value, account_currency @@ -817,9 +820,7 @@ class PurchaseInvoice(BuyingController): ) ) - elif not item.is_fixed_asset or ( - item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category) - ): + else: expense_account = ( item.expense_account if (not item.enable_deferred_expense or self.is_return) @@ -970,11 +971,16 @@ class PurchaseInvoice(BuyingController): (item.purchase_receipt, valuation_tax_accounts), ) + stock_rbnb = ( + self.asset_received_but_not_billed + if item.is_fixed_asset + else self.stock_received_but_not_billed + ) if not negative_expense_booked_in_pr: gl_entries.append( self.get_gl_dict( { - "account": self.stock_received_but_not_billed, + "account": stock_rbnb, "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or _("Accounting Entry for Stock"), @@ -989,156 +995,12 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount, item.precision("item_tax_amount") ) - def get_asset_gl_entry(self, gl_entries): - arbnb_account = None - eiiav_account = None - asset_eiiav_currency = None - - for item in self.get("items"): - if item.is_fixed_asset: - asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate) - base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) - - item_exp_acc_type = frappe.get_cached_value("Account", item.expense_account, "account_type") - if not item.expense_account or item_exp_acc_type not in [ - "Asset Received But Not Billed", - "Fixed Asset", - ]: - if not arbnb_account: - arbnb_account = self.get_company_default("asset_received_but_not_billed") - item.expense_account = arbnb_account - - if not self.update_stock: - arbnb_currency = get_account_currency(item.expense_account) - gl_entries.append( - self.get_gl_dict( - { - "account": item.expense_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": ( - base_asset_amount if arbnb_currency == self.company_currency else asset_amount - ), - "cost_center": item.cost_center, - "project": item.project or self.project, - }, - item=item, - ) - ) - - if item.item_tax_amount: - if not eiiav_account or not asset_eiiav_currency: - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - asset_eiiav_currency = get_account_currency(eiiav_account) - - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "cost_center": item.cost_center, - "project": item.project or self.project, - "credit": item.item_tax_amount, - "credit_in_account_currency": ( - item.item_tax_amount - if asset_eiiav_currency == self.company_currency - else item.item_tax_amount / self.conversion_rate - ), - }, - item=item, - ) - ) - else: - cwip_account = get_asset_account( - "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company - ) - - cwip_account_currency = get_account_currency(cwip_account) - gl_entries.append( - self.get_gl_dict( - { - "account": cwip_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": ( - base_asset_amount if cwip_account_currency == self.company_currency else asset_amount - ), - "cost_center": self.cost_center, - "project": item.project or self.project, - }, - item=item, - ) - ) - - if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): - if not eiiav_account or not asset_eiiav_currency: - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - asset_eiiav_currency = get_account_currency(eiiav_account) - - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "cost_center": item.cost_center, - "credit": item.item_tax_amount, - "project": item.project or self.project, - "credit_in_account_currency": ( - item.item_tax_amount - if asset_eiiav_currency == self.company_currency - else item.item_tax_amount / self.conversion_rate - ), - }, - item=item, - ) - ) - - # Assets are bought through this document then it will be linked to this document - if flt(item.landed_cost_voucher_amount): - if not eiiav_account: - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": cwip_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - - gl_entries.append( - self.get_gl_dict( - { - "account": cwip_account, - "against": eiiav_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - - # update gross amount of assets bought through this document - assets = frappe.db.get_all( - "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} - ) - for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) - frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) - - return gl_entries + assets = frappe.db.get_all( + "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) def make_stock_adjustment_entry( self, gl_entries, item, voucher_wise_stock_value, account_currency diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index d4967785ba..70a8470614 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -41,7 +41,7 @@ def make_gl_entries( from_repost=from_repost, ) save_entries(gl_map, adv_adj, update_outstanding, from_repost) - # Post GL Map proccess there may no be any GL Entries + # Post GL Map process there may no be any GL Entries elif gl_map: frappe.throw( _( From 21c3d9c3712ffca28d763b560ec8dbc9e5512fb0 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Sat, 21 Oct 2023 11:19:45 +0530 Subject: [PATCH 174/280] refactor: use gzip library's compress() and decompress() methods directly (#37611) The util methods in framework were added for python2.7 compat, so can be removed Signed-off-by: Akhil Narang [skip ci] --- .../closing_stock_balance.py | 6 +++--- erpnext/stock/stock_ledger.py | 17 ++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index 295d979b83..b0499bfe86 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -1,6 +1,6 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - +import gzip import json import frappe @@ -8,7 +8,7 @@ from frappe import _ from frappe.core.doctype.prepared_report.prepared_report import create_json_gz_file from frappe.desk.form.load import get_attachments from frappe.model.document import Document -from frappe.utils import get_link_to_form, gzip_decompress, parse_json +from frappe.utils import get_link_to_form, parse_json from frappe.utils.background_jobs import enqueue from erpnext.stock.report.stock_balance.stock_balance import execute @@ -109,7 +109,7 @@ class ClosingStockBalance(Document): attachment = attachments[0] attached_file = frappe.get_doc("File", attachment.name) - data = gzip_decompress(attached_file.get_content()) + data = gzip.decompress(attached_file.get_content()) if data := json.loads(data.decode("utf-8")): data = data diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 48119b8d1f..b950f18810 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import copy +import gzip import json from typing import Optional, Set, Tuple @@ -10,17 +11,7 @@ from frappe import _, scrub from frappe.model.meta import get_field_precision from frappe.query_builder import Case from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import ( - cint, - flt, - get_link_to_form, - getdate, - gzip_compress, - gzip_decompress, - now, - nowdate, - parse_json, -) +from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, parse_json import erpnext from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty @@ -295,7 +286,7 @@ def get_reposting_data(file_path) -> dict: attached_file = frappe.get_doc("File", file_name) - data = gzip_decompress(attached_file.get_content()) + data = gzip.decompress(attached_file.get_content()) if data := json.loads(data.decode("utf-8")): data = data @@ -378,7 +369,7 @@ def get_reposting_file_name(dt, dn): def create_json_gz_file(data, doc, file_name=None) -> str: encoded_content = frappe.safe_encode(frappe.as_json(data)) - compressed_content = gzip_compress(encoded_content) + compressed_content = gzip.compress(encoded_content) if not file_name: json_filename = f"{scrub(doc.doctype)}-{scrub(doc.name)}.json.gz" From b0d440c34b9cb4d0e0d75153c279ccaa6206253d Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Sat, 21 Oct 2023 17:58:43 +0530 Subject: [PATCH 175/280] fix: set empty value for tax template in item details (#37496) * fix: empty tax template for items with invalid templates * fix: test for empty tax template * fix: test for item tax template calculation * fix: test for pos inv tax template calculation --- .../doctype/pos_invoice/test_pos_invoice.py | 115 ++++++++--------- .../sales_invoice/test_sales_invoice.py | 120 +++++++++--------- erpnext/stock/doctype/item/test_item.py | 10 +- erpnext/stock/get_item_details.py | 1 + 4 files changed, 122 insertions(+), 124 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 00c402f97b..887f1eaeb1 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -6,6 +6,7 @@ import unittest import frappe from frappe import _ +from frappe.utils import add_days, nowdate from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile @@ -125,70 +126,64 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(inv.grand_total, 5474.0) def test_tax_calculation_with_item_tax_template(self): - inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1) - item_row = inv.get("items")[0] + import json - add_items = [ - (54, "_Test Account Excise Duty @ 12 - _TC"), - (288, "_Test Account Excise Duty @ 15 - _TC"), - (144, "_Test Account Excise Duty @ 20 - _TC"), - (430, "_Test Item Tax Template 1 - _TC"), + from erpnext.stock.get_item_details import get_item_details + + # set tax template in item + item = frappe.get_cached_doc("Item", "_Test Item") + item.set( + "taxes", + [ + { + "item_tax_template": "_Test Account Excise Duty @ 15 - _TC", + "valid_from": add_days(nowdate(), -5), + } + ], + ) + item.save() + + # create POS invoice with item + pos_inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True) + item_details = get_item_details( + doc=pos_inv, + args={ + "item_code": item.item_code, + "company": pos_inv.company, + "doctype": "POS Invoice", + "conversion_rate": 1.0, + }, + ) + tax_map = json.loads(item_details.item_tax_rate) + for tax in tax_map: + pos_inv.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": tax, + "rate": tax_map[tax], + "description": "Test", + "cost_center": "_Test Cost Center - _TC", + }, + ) + pos_inv.submit() + pos_inv.load_from_db() + + # check if correct tax values are applied from tax template + self.assertEqual(pos_inv.net_total, 386.4) + + expected_taxes = [ + { + "tax_amount": 57.96, + "total": 444.36, + }, ] - for qty, item_tax_template in add_items: - item_row_copy = copy.deepcopy(item_row) - item_row_copy.qty = qty - item_row_copy.item_tax_template = item_tax_template - inv.append("items", item_row_copy) - inv.append( - "taxes", - { - "account_head": "_Test Account Excise Duty - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Excise Duty", - "doctype": "Sales Taxes and Charges", - "rate": 11, - }, - ) - inv.append( - "taxes", - { - "account_head": "_Test Account Education Cess - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Education Cess", - "doctype": "Sales Taxes and Charges", - "rate": 0, - }, - ) - inv.append( - "taxes", - { - "account_head": "_Test Account S&H Education Cess - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "S&H Education Cess", - "doctype": "Sales Taxes and Charges", - "rate": 3, - }, - ) - inv.insert() + for i in range(len(expected_taxes)): + for key in expected_taxes[i]: + self.assertEqual(expected_taxes[i][key], pos_inv.get("taxes")[i].get(key)) - self.assertEqual(inv.net_total, 4600) - - self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41) - self.assertEqual(inv.get("taxes")[0].total, 5102.41) - - self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80) - self.assertEqual(inv.get("taxes")[1].total, 5300.21) - - self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36) - self.assertEqual(inv.get("taxes")[2].total, 5675.57) - - self.assertEqual(inv.grand_total, 5675.57) - self.assertEqual(inv.rounding_adjustment, 0.43) - self.assertEqual(inv.rounded_total, 5676.0) + self.assertEqual(pos_inv.get("base_total_taxes_and_charges"), 57.96) def test_tax_calculation_with_multiple_items_and_discount(self): inv = create_pos_invoice(qty=1, rate=75, do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c1adffde31..16477324e6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -516,70 +516,72 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(si.grand_total, 5474.0) def test_tax_calculation_with_item_tax_template(self): + import json + + from erpnext.stock.get_item_details import get_item_details + + # set tax template in item + item = frappe.get_cached_doc("Item", "_Test Item") + item.set( + "taxes", + [ + { + "item_tax_template": "_Test Item Tax Template 1 - _TC", + "valid_from": add_days(nowdate(), -5), + } + ], + ) + item.save() + + # create sales invoice with item si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) - item_row = si.get("items")[0] + item_details = get_item_details( + doc=si, + args={ + "item_code": item.item_code, + "company": si.company, + "doctype": "Sales Invoice", + "conversion_rate": 1.0, + }, + ) + tax_map = json.loads(item_details.item_tax_rate) + for tax in tax_map: + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": tax, + "rate": tax_map[tax], + "description": "Test", + "cost_center": "_Test Cost Center - _TC", + }, + ) + si.submit() + si.load_from_db() - add_items = [ - (54, "_Test Account Excise Duty @ 12 - _TC"), - (288, "_Test Account Excise Duty @ 15 - _TC"), - (144, "_Test Account Excise Duty @ 20 - _TC"), - (430, "_Test Item Tax Template 1 - _TC"), + # check if correct tax values are applied from tax template + self.assertEqual(si.net_total, 386.4) + + expected_taxes = [ + { + "tax_amount": 19.32, + "total": 405.72, + }, + { + "tax_amount": 38.64, + "total": 444.36, + }, + { + "tax_amount": 57.96, + "total": 502.32, + }, ] - for qty, item_tax_template in add_items: - item_row_copy = copy.deepcopy(item_row) - item_row_copy.qty = qty - item_row_copy.item_tax_template = item_tax_template - si.append("items", item_row_copy) - si.append( - "taxes", - { - "account_head": "_Test Account Excise Duty - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Excise Duty", - "doctype": "Sales Taxes and Charges", - "rate": 11, - }, - ) - si.append( - "taxes", - { - "account_head": "_Test Account Education Cess - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "Education Cess", - "doctype": "Sales Taxes and Charges", - "rate": 0, - }, - ) - si.append( - "taxes", - { - "account_head": "_Test Account S&H Education Cess - _TC", - "charge_type": "On Net Total", - "cost_center": "_Test Cost Center - _TC", - "description": "S&H Education Cess", - "doctype": "Sales Taxes and Charges", - "rate": 3, - }, - ) - si.insert() + for i in range(len(expected_taxes)): + for key in expected_taxes[i]: + self.assertEqual(expected_taxes[i][key], si.get("taxes")[i].get(key)) - self.assertEqual(si.net_total, 4600) - - self.assertEqual(si.get("taxes")[0].tax_amount, 502.41) - self.assertEqual(si.get("taxes")[0].total, 5102.41) - - self.assertEqual(si.get("taxes")[1].tax_amount, 197.80) - self.assertEqual(si.get("taxes")[1].total, 5300.21) - - self.assertEqual(si.get("taxes")[2].tax_amount, 375.36) - self.assertEqual(si.get("taxes")[2].total, 5675.57) - - self.assertEqual(si.grand_total, 5675.57) - self.assertEqual(si.rounding_adjustment, 0.43) - self.assertEqual(si.rounded_total, 5676.0) + self.assertEqual(si.get("base_total_taxes_and_charges"), 115.92) def test_tax_calculation_with_multiple_items_and_discount(self): si = create_sales_invoice(qty=1, rate=75, do_not_save=True) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index a942f58bd6..09d3dd1dad 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -163,7 +163,7 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item With Item Tax Template", "tax_category": "_Test Tax Category 2", - "item_tax_template": None, + "item_tax_template": "", }, { "item_code": "_Test Item Inherit Group Item Tax Template 1", @@ -178,7 +178,7 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item Inherit Group Item Tax Template 1", "tax_category": "_Test Tax Category 2", - "item_tax_template": None, + "item_tax_template": "", }, { "item_code": "_Test Item Inherit Group Item Tax Template 2", @@ -193,7 +193,7 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item Inherit Group Item Tax Template 2", "tax_category": "_Test Tax Category 2", - "item_tax_template": None, + "item_tax_template": "", }, { "item_code": "_Test Item Override Group Item Tax Template", @@ -208,12 +208,12 @@ class TestItem(FrappeTestCase): { "item_code": "_Test Item Override Group Item Tax Template", "tax_category": "_Test Tax Category 2", - "item_tax_template": None, + "item_tax_template": "", }, ] expected_item_tax_map = { - None: {}, + "": {}, "_Test Account Excise Duty @ 10 - _TC": {"_Test Account Excise Duty - _TC": 10}, "_Test Account Excise Duty @ 12 - _TC": {"_Test Account Excise Duty - _TC": 12}, "_Test Account Excise Duty @ 15 - _TC": {"_Test Account Excise Duty - _TC": 15}, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 595446228f..8c6fd84bc4 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -606,6 +606,7 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): # all templates have validity and no template is valid if not taxes_with_validity and (not taxes_with_no_validity): + out["item_tax_template"] = "" return None # do not change if already a valid template From 9d392970f02b510799baa7123e1eb64fbb62dcf5 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Sat, 21 Oct 2023 17:59:12 +0530 Subject: [PATCH 176/280] fix(minor): filter bank accounts in bank statement import (#37525) fix: filter by company in bank account --- .../bank_statement_import/bank_statement_import.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index a70af7a90e..db68dfad79 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -2,6 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on("Bank Statement Import", { + onload(frm) { + frm.set_query("bank_account", function (doc) { + return { + filters: { + company: doc.company, + }, + }; + }); + }, + setup(frm) { frappe.realtime.on("data_import_refresh", ({ data_import }) => { frm.import_in_progress = false; From 1cc1c9aa38ad3816c1456ec71dce478b672b011e Mon Sep 17 00:00:00 2001 From: William Moreno Date: Sat, 21 Oct 2023 06:32:02 -0600 Subject: [PATCH 177/280] fix: Typo in Nicaraguan chart of accounts (#37620) fix: Typo in Nicaraguan chart of accounts --- .../chart_of_accounts/verified/ni_catalogo_de_cuentas.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ni_catalogo_de_cuentas.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ni_catalogo_de_cuentas.json index e8402d6d7e..73ac4ab3c8 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ni_catalogo_de_cuentas.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ni_catalogo_de_cuentas.json @@ -1,6 +1,6 @@ { "country_code": "ni", - "name": "Nicaragua - Catalogo de Cuentas", + "name": "Nicaragua - Catálogo de Cuentas", "tree": { "Activo": { "Activo Corriente": { @@ -491,4 +491,4 @@ "root_type": "Liability" } } -} \ No newline at end of file +} From 35020a94234aee3412df99c1074d9c3a7fca16c1 Mon Sep 17 00:00:00 2001 From: Vishnu VS Date: Sat, 21 Oct 2023 18:02:32 +0530 Subject: [PATCH 178/280] fix: error while loading Financial Ratios report (#37613) --- erpnext/accounts/report/financial_ratios/financial_ratios.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/financial_ratios/financial_ratios.py b/erpnext/accounts/report/financial_ratios/financial_ratios.py index 57421ebcb0..47b4fd0da0 100644 --- a/erpnext/accounts/report/financial_ratios/financial_ratios.py +++ b/erpnext/accounts/report/financial_ratios/financial_ratios.py @@ -177,8 +177,8 @@ def add_solvency_ratios( return_on_equity_ratio = {"ratio": "Return on Equity Ratio"} for year in years: - profit_after_tax = total_income[year] + total_expense[year] - share_holder_fund = total_asset[year] - total_liability[year] + profit_after_tax = flt(total_income.get(year)) + flt(total_expense.get(year)) + share_holder_fund = flt(total_asset.get(year)) - flt(total_liability.get(year)) debt_equity_ratio[year] = calculate_ratio( total_liability.get(year), share_holder_fund, precision From 98cc7434d286b0e13a919a8aa7a481524d200650 Mon Sep 17 00:00:00 2001 From: Vishnu VS Date: Sat, 21 Oct 2023 18:04:54 +0530 Subject: [PATCH 179/280] feat(Supplier Scorecard): added method for invoiced quantity in supplier scorecard (#37580) feat(Supplier Scorecard): added method for invoiced quantity in supplier scorecard Co-authored-by: vishnu --- .../supplier_scorecard/supplier_scorecard.py | 5 +++++ .../supplier_scorecard_variable.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py index 6e22acf01a..683a12ac95 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -334,6 +334,11 @@ def make_default_records(): "variable_label": "Total Ordered", "path": "get_ordered_qty", }, + { + "param_name": "total_invoiced", + "variable_label": "Total Invoiced", + "path": "get_invoiced_qty", + }, ] install_standing_docs = [ { diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py index 4080d1fde0..6c91a049db 100644 --- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py @@ -440,6 +440,23 @@ def get_ordered_qty(scorecard): ).run(as_list=True)[0][0] or 0 +def get_invoiced_qty(scorecard): + """Returns the total number of invoiced quantity (based on Purchase Invoice)""" + + pi = frappe.qb.DocType("Purchase Invoice") + + return ( + frappe.qb.from_(pi) + .select(Sum(pi.total_qty)) + .where( + (pi.supplier == scorecard.supplier) + & (pi.docstatus == 1) + & (pi.posting_date >= scorecard.get("start_date")) + & (pi.posting_date <= scorecard.get("end_date")) + ) + ).run(as_list=True)[0][0] or 0 + + def get_rfq_total_number(scorecard): """Gets the total number of RFQs sent to supplier""" supplier = frappe.get_doc("Supplier", scorecard.supplier) From 4aa841786fbfd4b438a9a5913b002bf1faaa4bb7 Mon Sep 17 00:00:00 2001 From: Niraj Gautam Date: Sat, 21 Oct 2023 18:13:53 +0530 Subject: [PATCH 180/280] fix: Update user profile picture, if employee profile pic is changed (#37483) * fix: Update user pic if employee pic is changed. * fix: Update condition --- erpnext/setup/doctype/employee/employee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 566392c327..78fb4dfc58 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -123,7 +123,7 @@ class Employee(NestedSet): user.gender = self.gender if self.image: - if not user.user_image: + if not user.user_image or self.has_value_changed("image"): user.user_image = self.image try: frappe.get_doc( From 5136fe196b4e3aab6bb18d2edf5effbfacd2b060 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 22 Oct 2023 20:03:02 +0530 Subject: [PATCH 181/280] fix: remove from or target warehouse for non internal transfer entries (#37612) --- erpnext/controllers/stock_controller.py | 22 +++++++++++++------ .../delivery_note/test_delivery_note.py | 15 +++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 15 +++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 98d8248fff..61d7107437 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -689,13 +689,21 @@ class StockController(AccountsController): d.stock_uom_rate = d.rate / (d.conversion_factor or 1) def validate_internal_transfer(self): - if ( - self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt") - and self.is_internal_transfer() - ): - self.validate_in_transit_warehouses() - self.validate_multi_currency() - self.validate_packed_items() + if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"): + if self.is_internal_transfer(): + self.validate_in_transit_warehouses() + self.validate_multi_currency() + self.validate_packed_items() + else: + self.validate_internal_transfer_warehouse() + + def validate_internal_transfer_warehouse(self): + for row in self.items: + if row.get("target_warehouse"): + row.target_warehouse = None + + if row.get("from_warehouse"): + row.from_warehouse = None def validate_in_transit_warehouses(self): if ( diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 48b8ab7504..d06819208e 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1230,6 +1230,21 @@ class TestDeliveryNote(FrappeTestCase): frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) + def non_internal_transfer_delivery_note(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + dn = create_delivery_note(do_not_submit=True) + warehouse = create_warehouse("Internal Transfer Warehouse", dn.company) + dn.items[0].db_set("target_warehouse", "warehouse") + + dn.reload() + + self.assertEqual(dn.items[0].target_warehouse, warehouse.name) + + dn.save() + dn.reload() + self.assertFalse(dn.items[0].target_warehouse) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index cdf50532fc..1af7b9aefc 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2142,6 +2142,21 @@ class TestPurchaseReceipt(FrappeTestCase): for entry in gl_entries: self.assertEqual(abs(entry.debit + entry.credit), abs(sl_entries[0].stock_value_difference)) + def non_internal_transfer_purchase_receipt(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + pr_doc = make_purchase_receipt(do_not_submit=True) + warehouse = create_warehouse("Internal Transfer Warehouse", pr_doc.company) + pr_doc.items[0].db_set("target_warehouse", "warehouse") + + pr_doc.reload() + + self.assertEqual(pr_doc.items[0].from_warehouse, warehouse.name) + + pr_doc.save() + pr_doc.reload() + self.assertFalse(pr_doc.items[0].from_warehouse) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From d1ec0a609329ae94f90fcccc2d6a9f7a473f013d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 23 Oct 2023 00:16:40 +0530 Subject: [PATCH 182/280] chore: Add missing commits back (#37618) * chore: Add missing commits back * test: cwip accounting unit tests * chore: Attribute error * chore: Purchase Invoice tests * chore: Missing asset account * chore: Missing asset account * chore: update tests * fix: Internal transfer GL Entries --- erpnext/assets/doctype/asset/test_asset.py | 10 +- erpnext/controllers/stock_controller.py | 3 + .../purchase_receipt/purchase_receipt.py | 158 +++++++++++------- 3 files changed, 101 insertions(+), 70 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 88ef69cddc..99824b7f67 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -19,7 +19,6 @@ from frappe.utils import ( from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.assets.doctype.asset.asset import ( - get_asset_value_after_depreciation, make_sales_invoice, split_asset, update_maintenance_status, @@ -194,6 +193,7 @@ class TestAsset(AssetSetup): def test_is_fixed_asset_set(self): asset = create_asset(is_existing_asset=1) doc = frappe.new_doc("Purchase Invoice") + doc.company = "_Test Company" doc.supplier = "_Test Supplier" doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name}) @@ -534,7 +534,7 @@ class TestAsset(AssetSetup): self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account) - # CWIP: Capital Work In Progress + # Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt( item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location" @@ -567,7 +567,8 @@ class TestAsset(AssetSetup): pr.submit() expected_gle = ( - ("Asset Received But Not Billed - _TC", 0.0, 5250.0), + ("_Test Account Shipping Charges - _TC", 0.0, 250.0), + ("Asset Received But Not Billed - _TC", 0.0, 5000.0), ("CWIP Account - _TC", 5250.0, 0.0), ) @@ -586,9 +587,8 @@ class TestAsset(AssetSetup): expected_gle = ( ("_Test Account Service Tax - _TC", 250.0, 0.0), ("_Test Account Shipping Charges - _TC", 250.0, 0.0), - ("Asset Received But Not Billed - _TC", 5250.0, 0.0), + ("Asset Received But Not Billed - _TC", 5000.0, 0.0), ("Creditors - _TC", 0.0, 5500.0), - ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), ) pi_gle = frappe.db.sql( diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 61d7107437..a40976b8dd 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -62,9 +62,12 @@ class StockController(AccountsController): ) ) + is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items")) + if ( cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items + or is_asset_pr ): warehouse_account = get_warehouse_account_map(self.company) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 9fe06a2d2e..9fdb01a662 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -13,7 +13,6 @@ from pypika import functions as fn import erpnext from erpnext.accounts.utils import get_account_currency from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled -from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction @@ -313,6 +312,7 @@ class PurchaseReceipt(BuyingController): self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) self.make_tax_gl_entries(gl_entries) + update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries) @@ -321,22 +321,6 @@ class PurchaseReceipt(BuyingController): get_purchase_document_details, ) - is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) - stock_asset_rbnb = None - remarks = self.get("remarks") or _("Accounting Entry for {0}").format( - "Asset" if is_asset_pr else "Stock" - ) - - if erpnext.is_perpetual_inventory_enabled(self.company): - stock_asset_rbnb = ( - self.get_company_default("asset_received_but_not_billed") - if is_asset_pr - else self.get_company_default("stock_received_but_not_billed") - ) - landed_cost_entries = get_item_account_wise_additional_cost(self.name) - - warehouse_with_no_account = [] - stock_items = self.get_stock_items() provisional_accounting_for_non_stock_items = cint( frappe.db.get_value( "Company", self.company, "enable_provisional_accounting_for_non_stock_items" @@ -345,8 +329,15 @@ class PurchaseReceipt(BuyingController): exchange_rate_map, net_rate_map = get_purchase_document_details(self) - def make_item_asset_inward_entries(item, stock_value_diff, stock_asset_account_name): + def validate_account(account_type): + frappe.throw(_("{0} account not found while submitting purchase receipt").format(account_type)) + + def make_item_asset_inward_gl_entry(item, stock_value_diff, stock_asset_account_name): account_currency = get_account_currency(stock_asset_account_name) + + if not stock_asset_account_name: + validate_account("Asset or warehouse account") + self.add_gl_entry( gl_entries=gl_entries, account=stock_asset_account_name, @@ -365,7 +356,6 @@ class PurchaseReceipt(BuyingController): ) account_currency = get_account_currency(account) - outgoing_amount = item.base_net_amount # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation credit_amount = ( @@ -374,11 +364,15 @@ class PurchaseReceipt(BuyingController): else flt(item.net_amount, item.precision("net_amount")) ) + outgoing_amount = item.base_net_amount if self.is_internal_transfer() and item.valuation_rate: outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse)) credit_amount = outgoing_amount if credit_amount: + if not account: + validate_account("Stock or Asset Received But Not Billed") + self.add_gl_entry( gl_entries=gl_entries, account=account, @@ -387,7 +381,7 @@ class PurchaseReceipt(BuyingController): credit=0.0, remarks=remarks, against_account=stock_asset_account_name, - debit_in_account_currency=-1 * credit_amount, + debit_in_account_currency=-1 * flt(outgoing_amount, item.precision("base_net_amount")), account_currency=account_currency, item=item, ) @@ -430,8 +424,10 @@ class PurchaseReceipt(BuyingController): item=item, ) + return outgoing_amount + def make_landed_cost_gl_entries(item): - # Amount added through landed-cos-voucher + # Amount added through landed-cost-voucher if item.landed_cost_voucher_amount and landed_cost_entries: if (item.item_code, item.name) in landed_cost_entries: for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): @@ -442,6 +438,9 @@ class PurchaseReceipt(BuyingController): else flt(amount["amount"]) ) + if not account: + validate_account("Landed Cost Account") + self.add_gl_entry( gl_entries=gl_entries, account=account, @@ -487,14 +486,14 @@ class PurchaseReceipt(BuyingController): item=item, ) - def make_divisional_loss_gl_entry(item): + def make_divisional_loss_gl_entry(item, outgoing_amount): if item.is_fixed_asset: return # divisional loss adjustment expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") valuation_amount_as_per_doc = ( - flt(item.base_net_amount, d.precision("base_net_amount")) + flt(outgoing_amount, d.precision("base_net_amount")) + flt(item.landed_cost_voucher_amount) + flt(item.rm_supp_cost) + flt(item.item_tax_amount) @@ -531,35 +530,53 @@ class PurchaseReceipt(BuyingController): item=item, ) + stock_items = self.get_stock_items() + warehouse_with_no_account = [] + for d in self.get("items"): if ( - d.item_code not in stock_items + provisional_accounting_for_non_stock_items + and d.item_code not in stock_items and flt(d.qty) - and provisional_accounting_for_non_stock_items and d.get("provisional_expense_account") + and not d.is_fixed_asset ): self.add_provisional_gl_entry( d, gl_entries, self.posting_date, d.get("provisional_expense_account") ) elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return): + is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) + remarks = self.get("remarks") or _("Accounting Entry for {0}").format( + "Asset" if is_asset_pr else "Stock" + ) + + if not (erpnext.is_perpetual_inventory_enabled(self.company) or is_asset_pr): + return + + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed") + if is_asset_pr + else self.get_company_default("stock_received_but_not_billed") + ) + landed_cost_entries = get_item_account_wise_additional_cost(self.name) + if d.is_fixed_asset: account_type = ( "capital_work_in_progress_account" if is_cwip_accounting_enabled(d.asset_category) else "fixed_asset_account" ) - stock_asset_account_name = get_asset_category_account( - asset_category=d.asset_category, - fieldname=account_type, - company=self.company, + + stock_asset_account_name = get_asset_account( + account_type, asset_category=d.asset_category, company=self.company ) - stock_value_diff = flt(d.net_amount) + flt(d.item_tax_amount / self.conversion_rate) - elif ( - (flt(d.valuation_rate) or self.is_return) - and flt(d.qty) - and warehouse_account.get(d.warehouse) - ): + stock_value_diff = ( + flt(d.net_amount) + + flt(d.item_tax_amount / self.conversion_rate) + + flt(d.landed_cost_voucher_amount) + ) + elif warehouse_account.get(d.warehouse): stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) stock_asset_account_name = warehouse_account[d.warehouse]["account"] supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") @@ -577,12 +594,13 @@ class PurchaseReceipt(BuyingController): ): continue - make_item_asset_inward_entries(d, stock_value_diff, stock_asset_account_name) - make_stock_received_but_not_billed_entry(d) - make_landed_cost_gl_entries(d) - make_rate_difference_entry(d) - make_sub_contracting_gl_entries(d) - make_divisional_loss_gl_entry(d) + if (flt(d.valuation_rate) or self.is_return or d.is_fixed_asset) and flt(d.qty): + make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) + outgoing_amount = make_stock_received_but_not_billed_entry(d) + make_landed_cost_gl_entries(d) + make_rate_difference_entry(d) + make_sub_contracting_gl_entries(d) + make_divisional_loss_gl_entry(d, outgoing_amount) elif ( d.warehouse not in warehouse_with_no_account or d.rejected_warehouse not in warehouse_with_no_account @@ -603,8 +621,8 @@ class PurchaseReceipt(BuyingController): self, item, gl_entries, posting_date, provisional_account, reverse=0 ): credit_currency = get_account_currency(provisional_account) - debit_currency = get_account_currency(item.expense_account) expense_account = item.expense_account + debit_currency = get_account_currency(item.expense_account) remarks = self.get("remarks") or _("Accounting Entry for Service") multiplication_factor = 1 @@ -645,11 +663,8 @@ class PurchaseReceipt(BuyingController): ) def make_tax_gl_entries(self, gl_entries): - - if erpnext.is_perpetual_inventory_enabled(self.company): - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) + is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): @@ -673,22 +688,24 @@ class PurchaseReceipt(BuyingController): # and charges added via Landed Cost Voucher, # post valuation related charges on "Stock Received But Not Billed" # introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account - - negative_expense_booked_in_pi = frappe.db.sql( - """select name from `tabPurchase Invoice Item` pi - where docstatus = 1 and purchase_receipt=%s - and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' - and voucher_no=pi.parent and account=%s)""", - (self.name, expenses_included_in_valuation), - ) - against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked - stock_rbnb = self.get_company_default("stock_received_but_not_billed") + stock_rbnb = ( + self.get("asset_received_but_not_billed") + if is_asset_pr + else self.get_company_default("stock_received_but_not_billed") + ) i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): + negative_expense_booked_in_pi = frappe.db.sql( + """select name from `tabPurchase Invoice Item` pi + where docstatus = 1 and purchase_receipt=%s + and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' + and voucher_no=pi.parent and account=%s)""", + (self.name, tax.account_head), + ) if negative_expense_booked_in_pi: account = stock_rbnb @@ -740,7 +757,7 @@ class PurchaseReceipt(BuyingController): po_details.append(d.purchase_order_item) if po_details: - updated_pr += update_billed_amount_based_on_po(po_details, update_modified) + updated_pr += update_billed_amount_based_on_po(po_details, update_modified, self) for pr in set(updated_pr): pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) @@ -763,7 +780,7 @@ def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse): ) -def update_billed_amount_based_on_po(po_details, update_modified=True): +def update_billed_amount_based_on_po(po_details, update_modified=True, pr_doc=None): po_billed_amt_details = get_billed_amount_against_po(po_details) # Get all Purchase Receipt Item rows against the Purchase Order Items @@ -792,13 +809,19 @@ def update_billed_amount_based_on_po(po_details, update_modified=True): po_billed_amt_details[pr_item.purchase_order_item] = billed_against_po if pr_item.billed_amt != billed_amt_agianst_pr: - frappe.db.set_value( - "Purchase Receipt Item", - pr_item.name, - "billed_amt", - billed_amt_agianst_pr, - update_modified=update_modified, - ) + # update existing doc if possible + if pr_doc and pr_item.parent == pr_doc.name: + pr_item = next((item for item in pr_doc.items if item.name == pr_item.name), None) + pr_item.db_set("billed_amt", billed_amt_agianst_pr, update_modified=update_modified) + + else: + frappe.db.set_value( + "Purchase Receipt Item", + pr_item.name, + "billed_amt", + billed_amt_agianst_pr, + update_modified=update_modified, + ) updated_pr.append(pr_item.parent) @@ -1184,3 +1207,8 @@ def get_item_account_wise_additional_cost(purchase_document): def on_doctype_update(): frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"]) + + +@erpnext.allow_regional +def update_regional_gl_entries(gl_list, doc): + return From ec9434aae3d0ec0cfa6f6c58dcc9db21b677ac8a Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 23 Oct 2023 06:45:23 +0200 Subject: [PATCH 183/280] refactor: remove fr translation duplicate in frappe app (#37288) --- erpnext/translations/fr.csv | 412 +----------------------------------- 1 file changed, 4 insertions(+), 408 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 32fe9fffa0..d3875c1132 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -21,9 +21,6 @@ A Lead requires either a person's name or an organization's name,Un responsable A customer with the same name already exists,Un client avec un nom identique existe déjà, A question must have more than one options,Une question doit avoir plus d'une option, A qustion must have at least one correct options,Une qustion doit avoir au moins une des options correctes, -A4,A4, -API Endpoint,API Endpoint, -API Key,Clé API, Abbr can not be blank or space,Abré. ne peut être vide ou contenir un espace, Abbreviation already used for another company,Abréviation déjà utilisée pour une autre société, Abbreviation cannot have more than 5 characters,L'abbréviation ne peut pas avoir plus de 5 caractères, @@ -36,9 +33,7 @@ Academic Term: ,Période scolaire:, Academic Year,Année académique, Academic Year: ,Année scolaire:, Accepted + Rejected Qty must be equal to Received quantity for Item {0},La Qté Acceptée + Rejetée doit être égale à la quantité Reçue pour l'Article {0}, -Access Token,Jeton d'Accès, Accessable Value,Valeur accessible, -Account,Compte, Account Number,Numéro de compte, Account Number {0} already used in account {1},Numéro de compte {0} déjà utilisé dans le compte {1}, Account Pay Only,Compte Bénéficiaire Seulement, @@ -75,12 +70,10 @@ Accounting Entry for {0}: {1} can only be made in currency: {2},Écriture Compta Accounting Ledger,Livre des Comptes, Accounting journal entries.,Les écritures comptables., Accounts,Comptes, -Accounts Manager,Responsable des Comptes, Accounts Payable,Comptes Créditeurs, Accounts Payable Summary,Résumé des Comptes Créditeurs, Accounts Receivable,Comptes débiteurs, Accounts Receivable Summary,Résumé des Comptes Débiteurs, -Accounts User,Comptable, Accounts table cannot be blank.,Le tableau de comptes ne peut être vide., Accumulated Depreciation,Amortissement Cumulé, Accumulated Depreciation Amount,Montant d'Amortissement Cumulé, @@ -89,10 +82,8 @@ Accumulated Monthly,Cumul mensuel, Accumulated Values,Valeurs accumulées, Accumulated Values in Group Company,Valeurs accumulées dans la société mère, Achieved ({}),Atteint ({}), -Action,Action, Action Initialised,Action initialisée, Actions,Actions, -Active,actif, Activity Cost exists for Employee {0} against Activity Type - {1},Des Coûts d'Activité existent pour l'Employé {0} pour le Type d'Activité - {1}, Activity Cost per Employee,Coût de l'Activité par Employé, Activity Type,Type d'activité, @@ -104,7 +95,6 @@ Actual Qty {0} / Waiting Qty {1},Qté Réelle {0} / Quantité en Attente {1}, Actual Qty: Quantity available in the warehouse.,Quantité réelle : Quantité disponible dans l'entrepôt ., Actual qty in stock,Qté réelle en stock, Actual type tax cannot be included in Item rate in row {0},Le type de taxe réel ne peut pas être inclus dans le prix de l'Article à la ligne {0}, -Add,Ajouter, Add / Edit Prices,Ajouter / Modifier Prix, Add Comment,Ajouter un Commentaire, Add Customers,Ajouter des clients, @@ -127,17 +117,11 @@ Add more items or open full form,Ajouter plus d'articles ou ouvrir le formulaire Add notes,Ajouter des notes, Add the rest of your organization as your users. You can also add invite Customers to your portal by adding them from Contacts,Ajouter le reste de votre organisation en tant qu'utilisateurs. Vous pouvez aussi inviter des Clients sur votre portail en les ajoutant depuis les Contacts, Add/Remove Recipients,Ajouter/Supprimer des Destinataires, -Added,Ajouté, Added {0} users,{0} utilisateurs ajoutés, Additional Salary Component Exists.,La composante salariale supplémentaire existe., -Address,Adresse, -Address Line 2,Adresse Ligne 2, Address Name,Nom de l'Adresse, -Address Title,Titre de l'Adresse, -Address Type,Type d'Adresse, Administrative Expenses,Charges Administratives, Administrative Officer,Agent administratif, -Administrator,Administrateur, Admission,Admission, Admission and Enrollment,Admission et inscription, Admissions for {0},Admissions pour {0}, @@ -171,7 +155,6 @@ All Assessment Groups,Tous les Groupes d'Évaluation, All BOMs,Toutes les nomenclatures, All Contacts.,Tous les contacts., All Customer Groups,Tous les Groupes Client, -All Day,Toute la Journée, All Departments,Tous les départements, All Healthcare Service Units,Tous les services de soins de santé, All Item Groups,Tous les Groupes d'Articles, @@ -193,8 +176,6 @@ Already record exists for the item {0},L'enregistrement existe déjà pour l'art "Already set default in pos profile {0} for user {1}, kindly disabled default","Déjà défini par défaut dans le profil pdv {0} pour l'utilisateur {1}, veuillez désactiver la valeur par défaut", Alternate Item,Article alternatif, Alternative item must not be same as item code,L'article alternatif ne doit pas être le même que le code article, -Amended From,Modifié Depuis, -Amount,Montant, Amount After Depreciation,Montant après amortissement, Amount of Integrated Tax,Montant de la taxe intégrée, Amount of TDS Deducted,Quantité de TDS déduite, @@ -216,7 +197,6 @@ Another Period Closing Entry {0} has been made after {1},Une autre Entrée de Cl Another Sales Person {0} exists with the same Employee id,Un autre Commercial {0} existe avec le même ID d'Employé, Antibiotic,Antibiotique, Apparel & Accessories,Vêtements & Accessoires, -Applicable For,Applicable Pour, "Applicable if the company is SpA, SApA or SRL","Applicable si la société est SpA, SApA ou SRL", Applicable if the company is a limited liability company,Applicable si la société est une société à responsabilité limitée, Applicable if the company is an Individual or a Proprietorship,Applicable si la société est un particulier ou une entreprise, @@ -264,15 +244,12 @@ Asset scrapped via Journal Entry {0},Actif mis au rebut via Écriture de Journal Asset {0} does not belong to company {1},L'actif {0} ne fait pas partie à la société {1}, Asset {0} must be submitted,L'actif {0} doit être soumis, Assets,Actifs - Immo., -Assign To,Attribuer À, Associate,Associé, At least one mode of payment is required for POS invoice.,Au moins un mode de paiement est nécessaire pour une facture de PDV, Atleast one item should be entered with negative quantity in return document,Au moins un article doit être saisi avec quantité négative dans le document de retour, Atleast one of the Selling or Buying must be selected,Au moins Vente ou Achat doit être sélectionné, Atleast one warehouse is mandatory,Au moins un entrepôt est obligatoire, Attach Logo,Attacher le logo, -Attachment,Pièce jointe, -Attachments,Pièces jointes, Attendance can not be marked for future dates,La présence ne peut pas être marquée pour les dates à venir, Attendance date can not be less than employee's joining date,Date de présence ne peut pas être antérieure à la date d'embauche de l'employé, Attendance for employee {0} is already marked,La présence de l'employé {0} est déjà marquée, @@ -282,7 +259,6 @@ Attribute table is mandatory,Table d'Attribut est obligatoire, Attribute {0} selected multiple times in Attributes Table,Attribut {0} sélectionné à plusieurs reprises dans le Tableau des Attributs, Authorized Signatory,Signataire Autorisé, Auto Material Requests Generated,Demandes de Matériel Générées Automatiquement, -Auto Repeat,Répétition automatique, Auto repeat document updated,Document de répétition automatique mis à jour, Automotive,Automobile, Available,Disponible, @@ -332,8 +308,6 @@ Banking,Banque, Banking and Payments,Banque et paiements, Barcode {0} already used in Item {1},Le Code Barre {0} est déjà utilisé dans l'article {1}, Barcode {0} is not a valid {1} code,Le code-barres {0} n'est pas un code {1} valide, -Base URL,URL de base, -Based On,Basé Sur, Based On Payment Terms,Basé sur les conditions de paiement, Batch,Lot, Batch Entries,Entrées de lot, @@ -406,7 +380,6 @@ Can only make payment against unbilled {0},Le paiement n'est possible qu'avec le Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total',Peut se référer à ligne seulement si le type de charge est 'Montant de la ligne précedente' ou 'Total des lignes précedente', "Can't change valuation method, as there are transactions against some items which does not have it's own valuation method","Impossible de modifier la méthode de valorisation, car il existe des transactions sur certains articles ne possèdant pas leur propre méthode de valorisation", Can't create standard criteria. Please rename the criteria,Impossible de créer des critères standard. Veuillez renommer les critères, -Cancel,Annuler, Cancel Material Visit {0} before cancelling this Warranty Claim,Annuler la Visite Matérielle {0} avant d'annuler cette Réclamation de Garantie, Cancel Material Visits {0} before cancelling this Maintenance Visit,Annuler les Visites Matérielles {0} avant d'annuler cette Visite de Maintenance, Cancel Subscription,Annuler l'abonnement, @@ -459,8 +432,6 @@ Cash Flow from Operations,Flux de trésorerie provenant des opérations, Cash In Hand,Liquidités, Cash or Bank Account is mandatory for making payment entry,Espèces ou Compte Bancaire est obligatoire pour réaliser une écriture de paiement, Cashier Closing,Fermeture de la caisse, -Category,Catégorie, -Category Name,Nom de la Catégorie, Caution,Mise en garde, Central Tax,Taxe centrale, Certification,Certification, @@ -488,32 +459,22 @@ Child Task exists for this Task. You can not delete this Task.,Une tâche enfant Child nodes can be only created under 'Group' type nodes,Les noeuds enfants peuvent être créés uniquement dans les nœuds de type 'Groupe', Child warehouse exists for this warehouse. You can not delete this warehouse.,Un entrepôt enfant existe pour cet entrepôt. Vous ne pouvez pas supprimer cet entrepôt., Circular Reference Error,Erreur de référence circulaire, -City,Ville, -City/Town,Ville, Clay,Argile, -Clear filters,Effacer les filtres, Clear values,Des valeurs claires, Clearance Date,Date de Compensation, Clearance Date not mentioned,Date de Compensation non indiquée, Clearance Date updated,Date de Compensation mise à jour, -Client,Client, -Client ID,ID Client, -Client Secret,Secret Client, Clinical Procedure,Procédure clinique, Clinical Procedure Template,Modèle de procédure clinique, Close Balance Sheet and book Profit or Loss.,Clôturer Bilan et Compte de Résultats., Close Loan,Prêt proche, Close the POS,Clôturer le point de vente, -Closed,Fermé, Closed order cannot be cancelled. Unclose to cancel.,Les commandes fermées ne peuvent être annulées. Réouvrir pour annuler., Closing (Cr),Fermeture (Cr), Closing (Dr),Fermeture (Dr), Closing (Opening + Total),Fermeture (ouverture + total), Closing Account {0} must be of type Liability / Equity,Le Compte Clôturé {0} doit être de type Passif / Capitaux Propres, Closing Balance,Solde de clôture, -Code,Code, -Collapse All,Tout réduire, -Color,Couleur, Colour,Couleur, Combined invoice portion must equal 100%,La portion combinée de la facture doit être égale à 100%, Commercial,Commercial, @@ -525,7 +486,6 @@ Community Forum,Forum de la communauté, Company (not Customer or Supplier) master.,Données de base de la Société (ni les Clients ni les Fournisseurs), Company Abbreviation,Abréviation de la Société, Company Abbreviation cannot have more than 5 characters,L'abréviation de l'entreprise ne peut pas comporter plus de 5 caractères, -Company Name,Nom de la Société, Company Name cannot be Company,Nom de la Société ne peut pas être Company, Company currencies of both the companies should match for Inter Company Transactions.,Les devises des deux sociétés doivent correspondre pour les transactions inter-sociétés., Company is manadatory for company account,La société est le maître d'œuvre du compte d'entreprise, @@ -535,7 +495,6 @@ Compensatory leave request days not in valid holidays,Les jours de la demande de Complaint,Plainte, Completion Date,Date d'Achèvement, Computer,Ordinateur, -Condition,Conditions, Configure,Configurer, Configure {0},Configurer {0}, Confirmed orders from Customers.,Commandes confirmées des clients., @@ -552,11 +511,8 @@ Consumed,Consommé, Consumed Amount,Montant Consommé, Consumed Qty,Qté Consommée, Consumer Products,Produits de Consommation, -Contact,Contact, Contact Us,Contactez nous, -Content,Contenu, Content Masters,Masters de contenu, -Content Type,Type de Contenu, Continue Configuration,Continuer la configuration, Contract,Contrat, Contract End Date must be greater than Date of Joining,La Date de Fin de Contrat doit être supérieure à la Date d'Embauche, @@ -597,7 +553,6 @@ Course Enrollment {0} does not exists,L'inscription au cours {0} n'existe pas, Course Schedule,Horaire du cours, Course: ,Cours:, Cr,Cr, -Create,Créer, Create BOM,Créer une nomenclature, Create Delivery Trip,Créer un voyage de livraison, Create Employee,Créer un employé, @@ -673,8 +628,6 @@ Current BOM and New BOM can not be same,La nomenclature actuelle et la nouvelle Current Liabilities,Dettes Actuelles, Current Qty,Qté actuelle, Current invoice {0} is missing,La facture en cours {0} est manquante, -Custom HTML,HTML Personnalisé, -Custom?,Personnaliser ?, Customer,Client, Customer Addresses And Contacts,Adresses et Contacts des Clients, Customer Contact,Contact client, @@ -699,7 +652,6 @@ Daily Reminders,Rappels quotidiens, Data Import and Export,Importer et Exporter des Données, Data Import and Settings,Importation de données et paramètres, Database of potential customers.,Base de données de clients potentiels., -Date Format,Format de Date, Date Of Retirement must be greater than Date of Joining,La Date de Départ à la Retraite doit être supérieure à Date d'Embauche, Date of Birth,Date de naissance, Date of Birth cannot be greater than today.,Date de Naissance ne peut être après la Date du Jour., @@ -707,7 +659,6 @@ Date of Commencement should be greater than Date of Incorporation,La date de dé Date of Joining,Date d'Embauche, Date of Joining must be greater than Date of Birth,La Date d'Embauche doit être après à la Date de Naissance, Date of Transaction,Date de transaction, -Day,Jour, Debit,Débit, Debit ({0}),Débit ({0}), Debit Account,Compte de débit, @@ -723,14 +674,12 @@ Default Activity Cost exists for Activity Type - {0},Un Coût d’Activité par Default BOM ({0}) must be active for this item or its template,Nomenclature par défaut ({0}) doit être actif pour ce produit ou son modèle, Default BOM for {0} not found,Nomenclature par défaut {0} introuvable, Default BOM not found for Item {0} and Project {1},La nomenclature par défaut n'a pas été trouvée pour l'Article {0} et le Projet {1}, -Default Letter Head,En-Tête de Courrier par Défaut, Default Tax Template,Modèle de Taxes par Défaut, Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,L’Unité de Mesure par Défaut pour l’Article {0} ne peut pas être modifiée directement parce que vous avez déjà fait une (des) transaction (s) avec une autre unité de mesure. Vous devez créer un nouvel article pour utiliser une UdM par défaut différente., Default Unit of Measure for Variant '{0}' must be same as in Template '{1}',L’Unité de mesure par défaut pour la variante '{0}' doit être la même que dans le Modèle '{1}', Default settings for buying transactions.,Paramètres par défaut pour les transactions d'achat., Default settings for selling transactions.,Paramètres par défaut pour les transactions de vente., Default tax templates for sales and purchase are created.,Les modèles de taxe par défaut pour les ventes et les achats sont créés., -Defaults,Valeurs Par Défaut, Defense,Défense, Define Project type.,Définir le type de projet., Define budget for a financial year.,Définir le budget pour un exercice., @@ -750,10 +699,8 @@ Delivery Note {0} is not submitted,Bon de Livraison {0} n'est pas soumis, Delivery Note {0} must not be submitted,Bon de Livraison {0} ne doit pas être soumis, Delivery Notes {0} must be cancelled before cancelling this Sales Order,Bons de Livraison {0} doivent être annulés avant d’annuler cette Commande Client, Delivery Notes {0} updated,Notes de livraison {0} mises à jour, -Delivery Status,Statut de la Livraison, Delivery Trip,Service de Livraison, Delivery warehouse required for stock item {0},Entrepôt de Livraison requis pour article du stock {0}, -Department,Département, Department Stores,Grands magasins, Depreciation,Amortissement, Depreciation Amount,Montant d'Amortissement, @@ -768,7 +715,6 @@ Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date,Ligne d'amortissement {0}: la date d'amortissement suivante ne peut pas être antérieure à la date d'achat, Designer,Designer, Detailed Reason,Raison détaillée, -Details,Détails, Details of Outward Supplies and inward supplies liable to reverse charge,Détails des livraisons sortantes et des livraisons entrantes susceptibles d'inverser la charge, Details of the operations carried out.,Détails des opérations effectuées., Diagnosis,Diagnostique, @@ -805,16 +751,11 @@ Doc Date,Date du document, Doc Name,Nom du document, Doc Type,Type de document, Docs Search,Recherche de documents, -Document Name,Nom du Document, -Document Type,Type de Document, -Domain,Domaine, -Domains,Domaines, Done,Terminé, Donor,Donneur, Donor Type information.,Informations sur le type de donneur., Donor information.,Informations sur le donneur, Download JSON,Télécharger JSON, -Draft,Brouillon, Drop Ship,Expédition Directe, Drug,Médicament, Due / Reference Date cannot be after {0},Date d’échéance / de référence ne peut pas être après le {0}, @@ -835,7 +776,6 @@ ERPNext Demo,Démo ERPNext, ERPNext Settings,Paramètres ERPNext, Earliest,Au plus tôt, Earnest Money,Arrhes, -Edit,modifier, Edit Publishing Details,Modifier les détails de publication, "Edit in full page for more options like assets, serial nos, batches etc.","Modifier en pleine page pour plus d'options comme les actifs, les numéros de série, les lots, etc.", Education,Éducation, @@ -846,13 +786,9 @@ Electrical,Électrique, Electronic Equipments,Équipements électroniques, Electronics,Électronique, Eligible ITC,CTI éligible, -Email Account,Compte Email, -Email Address,Adresse électronique, "Email Address must be unique, already exists for {0}","Adresse Email doit être unique, existe déjà pour {0}", Email Digest: ,Compte Rendu par Email :, Email Reminders will be sent to all parties with email contacts,Les rappels par emails seront envoyés à toutes les parties avec des contacts ayant une adresse email, -Email Sent,Email Envoyé, -Email Template,Modèle d'email, Email not found in default contact,Email non trouvé dans le contact par défaut, Email sent to {0},Email envoyé à {0}, Employee,Employé, @@ -866,9 +802,7 @@ Employee cannot report to himself.,L'employé ne peut pas rendre de compte à lu Employee {0} has already applied for {1} between {2} and {3} : ,L'employé {0} a déjà postulé pour {1} entre {2} et {3}:, Employee {0} of grade {1} have no default leave policy,L'employé {0} avec l'échelon {1} n'a pas de politique de congé par défaut, Enable / disable currencies.,Activer / Désactiver les devises, -Enabled,Activé, "Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart","Activation de 'Utiliser pour Panier', comme le Panier est activé et qu'il devrait y avoir au moins une Règle de Taxes pour le Panier", -End Date,Date de Fin, End Date can not be less than Start Date,La date de fin ne peut être inférieure à la date de début, End Date cannot be before Start Date.,La date de fin ne peut pas être antérieure à la date de début., End Year,Année de Fin, @@ -889,7 +823,6 @@ Enter value betweeen {0} and {1},Entrez une valeur entre {0} et {1}, Entertainment & Leisure,Divertissement et Loisir, Entertainment Expenses,Charges de Représentation, Equity,Capitaux Propres, -Error Log,Journal des Erreurs, Error evaluating the criteria formula,Erreur lors de l'évaluation de la formule du critère, Error in formula or condition: {0},Erreur dans la formule ou dans la condition : {0}, Error: Not a valid id?,Erreur : Pas un identifiant valide ?, @@ -901,7 +834,6 @@ Exchange Rate must be same as {0} {1} ({2}),Taux de Change doit être le même q Excise Invoice,Facture d'Accise, Execution,Exécution, Executive Search,Recrutement de Cadres, -Expand All,Développer Tout, Expected Delivery Date,Date de livraison prévue, Expected Delivery Date should be after Sales Order Date,La Date de Livraison Prévue doit être après la Date indiquée sur la Commande Client, Expected End Date,Date de fin prévue, @@ -924,30 +856,22 @@ Explore,Explorer, Export E-Invoices,Exporter des factures électroniques, Extra Large,Extra large, Extra Small,Très Petit, -Fail,Échec, -Failed,Échoué, Failed to create website,Échec de la création du site Web, Failed to install presets,Échec de l'installation des préréglages, Failed to login,Échec de la connexion, Failed to setup company,Échec de la configuration de la société, Failed to setup defaults,Échec de la configuration par défaut, Failed to setup post company fixtures,Échec de la configuration des éléments liés la société, -Fax,Fax, Fee,Frais, Fee Created,Honoraires Créés, Fee Creation Failed,La création des honoraires a échoué, Fee Creation Pending,Création d'honoraires en attente, Fee Records Created - {0},Archive d'Honoraires Créée - {0}, -Feedback,Retour d’Expérience, Fees,Honoraires, -Female,Féminin, Fetch Data,Récupérer des données, Fetch Subscription Updates,Vérifier les mises à jour des abonnements, Fetch exploded BOM (including sub-assemblies),Récupérer la nomenclature éclatée (y compris les sous-ensembles), Fetching records......,Récupération des enregistrements ......, -Field Name,Nom du Champ, -Fieldname,Nom du Champ, -Fields,Champ, "Filter Fields Row #{0}: Fieldname {1} must be of type ""Link"" or ""Table MultiSelect""",Filtrer les champs Ligne # {0}: le nom de champ {1} doit être de type "Lien" ou "Table MultiSelect", Filter Total Zero Qty,Filtrer les totaux pour les qtés égales à zéro, Finance Book,Livre comptable, @@ -961,7 +885,6 @@ Finished Good Item Code,Code d'article fini, Finished Goods,Produits finis, Finished Item {0} must be entered for Manufacture type entry,Le Produit Fini {0} doit être saisi pour une écriture de type Production, Finished product quantity {0} and For Quantity {1} cannot be different,La quantité de produit fini {0} et Pour la quantité {1} ne peut pas être différente, -First Name,Prénom, "Fiscal Regime is mandatory, kindly set the fiscal regime in the company {0}","Le régime fiscal est obligatoire, veuillez définir le régime fiscal de l'entreprise {0}", Fiscal Year,Exercice fiscal, Fiscal Year End Date should be one year after Fiscal Year Start Date,La date de fin d'exercice doit être un an après la date de début d'exercice, @@ -994,9 +917,6 @@ For row {0}: Enter Planned Qty,Pour la ligne {0}: entrez la quantité planifiée Forum Activity,Activité du forum, Free item code is not selected,Le code d'article gratuit n'est pas sélectionné, Freight and Forwarding Charges,Frais de Fret et d'Expédition, -Frequency,Fréquence, -Friday,Vendredi, -From,À partir de, From Address 1,Ligne d'addresse 1 (Origine), From Address 2,Ligne d'addresse 2 (Origine), From Currency and To Currency cannot be same,La Devise de Base et la Devise de Cotation ne peuvent pas identiques, @@ -1021,18 +941,15 @@ From and To dates required,Les date Du et Au sont requises, From value must be less than to value in row {0},De la valeur doit être inférieure à la valeur de la ligne {0}, From {0} | {1} {2},Du {0} | {1} {2}, Fulfillment,Livraison, -Full Name,Nom Complet, Fully Depreciated,Complètement Déprécié, Furnitures and Fixtures,Meubles et Accessoires, "Further accounts can be made under Groups, but entries can be made against non-Groups","D'autres comptes individuels peuvent être créés dans les groupes, mais les écritures ne peuvent être faites que sur les comptes individuels", Further cost centers can be made under Groups but entries can be made against non-Groups,"D'autres centres de coûts peuvent être créés dans des Groupes, mais des écritures ne peuvent être faites que sur des centres de coûts individuels.", -Further nodes can be only created under 'Group' type nodes,D'autres nœuds peuvent être créés uniquement sous les nœuds de type 'Groupe', GSTIN,GSTIN, GSTR3B-Form,GSTR3B-Form, Gain/Loss on Asset Disposal,Gain/Perte sur Cessions des Immobilisations, Gantt Chart,Diagramme de Gantt, Gantt chart of all tasks.,Diagramme de Gantt de toutes les tâches., -Gender,Sexe, General,Général, General Ledger,Grand Livre, Generate Material Requests (MRP) and Work Orders.,Générer des demandes de matériel (MRP) et des ordres de travail., @@ -1050,7 +967,6 @@ Get Updates,Obtenir les mises à jour, Get customers from,Obtenir les clients de, Get from Patient Encounter,Obtenez de la rencontre du patient, Getting Started,Commencer, -GitHub Sync ID,GitHub Sync ID, Global settings for all manufacturing processes.,Paramètres globaux pour tous les processus de production., Go to the Desktop and start using ERPNext,Accédez au bureau et commencez à utiliser ERPNext, GoCardless SEPA Mandate,Mandat SEPA GoCardless, @@ -1090,8 +1006,6 @@ Guardian2 Name,Nom du Tuteur 2, HR Manager,Responsable RH, HSN,HSN, HSN/SAC,HSN / SAC, -Half Yearly,Semestriel, -Half-Yearly,Semestriel, Hardware,Matériel, Head of Marketing and Sales,Responsable du Marketing et des Ventes, Health Care,Soins de santé, @@ -1106,7 +1020,6 @@ Healthcare Service Unit Type,Type d'unité de service de soins de santé, Healthcare Services,Services de santé, Healthcare Settings,Paramètres de santé, Help Results for,Aide Résultats pour, -High,Haut, High Sensitivity,Haute sensibilité, Hold,Mettre en attente, Hold Invoice,Facture en attente, @@ -1114,15 +1027,12 @@ Holiday,Vacances, Holiday List,Liste de vacances, Hotel Rooms of type {0} are unavailable on {1},Les chambres d'hôtel de type {0} sont indisponibles le {1}, Hotels,Hôtels, -Hourly,Horaire, Hours,Heures, How Pricing Rule is applied?,Comment la Règle de Prix doit-elle être appliquée ?, Hub Category,Catégorie du Hub, -Hub Sync ID,Hub Sync ID, Human Resource,Ressource humaine, Human Resources,Ressources humaines, IGST Amount,IGST Montant, -IP Address,Adresse IP, ITC Available (whether in full op part),CIT Disponible (que ce soit en partie op), ITC Reversed,CTI inversé, Identifying Decision Makers,Identifier les décideurs, @@ -1133,11 +1043,7 @@ Identifying Decision Makers,Identifier les décideurs, "If unlimited expiry for the Loyalty Points, keep the Expiry Duration empty or 0.","Si vous souhaitez ne pas mettre de date d'expiration pour les points de fidélité, laissez la durée d'expiration vide ou mettez 0.", "If you have any questions, please get back to us.","Si vous avez des questions, veuillez revenir vers nous.", Ignore Existing Ordered Qty,Ignorer la quantité commandée existante, -Image,Image, -Image View,Voir l'Image, -Import Data,Importer des données, Import Day Book Data,Données du journal d'importation, -Import Log,Journal d'import, Import Master Data,Importer des données de base, Import in Bulk,Importer en Masse, Import of goods,Importation de marchandises, @@ -1151,7 +1057,6 @@ In Stock Qty,Qté En Stock, In Stock: ,En Stock :, In Value,En valeur, "In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent","Dans le cas d'un programme à plusieurs échelons, les clients seront automatiquement affectés au niveau approprié en fonction de leurs dépenses", -Inactive,Inactif, Incentives,Incitations, Include Default Book Entries,Inclure les entrées de livre par défaut, Include Exploded Items,Inclure les articles éclatés, @@ -1185,7 +1090,6 @@ Integrated Tax,Taxe intégrée, Inter-State Supplies,Fournitures inter-Etats, Internet Publishing,Publication Internet, Intra-State Supplies,Fournitures intra-étatiques, -Introduction,Introduction, Invalid Attribute,Attribut invalide, Invalid Blanket Order for the selected Customer and Item,Commande avec limites non valide pour le client et l'article sélectionnés, Invalid Company for Inter Company Transaction.,Société non valide pour une transaction inter-sociétés., @@ -1217,8 +1121,6 @@ Invoices,Factures, Invoices for Costumers.,Factures pour les clients., Inward supplies from ISD,Approvisionnement entrant de la DSI, Inward supplies liable to reverse charge (other than 1 & 2 above),Approvisionnements entrants susceptibles d’être dédouanés (autres que 1 et 2 ci-dessus), -Is Active,Est Active, -Is Default,Est Défaut, Is Existing Asset,Est Actif Existant, Is Frozen,Est gelé, Is Group,Est un Groupe, @@ -1288,7 +1190,6 @@ Join,Joindre, Journal Entries {0} are un-linked,Les Écritures de Journal {0} ne sont pas liées, Journal Entry,Écriture de Journal, Journal Entry {0} does not have account {1} or already matched against other voucher,L’Écriture de Journal {0} n'a pas le compte {1} ou est déjà réconciliée avec une autre pièce justificative, -Kanban Board,Tableau Kanban, Key Reports,Rapports clés, LMS Activity,Activité LMS, Lab Test,Test de laboratoire, @@ -1299,12 +1200,10 @@ Lab Test UOM,UdM de test de laboratoire, Lab Tests and Vital Signs,Tests de laboratoire et signes vitaux, Lab result datetime cannot be before testing datetime,La date et l'heure du résultat de laboratoire ne peuvent pas être avant la date et l'heure du test, Lab testing datetime cannot be before collection datetime,La date et l'heure du test de laboratoire ne peuvent pas être avant la date et l'heure de collecte, -Label,Étiquette, Laboratory,Laboratoire, Large,Grand, Last Communication,Dernière communication, Last Communication Date,Date de la Dernière Communication, -Last Name,Nom de Famille, Last Order Amount,Montant de la Dernière Commande, Last Order Date,Date de la dernière commande, Last Purchase Price,Dernier prix d'achat, @@ -1326,9 +1225,7 @@ Leaves must be allocated in multiples of 0.5,"Les Congés doivent être alloués Ledger,Livre, Legal,Juridique, Legal Expenses,Frais juridiques, -Letter Head,En-Tête, Letter Heads for print templates.,En-Têtes pour les modèles d'impression., -Level,Niveau, Liability,Passif, Limit Crossed,Limite Dépassée, Link to Material Request,Lien vers la demande de matériel, @@ -1343,7 +1240,6 @@ Local,Locale, Logs for maintaining sms delivery status,Journaux pour maintenir le statut de livraison des sms, Lost,Perdu, Lost Reasons,Raisons perdues, -Low,Bas, Low Sensitivity,Faible sensibilité, Lower Income,Revenu bas, Loyalty Amount,Montant de fidélité, @@ -1355,13 +1251,11 @@ Loyalty Program,Programme de fidélité, Main,Principal, Maintenance,Entretien, Maintenance Log,Journal de maintenance, -Maintenance Manager,Responsable de Maintenance, Maintenance Schedule,Échéancier d'Entretien, Maintenance Schedule is not generated for all the items. Please click on 'Generate Schedule',L'Échéancier d'Entretien n'est pas créé pour tous les articles. Veuillez clicker sur 'Créer un Échéancier', Maintenance Schedule {0} exists against {1},Un Calendrier de Maintenance {0} existe pour {1}, Maintenance Schedule {0} must be cancelled before cancelling this Sales Order,L'Échéancier d'Entretien {0} doit être annulé avant d'annuler cette Commande Client, Maintenance Status has to be Cancelled or Completed to Submit,Le statut de maintenance doit être annulé ou complété pour pouvoir être envoyé, -Maintenance User,Maintenance Utilisateur, Maintenance Visit,Visite d'Entretien, Maintenance Visit {0} must be cancelled before cancelling this Sales Order,La Visite d'Entretien {0} doit être annulée avant d'annuler cette Commande Client, Maintenance start date can not be before delivery date for Serial No {0},La date de début d'entretien ne peut pas être antérieure à la date de livraison pour le N° de Série {0}, @@ -1369,7 +1263,6 @@ Make,Faire, Make Payment,Faire un Paiement, Make project from a template.,Faire un projet à partir d'un modèle., Making Stock Entries,Faire des Écritures de Stock, -Male,Masculin, Manage Customer Group Tree.,Gérer l'Arborescence des Groupes de Clients., Manage Sales Partners.,Gérer les Partenaires Commerciaux., Manage Sales Person Tree.,Gérer l'Arborescence des Vendeurs., @@ -1379,7 +1272,6 @@ Management,Gestion, Manager,Directeur, Managing Projects,Gestion de Projets, Managing Subcontracting,Gestion de la Sous-traitance, -Mandatory,Obligatoire, Mandatory field - Academic Year,Champ Obligatoire - Année Académique, Mandatory field - Get Students From,Champ Obligatoire - Obtenir des étudiants de, Mandatory field - Program,Champ obligatoire - Programme, @@ -1388,8 +1280,6 @@ Manufacturer,Fabricant, Manufacturer Part Number,Numéro de Pièce du Fabricant, Manufacturing,Production, Manufacturing Quantity is mandatory,Quantité de production obligatoire, -Mapping,Mapping, -Mapping Type,Type de Mapping, Mark Absent,Marquer Absent, Mark Half Day,Marquer Demi-Journée, Mark Present,Marquer Présent, @@ -1424,7 +1314,6 @@ Medical Code,Code médical, Medical Code Standard,Standard du code médical, Medical Department,Département médical, Medical Record,Dossier médical, -Medium,Moyen, Member Activity,Activité des membres, Member ID,ID du membre, Member Name,Nom de membre, @@ -1439,12 +1328,8 @@ Merge,Fusionner, Merge Account,Fusionner le compte, Merge with Existing Account,Fusionner avec un compte existant, "Merging is only possible if following properties are same in both records. Is Group, Root Type, Company","La combinaison est possible seulement si les propriétés suivantes sont les mêmes dans les deux dossiers. Est Groupe, Type de Racine, Société", -Message Examples,Exemples de Messages, Message Sent,Message envoyé, -Method,Méthode, Middle Income,Revenu Intermédiaire, -Middle Name,Deuxième Nom, -Middle Name (Optional),Deuxième Prénom (Optionnel), Min Amt can not be greater than Max Amt,Min Amt ne peut pas être supérieur à Max Amt, Min Qty can not be greater than Max Qty,Qté Min ne peut pas être supérieure à Qté Max, Minimum Lead Age (Days),Âge Minimum du lead (Jours), @@ -1458,14 +1343,9 @@ Mode of Transport,Mode de transport, Mode of Transportation,Mode de transport, Model,Modèle, Moderate Sensitivity,Sensibilité modérée, -Monday,Lundi, -Monthly,Mensuel, Monthly Distribution,Répartition Mensuelle, -More,Plus, -More Information,Informations Complémentaires, More...,Plus..., Motion Picture & Video,Cinéma & Vidéo, -Move,mouvement, Move Item,Déplacer l'Article, Multi Currency,Multi-devise, Multiple Item prices.,Plusieurs Prix d'Articles., @@ -1497,7 +1377,6 @@ Net ITC Available(A) - (B),CTI net disponible (A) - (B), Net Profit,Bénéfice net, Net Total,Total net, New Account Name,Nouveau Nom de Compte, -New Address,Nouvelle adresse, New BOM,Nouvelle nomenclature, New Batch ID (Optional),Nouveau Numéro de Lot (Optionnel), New Batch Qty,Nouvelle Qté de Lot, @@ -1517,13 +1396,11 @@ New credit limit is less than current outstanding amount for the customer. Credi New task,Nouvelle tâche, New {0} pricing rules are created,De nouvelles règles de tarification {0} sont créées., Newspaper Publishers,Éditeurs de journaux, -Next,Suivant, Next Contact By cannot be same as the Lead Email Address,Prochain Contact Par ne peut être identique à l’Adresse Email du Lead, Next Contact Date cannot be in the past,La Date de Prochain Contact ne peut pas être dans le passé, Next Steps,Prochaines étapes, No Action,Pas d'action, No Customers yet!,Pas encore de clients!, -No Data,Aucune Donnée, No Delivery Note selected for Customer {},Aucun bon de livraison sélectionné pour le client {}, No Item with Barcode {0},Aucun Article avec le Code Barre {0}, No Item with Serial No {0},Aucun Article avec le N° de Série {0}, @@ -1565,15 +1442,12 @@ Non Profit,À But Non Lucratif, Non Profit (beta),Association (bêta), Non-GST outward supplies,Fournitures sortantes non liées à la TPS, Non-Group to Group,Non-Groupe à Groupe, -None,Aucun, None of the items have any change in quantity or value.,Aucun des Articles n’a de changement en quantité ou en valeur., Nos,N°, Not Available,Indisponible, Not Marked,Non marqué, Not Paid and Not Delivered,Non payé et non livré, -Not Permitted,Non Autorisé, Not Started,Non Commencé, -Not active,Non actif, Not allow to set alternative item for the item {0},Ne permet pas de définir un autre article pour l'article {0}, Not allowed to update stock transactions older than {0},Non autorisé à mettre à jour les transactions du stock antérieures à {0}, Not authorized to edit frozen Account {0},Vous n'êtes pas autorisé à modifier le compte gelé {0}, @@ -1592,7 +1466,6 @@ Notes,Remarques, Nothing is included in gross,Rien n'est inclus dans le brut, Nothing more to show.,Rien de plus à montrer., Notify Customers via Email,Avertir les clients par courrier électronique, -Number,Nombre, Number of Depreciations Booked cannot be greater than Total Number of Depreciations,Nombre d’Amortissements Comptabilisés ne peut pas être supérieur à Nombre Total d'Amortissements, Number of Interaction,Nombre d'Interactions, Number of Order,Nombre de Commandes, @@ -1634,7 +1507,6 @@ Opening Stock,Stock d'Ouverture, Opening Stock Balance,Solde d'Ouverture des Stocks, Opening Value,Valeur d'Ouverture, Opening {0} Invoice created,Ouverture {0} Facture créée, -Operation,Opération, Operation Time must be greater than 0 for Operation {0},Temps de l'Opération doit être supérieur à 0 pour l'Opération {0}, "Operation {0} longer than any available working hours in workstation {1}, break down the operation into multiple operations","Opération {0} plus longue que toute heure de travail disponible dans la station de travail {1}, veuillez séparer l'opération en plusieurs opérations", Operations,Opérations, @@ -1647,7 +1519,6 @@ Opportunity,Opportunité, Opportunity Amount,Montant de l'opportunité, "Optional. Sets company's default currency, if not specified.","Optionnel. Défini la devise par défaut de l'entreprise, si non spécifié.", Optional. This setting will be used to filter in various transactions.,Facultatif. Ce paramètre sera utilisé pour filtrer différentes transactions., -Options,Options, Order Count,Compte de Commandes, Order Entry,Saisie de Commande, Order Value,Valeur de la commande, @@ -1660,9 +1531,9 @@ Orders,Commandes, Orders released for production.,Commandes validées pour la production., Organization,Organisation, Organization Name,Nom de l'Organisation, -Other,Autre, Other Reports,Autres rapports, "Other outward supplies(Nil rated,Exempted)","Autres livraisons sortantes (cotations nulles, exemptées)", +Others,Autres, Out Qty,Qté Sortante, Out Value,Valeur Sortante, Out of Order,Hors service, @@ -1676,7 +1547,6 @@ Outward taxable supplies(zero rated),Fournitures taxables à la sortie (détaxé Overdue,En retard, Overlap in scoring between {0} and {1},Chevauchement dans la notation entre {0} et {1}, Overlapping conditions found between:,Conditions qui coincident touvées entre :, -Owner,Responsable, PAN,Numéro de compte permanent (PAN), POS,PDV, POS Profile,Profil PDV, @@ -1691,7 +1561,6 @@ Paid Amount,Montant payé, Paid Amount cannot be greater than total negative outstanding amount {0},Le Montant Payé ne peut pas être supérieur au montant impayé restant {0}, Paid amount + Write Off Amount can not be greater than Grand Total,Le Montant Payé + Montant Repris ne peut pas être supérieur au Total Général, Paid and Not Delivered,Payé et non livré, -Parameter,Paramètre, Parent Item {0} must not be a Stock Item,L'Article Parent {0} ne doit pas être un Élément de Stock, Parents Teacher Meeting Attendance,Participation à la réunion parents-professeurs, Partially Depreciated,Partiellement déprécié, @@ -1751,7 +1620,6 @@ Pending activities for today,Activités en Attente pour aujourd'hui, Pension Funds,Fonds de Pension, Percentage Allocation should be equal to 100%,Pourcentage d'Allocation doit être égale à 100 %, Perception Analysis,Analyse de perception, -Period,Période, Period Closing Entry,Écriture de Clôture de la Période, Period Closing Voucher,Bon de Clôture de la Période, Periodicity,Périodicité, @@ -1778,7 +1646,6 @@ Please create purchase receipt or purchase invoice for the item {0},Veuillez cr Please define grade for Threshold 0%,Veuillez définir une note pour le Seuil 0%, Please enable Applicable on Booking Actual Expenses,Veuillez activer l'option : Applicable sur la base de l'enregistrement des dépenses réelles, Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses,Veuillez activer les options : Applicable sur la base des bons de commande d'achat et Applicable sur la base des bons de commande d'achat, -Please enable pop-ups,Veuillez autoriser les pop-ups, Please enter 'Is Subcontracted' as Yes or No,Veuillez entrer Oui ou Non pour 'Est sous-traitée', Please enter API Consumer Key,"Veuillez entrer la clé ""API Consumer Key""", Please enter API Consumer Secret,"Veuillez entrer la clé ""API Consumer Secret""", @@ -1834,7 +1701,6 @@ Please select BOM for Item in Row {0},Veuillez sélectionnez une nomenclature po Please select BOM in BOM field for Item {0},Veuillez sélectionner une nomenclature dans le champ nomenclature pour l’Article {0}, Please select Category first,Veuillez d’abord sélectionner une Catégorie, Please select Charge Type first,Veuillez d’abord sélectionner le Type de Facturation, -Please select Company,Veuillez sélectionner une Société, Please select Company and Posting Date to getting entries,Veuillez sélectionner la société et la date de comptabilisation pour obtenir les écritures, Please select Company first,Veuillez d’abord sélectionner une Société, Please select Completion Date for Completed Asset Maintenance Log,Veuillez sélectionner la date d'achèvement pour le journal de maintenance des actifs terminé, @@ -1875,7 +1741,6 @@ Please select the Multiple Tier Program type for more than one collection rules. Please select the assessment group other than 'All Assessment Groups',Sélectionnez un groupe d'évaluation autre que «Tous les Groupes d'Évaluation», Please select the document type first,Veuillez d’abord sélectionner le type de document, Please select weekly off day,Veuillez sélectionnez les jours de congé hebdomadaires, -Please select {0},Veuillez sélectionner {0}, Please select {0} first,Veuillez d’abord sélectionner {0}, Please set 'Apply Additional Discount On',Veuillez définir ‘Appliquer Réduction Supplémentaire Sur ‘, Please set 'Asset Depreciation Cost Center' in Company {0},Veuillez définir 'Centre de Coûts des Amortissements d’Actifs’ de la Société {0}, @@ -1941,7 +1806,6 @@ Prescription Dosage,Dosage de la prescription, Prescription Duration,Durée de la prescription, Prescriptions,Les prescriptions, Prev,Précédent, -Preview,Aperçu, Previous Financial Year is not closed,L’Exercice Financier Précédent n’est pas fermé, Price,Prix, Price List,Liste de prix, @@ -1959,13 +1823,11 @@ Pricing Rule {0} is updated,La règle de tarification {0} est mise à jour, Pricing Rules are further filtered based on quantity.,Les Règles de Tarification sont d'avantage filtrés en fonction de la quantité., Primary Address Details,Détails de l'adresse principale, Primary Contact Details,Détails du contact principal, -Print Format,Format d'Impression, Print IRS 1099 Forms,Imprimer les formulaires IRS 1099, Print Report Card,Imprimer le rapport, Print Settings,Paramètres d'impression, Print and Stationery,Impression et Papeterie, Print settings updated in respective print format,Paramètres d'impression mis à jour avec le format d'impression indiqué, -Print taxes with zero amount,Impression de taxes avec un montant nul, Printing and Branding,Impression et Marque, Private Equity,Capital Investissement, Procedure,Procédure, @@ -2012,15 +1874,12 @@ Prospecting,Prospection, Provisional Profit / Loss (Credit),Gain / Perte (Crédit) Provisoire, Publications,Des publications, Publish Items on Website,Publier les Articles sur le Site Web, -Published,Publié, Publishing,Édition, Purchase,achat, Purchase Amount,Montant de l'Achat, Purchase Date,Date d'Achat, Purchase Invoice,Facture d’Achat, Purchase Invoice {0} is already submitted,La Facture d’Achat {0} est déjà soumise, -Purchase Manager,Responsable des Achats, -Purchase Master Manager,Responsable des Données d’Achats, Purchase Order,Commande d'Achat, Purchase Order Amount,Montant de la Commande d'Achat, Purchase Order Amount(Company Currency),Montant de la Commande d'Achat (devise de la société), @@ -2035,7 +1894,6 @@ Purchase Price List,Liste des Prix d'Achat, Purchase Receipt,Reçu d’Achat, Purchase Receipt {0} is not submitted,Le Reçu d’Achat {0} n'est pas soumis, Purchase Tax Template,Modèle de Taxes pour les Achats, -Purchase User,Utilisateur Acheteur, Purchase orders help you plan and follow up on your purchases,Les Bons de Commande vous aider à planifier et à assurer le suivi de vos achats, Purchasing,Achat, Purpose must be one of {0},L'Objet doit être parmi {0}, @@ -2065,7 +1923,6 @@ Quantity to Make,Quantité à faire, Quantity to Manufacture must be greater than 0.,La quantité à produire doit être supérieur à 0., Quantity to Produce,Quantité à produire, Quantity to Produce can not be less than Zero,La quantité à produire ne peut être inférieure à zéro, -Query Options,Options de Requête, Queued for replacing the BOM. It may take a few minutes.,En file d'attente pour remplacer la nomenclature. Cela peut prendre quelques minutes., Queued for updating latest price in all Bill of Materials. It may take a few minutes.,Mise à jour des prix les plus récents dans toutes les nomenclatures en file d'attente. Cela peut prendre quelques minutes., Quick Journal Entry,Écriture Rapide dans le Journal, @@ -2080,10 +1937,8 @@ Quotations received from Suppliers.,Devis reçus des Fournisseurs., Quotations: ,Devis :, Quotes to Leads or Customers.,Devis de Lead ou Clients., RFQs are not allowed for {0} due to a scorecard standing of {1},Les Appels d'Offres ne sont pas autorisés pour {0} en raison d'une note de {1} sur la fiche d'évaluation, -Range,Plage, Rate,Prix, Rate:,Prix:, -Rating,Évaluation, Raw Material,Matières Premières, Raw Materials,Matières premières, Raw Materials cannot be blank.,Matières Premières ne peuvent pas être vides., @@ -2099,35 +1954,25 @@ Receipt,Reçu, Receipt document must be submitted,Le reçu doit être soumis, Receivable,Créance, Receivable Account,Compte Débiteur, -Received,Reçu, Received On,Reçu le, Received Quantity,Quantité reçue, Received Stock Entries,Entrées de stock reçues, Receiver List is empty. Please create Receiver List,La Liste de Destinataires est vide. Veuillez créer une Liste de Destinataires, -Recipients,Destinataires, Reconcile,Réconcilier, "Record of all communications of type email, phone, chat, visit, etc.","Enregistrement de toutes les communications de type email, téléphone, chat, visite, etc.", Records,Dossiers, -Redirect URL,URL de Redirection, Ref,Ref, Ref Date,Date de Réf., -Reference,Référence, Reference #{0} dated {1},Référence #{0} datée du {1}, -Reference Date,Date de Référence, Reference Doctype must be one of {0},Doctype de la Référence doit être parmi {0}, -Reference Document,Document de Référence, -Reference Document Type,Type du document de référence, Reference No & Reference Date is required for {0},N° et Date de Référence sont nécessaires pour {0}, Reference No and Reference Date is mandatory for Bank transaction,Le N° de Référence et la Date de Référence sont nécessaires pour une Transaction Bancaire, Reference No is mandatory if you entered Reference Date,N° de Référence obligatoire si vous avez entré une date, Reference No.,Numéro de référence, Reference Number,Numéro de réference, -Reference Type,Type de référence, "Reference: {0}, Item Code: {1} and Customer: {2}","Référence: {0}, Code de l'article: {1} et Client: {2}", References,Références, -Refresh Token,Jeton de Rafraîchissement, Register,registre, -Rejected,Rejeté, Related,en relation, Relation with Guardian1,Relation avec Tuteur1, Relation with Guardian2,Relation avec Tuteur2, @@ -2139,17 +1984,12 @@ Remarks,Remarques, Reminder to update GSTIN Sent,Rappel pour mettre à jour GSTIN envoyé, Remove item if charges is not applicable to that item,Retirer l'article si les charges ne lui sont pas applicables, Removed items with no change in quantity or value.,Les articles avec aucune modification de quantité ou de valeur ont étés retirés., -Reopen,Ré-ouvrir, Reorder Level,Niveau de réapprovisionnement, Reorder Qty,Qté de Réapprovisionnement, Repeat Customer Revenue,Revenus de Clients Récurrents, Repeat Customers,Clients Récurrents, Replace BOM and update latest price in all BOMs,Remplacer la nomenclature et actualiser les prix les plus récents dans toutes les nomenclatures, -Replied,Répondu, -Report,Rapport, -Report Type,Type de Rapport, Report Type is mandatory,Le Type de Rapport est nécessaire, -Reports,Rapports, Reqd By Date,Requis par date, Reqd Qty,Qté obligatoire, Request for Quotation,Appel d'Offre, @@ -2208,7 +2048,6 @@ Root cannot be edited.,La racine ne peut pas être modifiée., Root cannot have a parent cost center,Racine ne peut pas avoir un centre de coûts parent, Round Off,Arrondi, Rounded Total,Total arrondi, -Route,Route, Row # {0}: ,Ligne # {0} :, Row # {0}: Batch No must be same as {1} {2},Ligne # {0} : Le N° de Lot doit être le même que {1} {2}, Row # {0}: Cannot return more than {1} for Item {2},Ligne # {0} : Vous ne pouvez pas retourner plus de {1} pour l’Article {2}, @@ -2297,8 +2136,6 @@ Sales Funnel,Entonnoir de vente, Sales Invoice,Facture de vente, Sales Invoice {0} has already been submitted,La Facture Vente {0} a déjà été transmise, Sales Invoice {0} must be cancelled before cancelling this Sales Order,Facture de Vente {0} doit être annulée avant l'annulation de cette Commande Client, -Sales Manager,Responsable des Ventes, -Sales Master Manager,Directeur des Ventes, Sales Order,Commande client, Sales Order Item,Article de la Commande Client, Sales Order required for Item {0},Commande Client requise pour l'Article {0}, @@ -2314,11 +2151,9 @@ Sales Return,Retour de Ventes, Sales Summary,Récapitulatif des ventes, Sales Tax Template,Modèle de la Taxe de Vente, Sales Team,Équipe des Ventes, -Sales User,Chargé de Ventes, Sales and Returns,Ventes et retours, Sales campaigns.,Campagnes de vente., Sales orders are not available for production,Aucune commande client n'est disponible pour la production, -Salutation,Salutations, Same Company is entered more than once,La même Société a été entrée plus d'une fois, Same item cannot be entered multiple times.,Le même article ne peut pas être entré plusieurs fois., Same supplier has been entered multiple times,Le même fournisseur a été saisi plusieurs fois, @@ -2326,7 +2161,6 @@ Sample Collection,Collecte d'Échantillons, Sample quantity {0} cannot be more than received quantity {1},La quantité d'échantillon {0} ne peut pas dépasser la quantité reçue {1}, Sanctioned,Sanctionné, Sand,Le sable, -Saturday,Samedi, Saving {0},Enregistrement {0}, Scan Barcode,Scan Code Barre, Schedule,Calendrier, @@ -2334,18 +2168,15 @@ Schedule Admission,Calendrier d'admission, Schedule Course,Cours Calendrier, Schedule Date,Date du Calendrier, Schedule Discharge,Décharge horaire, -Scheduled,Prévu, Scheduled Upto,Programmé jusqu'à, "Schedules for {0} overlaps, do you want to proceed after skiping overlaped slots ?","Les plannings pour {0} se chevauchent, voulez-vous continuer sans prendre en compte les créneaux qui se chevauchent ?", Score cannot be greater than Maximum Score,Score ne peut pas être supérieure à Score maximum, Scorecards,Fiches d'Évaluation, Scrapped,Mis au rebut, -Search,Rechercher, Search Results,Résultats de la recherche, Search Sub Assemblies,Rechercher les Sous-Ensembles, "Search by item code, serial number, batch no or barcode","Recherche par code article, numéro de série, numéro de lot ou code-barres", "Seasonality for setting budgets, targets etc.","Saisonnalité de l'établissement des budgets, des objectifs, etc.", -Secret Key,Clef Secrète, Secretary,secrétaire, Section Code,Code de section, Secured Loans,Prêts garantis, @@ -2355,7 +2186,6 @@ See All Articles,Voir tous les articles, See all open tickets,Voir tous les tickets ouverts, See past orders,Voir les commandes passées, See past quotations,Voir les citations passées, -Select,Sélectionner, Select Alternate Item,Sélectionnez un autre élément, Select Attribute Values,Sélectionner les valeurs d'attribut, Select BOM,Sélectionner une nomenclature, @@ -2369,7 +2199,6 @@ Select Company...,Sélectionner la Société ..., Select Customer,Sélectionnez un client, Select Days,Choisissez des jours, Select Default Supplier,Sélectionner le Fournisseur par Défaut, -Select DocType,Sélectionner le DocType, Select Fiscal Year...,Sélectionner Exercice ..., Select Item (optional),Sélectionnez l'Article (facultatif), Select Items based on Delivery Date,Sélectionnez les articles en fonction de la Date de Livraison, @@ -2399,11 +2228,9 @@ Selling Price List,Liste de prix de vente, Selling Rate,Prix de vente, "Selling must be checked, if Applicable For is selected as {0}","Vente doit être vérifiée, si ""Applicable pour"" est sélectionné comme {0}", Send Grant Review Email,Envoyer un email d'examen de la demande de subvention, -Send Now,Envoyer Maintenant, Send SMS,Envoyer un SMS, Send mass SMS to your contacts,Envoyer un SMS en masse à vos contacts, Sensitivity,Sensibilité, -Sent,Envoyé, Serial No and Batch,N° de Série et lot, Serial No is mandatory for Item {0},N° de Série est obligatoire pour l'Article {0}, Serial No {0} does not belong to Batch {1},Le numéro de série {0} n'appartient pas au lot {1}, @@ -2428,7 +2255,6 @@ Serialized Inventory,Inventaire Sérialisé, Series Updated,Série mise à jour, Series Updated Successfully,Mise à jour des Séries Réussie, Series is mandatory,Série est obligatoire, -Service,Service, Service Level Agreement,Contrat de niveau de service, Service Level Agreement.,Contrat de niveau de service., Service Level.,Niveau de service., @@ -2455,7 +2281,6 @@ Setting up Email Account,Configuration du Compte Email, Setting up Employees,Configuration des Employés, Setting up Taxes,Configuration des Impôts, Setting up company,Création d'entreprise, -Settings,Paramètres, "Settings for online shopping cart such as shipping rules, price list etc.","Paramètres du panier tels que les règles de livraison, liste de prix, etc.", Settings for website homepage,Paramètres de la page d'accueil du site, Settings for website product listing,Paramètres pour la liste de produits de sites Web, @@ -2481,7 +2306,6 @@ Shipping rule only applicable for Selling,Règle d'expédition applicable unique Shopify Supplier,Fournisseur Shopify, Shopping Cart,Panier, Shopping Cart Settings,Paramètres du panier, -Short Name,Nom Court, Shortage Qty,Qté de Pénurie, Show Completed,Montrer terminé, Show Cumulative Amount,Afficher le montant cumulatif, @@ -2500,7 +2324,6 @@ Silt,Limon, Single Variant,Variante unique, Single unit of an Item.,Seule unité d'un Article., "Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}","Attribution des congés de congé pour les employés suivants, car des dossiers de répartition des congés existent déjà contre eux. {0}", -Slideshow,Diaporama, Slots for {0} are not added to the schedule,Les créneaux pour {0} ne sont pas ajoutés à l'agenda, Small,Petit, Soap & Detergent,Savons & Détergents, @@ -2513,8 +2336,6 @@ Some emails are invalid,Certains emails sont invalides, Some information is missing,Certaines informations sont manquantes, Something went wrong!,Quelque chose a mal tourné !, "Sorry, Serial Nos cannot be merged","Désolé, les N° de Série ne peut pas être fusionnés", -Source,Source, -Source Name,Nom de la Source, Source Warehouse,Entrepôt source, Source and Target Location cannot be same,Les localisations source et cible ne peuvent pas être identiques, Source and target warehouse cannot be same for row {0},L'entrepôt source et destination ne peuvent être similaire dans la ligne {0}, @@ -2529,14 +2350,12 @@ Sports,Sportif, Standard Buying,Achat standard, Standard Selling,Vente standard, Standard contract terms for Sales or Purchase.,Termes contractuels standards pour Ventes ou Achats, -Start Date,Date de Début, Start Date of Agreement can't be greater than or equal to End Date.,La date de début de l'accord ne peut être supérieure ou égale à la date de fin., Start Year,Année de début, Start date should be less than end date for Item {0},La date de début doit être antérieure à la date de fin pour l'Article {0}, Start date should be less than end date for task {0},La date de début doit être inférieure à la date de fin de la tâche {0}, Start day is greater than end day in task '{0}',La date de début est supérieure à la date de fin dans la tâche '{0}', Start on,Démarrer, -State,Etat, State/UT Tax,Taxe Etat / UT, Statement of Account,Relevé de compte, Status must be one of {0},Le statut doit être l'un des {0}, @@ -2569,8 +2388,6 @@ Stock cannot be updated against Delivery Note {0},Stock ne peut pas être mis à Stock cannot be updated against Purchase Receipt {0},Stock ne peut pas être mis à jour pour le Reçu d'Achat {0}, Stock cannot exist for Item {0} since has variants,Stock ne peut pas exister pour l'Article {0} puisqu'il a des variantes, Stock transactions before {0} are frozen,Les transactions du stock avant {0} sont gelées, -Stop,Arrêter, -Stopped,Arrêté, "Stopped Work Order cannot be cancelled, Unstop it first to cancel","Un ordre de fabrication arrêté ne peut être annulé, Re-démarrez le pour pouvoir l'annuler", Stores,Magasins, Student,Étudiant, @@ -2601,8 +2418,6 @@ Sub Assemblies,Sous-Ensembles, Sub Type,Sous type, Sub-contracting,Sous-traitant, Subcontract,Sous-traiter, -Subject,Sujet, -Submit,Valider, Submit this Work Order for further processing.,Valider cet ordre de fabrication pour continuer son traitement., Subscription,Abonnement, Subscription Management,Gestion des abonnements, @@ -2615,10 +2430,8 @@ Successfully created payment entries,Ecritures de paiement créées avec succès Successfully deleted all transactions related to this company!,Suppression de toutes les transactions liées à cette société avec succès !, Sum of Scores of Assessment Criteria needs to be {0}.,Somme des Scores de Critères d'Évaluation doit être {0}., Sum of points for all goals should be 100. It is {0},Somme des points pour tous les objectifs devraient être 100. Il est {0}, -Summary,Résumé, Summary for this month and pending activities,Résumé du mois et des activités en suspens, Summary for this week and pending activities,Résumé de la semaine et des activités en suspens, -Sunday,Dimanche, Suplier,Fournisseur, Supplier,Fournisseur, Supplier Group,Groupe de fournisseurs, @@ -2648,20 +2461,15 @@ Susceptible,Sensible, Sync has been temporarily disabled because maximum retries have been exceeded,La synchronisation a été temporairement désactivée car les tentatives maximales ont été dépassées, Syntax error in condition: {0},Erreur de syntaxe dans la condition: {0}, Syntax error in formula or condition: {0},Erreur de syntaxe dans la formule ou condition : {0}, -System Manager,Responsable Système, TDS Rate %,Pourcentage de TDS, Tap items to add them here,Choisissez des articles pour les ajouter ici, -Target,Cible, Target ({}),Cible ({}), Target On,Cible sur, Target Warehouse,Entrepôt cible, Target warehouse is mandatory for row {0},L’Entrepôt cible est obligatoire pour la ligne {0}, -Task,Tâche, -Tasks,Tâches, Tasks have been created for managing the {0} disease (on row {1}),Des tâches ont été créées pour gérer la maladie {0} (sur la ligne {1}), Tax,Taxe, Tax Assets,Actifs d'Impôts, -Tax Category,Catégorie de taxe, Tax Category for overriding tax rates.,Catégorie de taxe pour les taux de taxe prépondérants., "Tax Category has been changed to ""Total"" because all the Items are non-stock items","La Catégorie de Taxe a été changée à ""Total"" car tous les articles sont des articles hors stock", Tax ID,Numéro d'identification fiscale, @@ -2769,11 +2577,9 @@ Timesheet {0} is already completed or cancelled,La Feuille de Temps {0} est déj Timesheets,Feuilles de temps, "Timesheets help keep track of time, cost and billing for activites done by your team","Les Feuilles de Temps aident au suivi du temps, coût et facturation des activités effectuées par votre équipe", Titles for print templates e.g. Proforma Invoice.,Titres pour les modèles d'impression e.g. Facture Proforma., -To,À, To Address 1,Ligne d'adresse 1 (Destination), To Address 2,Ligne d'adresse 2 (Destination), To Bill,À Facturer, -To Date,Jusqu'au, To Date cannot be before From Date,La date de fin ne peut être antérieure à la date de début, To Date cannot be less than From Date,La date de fin ne peut pas précéder la date de début, To Date must be greater than From Date,La date de fin doit être supérieure à la date de début, @@ -2809,6 +2615,7 @@ Total (Credit),Total (Crédit), Total (Without Tax),Total (hors taxes), Total Achieved,Total Obtenu, Total Actual,Total réel, +Total Allocated Leaves,Total des congés alloués, Total Amount,Montant total, Total Amount Credited,Montant total crédité, Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges,Total des Frais Applicables dans la Table des Articles de Reçus d’Achat doit être égal au Total des Taxes et Frais, @@ -2888,7 +2695,6 @@ Types of activities for Time Logs,Types d'activités pour Journaux de Temps, UOM,UdM, UOM Conversion factor is required in row {0},Facteur de conversion de l'UdM est obligatoire dans la ligne {0}, UOM coversion factor required for UOM: {0} in Item: {1},Facteur de coversion UdM requis pour l'UdM : {0} dans l'Article : {1}, -URL,URL, Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually,Impossible de trouver le taux de change pour {0} à {1} pour la date clé {2}. Veuillez créer une entrée de taux de change manuellement, Unable to find score starting at {0}. You need to have standing scores covering 0 to 100,Impossible de trouver un score démarrant à {0}. Vous devez avoir des scores couvrant 0 à 100, Unable to find variable: ,Impossible de trouver une variable:, @@ -2902,7 +2708,6 @@ Unknown,Inconnu, Unpaid,Impayé, Unsecured Loans,Prêts non garantis, Unsubscribe from this Email Digest,Se Désinscire de ce Compte Rendu par Email, -Unsubscribed,Désinscrit, Until,Jusqu'à, Unverified Webhook Data,Données de Webhook non vérifiées, Update Account Name / Number,Mettre à jour le nom / numéro du compte, @@ -2918,8 +2723,7 @@ Updating Variants...,Mise à jour des variantes ..., Upload your letter head and logo. (you can edit them later).,Charger votre en-tête et logo. (vous pouvez les modifier ultérieurement)., Upper Income,Revenu Élevé, Use Sandbox,Utiliser Sandbox, -User,Utilisateur, -User ID,Identifiant d'utilisateur, +Used Leaves,Congés utilisés, User ID not set for Employee {0},ID de l'Utilisateur non défini pour l'Employé {0}, User Remark,Remarque de l'Utilisateur, User has not applied rule on the invoice {0},L'utilisateur n'a pas appliqué la règle sur la facture {0}, @@ -2929,14 +2733,12 @@ User {0} does not exist,Utilisateur {0} n'existe pas, User {0} doesn't have any default POS Profile. Check Default at Row {1} for this User.,L'utilisateur {0} n'a aucun profil POS par défaut. Vérifiez par défaut à la ligne {1} pour cet utilisateur., User {0} is already assigned to Employee {1},Utilisateur {0} est déjà attribué à l'Employé {1}, User {0} is already assigned to Healthcare Practitioner {1},L'utilisateur {0} est déjà attribué à un professionnel de la santé {1}, -Users,Utilisateurs, Utility Expenses,Frais de Services d'Utilité Publique, Valid From Date must be lesser than Valid Upto Date.,La date de début de validité doit être inférieure à la date de mise en service valide., Valid Till,Valable Jusqu'au, Valid from and valid upto fields are mandatory for the cumulative,Les champs valides à partir de et valables jusqu'à sont obligatoires pour le cumulatif., Valid from date must be less than valid upto date,La date de début de validité doit être inférieure à la date de validité, Valid till date cannot be before transaction date,La date de validité ne peut pas être avant la date de transaction, -Validity,Validité, Validity period of this quotation has ended.,La période de validité de ce devis a pris fin., Valuation Rate,Taux de Valorisation, Valuation Rate is mandatory if Opening Stock entered,Le Taux de Valorisation est obligatoire si un Stock Initial est entré, @@ -2991,7 +2793,6 @@ Warehouse {0} does not exist,L'entrepôt {0} n'existe pas, Warehouses with child nodes cannot be converted to ledger,Les entrepôts avec nœuds enfants ne peuvent pas être convertis en livre, Warehouses with existing transaction can not be converted to group.,Les entrepôts avec des transactions existantes ne peuvent pas être convertis en groupe., Warehouses with existing transaction can not be converted to ledger.,Les entrepôts avec des transactions existantes ne peuvent pas être convertis en livre., -Warning,Avertissement, Warning: Another {0} # {1} exists against stock entry {2},Attention : Un autre {0} {1} # existe pour l'écriture de stock {2}, Warning: Invalid SSL certificate on attachment {0},Attention : certificat SSL non valide sur la pièce jointe {0}, Warning: Invalid attachment {0},Attention : Pièce jointe non valide {0}, @@ -3001,16 +2802,9 @@ Warning: System will not check overbilling since amount for Item {0} in {1} is z Warranty,garantie, Warranty Claim,Réclamation de Garantie, Warranty Claim against Serial No.,Réclamation de Garantie pour le N° de Série., -Website,Site Web, Website Image should be a public file or website URL,L'Image du Site Web doit être un fichier public ou l'URL d'un site web, Website Image {0} attached to Item {1} cannot be found,Image pour le Site Web {0} attachée à l'Article {1} ne peut pas être trouvée, -Website Manager,Responsable du Site Web, -Website Settings,Paramètres du Site web, -Wednesday,Mercredi, -Week,Semaine, -Weekly,Hebdomadaire, "Weight is mentioned,\nPlease mention ""Weight UOM"" too","Poids est mentionné,\nVeuillez aussi mentionner ""UdM de Poids""", -Welcome email sent,Email de bienvenue envoyé, Welcome to ERPNext,Bienvenue sur ERPNext, What do you need help with?,Avec quoi avez vous besoin d'aide ?, What does it do?,Qu'est-ce que ça fait ?, @@ -3073,9 +2867,7 @@ disabled user,utilisateur désactivé, "e.g. ""Build tools for builders""","e.g. ""Construire des outils pour les constructeurs""", "e.g. ""Primary School"" or ""University""","e.g. ""École Primaire"" ou ""Université""", "e.g. Bank, Cash, Credit Card","e.g. Cash, Banque, Carte de crédit", -hidden,Masqué, modified,modifié, -old_parent,grand_parent, on,sur, {0} '{1}' is disabled,{0} '{1}' est désactivé(e), {0} '{1}' not in Fiscal Year {2},{0} '{1}' n'est pas dans l’Exercice {2}, @@ -3108,7 +2900,6 @@ on,sur, {0} hours,{0} heures, {0} in row {1},{0} dans la ligne {1}, {0} is blocked so this transaction cannot proceed,{0} est bloqué donc cette transaction ne peut pas continuer, -{0} is mandatory,{0} est obligatoire, {0} is mandatory for Item {1},{0} est obligatoire pour l’Article {1}, {0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.,{0} est obligatoire. Peut-être qu’un enregistrement de Taux de Change n'est pas créé pour {1} et {2}., {0} is not a stock Item,{0} n'est pas un Article de stock, @@ -3168,37 +2959,8 @@ on,sur, {0}: {1} does not exists,{0} : {1} n’existe pas, {0}: {1} not found in Invoice Details table,{0} : {1} introuvable dans la table de Détails de la Facture, {} of {},{} de {}, -Assigned To,Assigné À, -Chat,Chat, Completed By,Effectué par, -Day of Week,Jour de la semaine, -"Dear System Manager,","Cher Administrateur Système ,", -Default Value,Valeur par Défaut, -Email Group,Groupe Email, -Email Settings,Paramètres d'Email, -Email not sent to {0} (unsubscribed / disabled),Email pas envoyé à {0} (désabonné / désactivé), -Error Message,Message d'erreur, -Fieldtype,Type de Champ, -Help Articles,Articles d'Aide, -ID,ID, -Import,Importer, -Language,Langue, -Likes,Aime, -Merge with existing,Fusionner avec existant, -Orientation,Orientation, -Parent,Parent, Payment Failed,Le Paiement a Échoué, -Personal,Personnel, -Post,Poster, -Postal Code,code postal, -Provider,Fournisseur, -Read Only,Lecture Seule, -Recipient,Destinataire, -Reviews,Avis, -Sender,Expéditeur, -There were errors while sending email. Please try again.,Il y a eu des erreurs lors de l'envoi d’emails. Veuillez essayer à nouveau., -Values Changed,Valeurs Modifiées, -or,ou, Ageing Range 4,Gamme de vieillissement 4, Allocated amount cannot be greater than unadjusted amount,Le montant alloué ne peut être supérieur au montant non ajusté, Allocated amount cannot be negative,Le montant alloué ne peut être négatif, @@ -3224,22 +2986,7 @@ Rules for applying different promotional schemes.,Règles d'application de diff Show {0},Montrer {0}, Target Details,Détails de la cible, {0} already has a Parent Procedure {1}.,{0} a déjà une procédure parent {1}., -API,API, -Annual,Annuel, -Change,Changement, -Contact Email,Email du Contact, -From Date,A partir du, -Group By,Par groupe, -Invalid URL,URL invalide, -Landscape,Paysage, -Naming Series,Masque de numérotation, -No data to export,Aucune donnée à exporter, -Portrait,Portrait, Print Heading,Imprimer Titre, -Scheduler Inactive,Planificateur inactif, -Scheduler is inactive. Cannot import data.,Le planificateur est inactif. Impossible d'importer des données., -Show Document,Afficher le document, -Show Traceback,Afficher le traçage, Video,Vidéo, % Of Grand Total,% Du grand total, Company is a mandatory filter.,La société est un filtre obligatoire., @@ -3257,20 +3004,11 @@ Accounting Dimension {0} is required for 'Balance Sheet' account {1}.,La Accounting Dimension {0} is required for 'Profit and Loss' account {1}.,La dimension de comptabilité {0} est requise pour le compte 'Bénéfices et pertes' {1}., Accounting Masters,Maîtres Comptables, Accounting Period overlaps with {0},La période comptable chevauche avec {0}, -Activity,Activité, -Add / Manage Email Accounts.,Ajouter / Gérer les Comptes de Messagerie., -Add Child,Ajouter une Sous-Catégorie, -Add Multiple,Ajout Multiple, -Add Participants,Ajouter des participants, Add to Featured Item,Ajouter à l'article en vedette, Add your review,Ajouter votre avis, Add/Edit Coupon Conditions,Ajouter / Modifier les conditions du coupon, Added to Featured Items,Ajouté aux articles en vedette, -Added {0} ({1}),Ajouté {0} ({1}), -Address Line 1,Adresse Ligne 1, -Addresses,Adresses, Admission End Date should be greater than Admission Start Date.,La date de fin d'admission doit être supérieure à la date de début d'admission., -All,Tout, All bank transactions have been created,Toutes les transactions bancaires ont été créées, All the depreciations has been booked,Toutes les amortissements ont été comptabilisés, Allow Resetting Service Level Agreement from Support Settings.,Autoriser la réinitialisation du contrat de niveau de service à partir des paramètres de support., @@ -3305,17 +3043,12 @@ Bank accounts added,Comptes bancaires ajoutés, Batch no is required for batched item {0},Le numéro de lot est requis pour l'article en lot {0}., Billing Date,Date de facturation, Billing Interval Count cannot be less than 1,Le nombre d'intervalles de facturation ne peut pas être inférieur à 1, -Blue,Bleu, -Book,Livre, Book Appointment,Prendre rendez-vous, -Brand,Marque, -Browse,Feuilleter, Call Connected,Appel connecté, Call Disconnected,Appel déconnecté, Call Missed,Appel manqué, Call Summary,Résumé d'appel, Call Summary Saved,Résumé de l'appel enregistré, -Cancelled,Annulé, Cannot Calculate Arrival Time as Driver Address is Missing.,Impossible de calculer l'heure d'arrivée car l'adresse du conducteur est manquante., Cannot Optimize Route as Driver Address is Missing.,Impossible d'optimiser l'itinéraire car l'adresse du pilote est manquante., Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.,Impossible de terminer la tâche {0} car sa tâche dépendante {1} n'est pas terminée / annulée., @@ -3324,26 +3057,19 @@ Cannot find a matching Item. Please select some other value for {0}.,Impossible "Capacity Planning Error, planned start time can not be same as end time","Erreur de planification de capacité, l'heure de début prévue ne peut pas être identique à l'heure de fin", Categories,Catégories, Changes in {0},Changements dans {0}, -Chart,Graphique, Choose a corresponding payment,Choisissez un paiement correspondant, Click on the link below to verify your email and confirm the appointment,Cliquez sur le lien ci-dessous pour vérifier votre email et confirmer le rendez-vous, -Close,Fermer, Communication,Communication, Compact Item Print,Impression de l'Article Compacté, -Company,Société, Company of asset {0} and purchase document {1} doesn't matches.,La société de l'actif {0} et le document d'achat {1} ne correspondent pas., Compare BOMs for changes in Raw Materials and Operations,Comparer les nomenclatures aux modifications apportées aux matières premières et aux opérations, Compare List function takes on list arguments,La fonction de comparaison de liste accepte les arguments de liste, -Complete,Terminé, -Completed,Terminé, Completed Quantity,Quantité terminée, Connect your Exotel Account to ERPNext and track call logs,Connectez votre compte Exotel à ERPNext et suivez les journaux d'appels, Connect your bank accounts to ERPNext,Connectez vos comptes bancaires à ERPNext, Contact Seller,Contacter le vendeur, -Continue,Continuer, Cost Center: {0} does not exist,Centre de coûts: {0} n'existe pas, Couldn't Set Service Level Agreement {0}.,Impossible de définir le contrat de service {0}., -Country,Pays, Country Code in File does not match with country code set up in the system,Le code de pays dans le fichier ne correspond pas au code de pays configuré dans le système, Create New Contact,Créer un nouveau contact, Create New Lead,Créer une nouvelle lead, @@ -3354,34 +3080,20 @@ Creating bank entries...,Création d'entrées bancaires ..., Credit limit is already defined for the Company {0},La limite de crédit est déjà définie pour la société {0}., Ctrl + Enter to submit,Ctrl + Entrée pour valider, Ctrl+Enter to submit,Ctrl + Entrée pour valider, -Currency,Devise, -Current Status,Statut Actuel, Customer PO,Commande d'Achat client, -Daily,Quotidien, -Date,Date, Date of Birth cannot be greater than Joining Date.,La date de naissance ne peut pas être supérieure à la date d'adhésion., -Dear,Cher/Chère, -Default,Par Défaut, Define coupon codes.,Définissez les codes promo., Delayed Days,Jours retardés, -Delete,Supprimer, Delivered Quantity,Quantité livrée, Delivery Notes,Bons de livraison, Depreciated Amount,Montant amorti, -Description,Description, -Designation,Désignation, Difference Value,Valeur de différence, Dimension Filter,Filtre de dimension, -Disabled,Desactivé, Disbursement and Repayment,Décaissement et remboursement, Distance cannot be greater than 4000 kms,La distance ne peut pas dépasser 4000 km, Do you want to submit the material request,Voulez-vous valider la demande de matériel, -Doctype,Doctype, Document {0} successfully uncleared,Document {0} non effacé avec succès, -Download Template,Télécharger le Modèle, Dr,Dr, -Due Date,Date d'Échéance, -Duplicate,Dupliquer, Duplicate Project with Tasks,Projet en double avec tâches, Duplicate project has been created,Un projet en double a été créé, E-Way Bill JSON can only be generated from a submitted document,E-Way Bill JSON ne peut être généré qu'à partir d'un document soumis, @@ -3391,7 +3103,6 @@ ERPNext could not find any matching payment entry,ERPNext n'a trouvé aucune ent Earliest Age,Âge le plus précoce, Edit Details,Modifier les détails, Either GST Transporter ID or Vehicle No is required if Mode of Transport is Road,Un numéro d'identification de transporteur ou un numéro de véhicule est requis si le mode de transport est la route., -Email,Email, Email Campaigns,Campagnes de courrier électronique, Employee ID is linked with another instructor,L'ID de l'employé est lié à un autre instructeur, Employee Tax and Benefits,Impôt et avantages sociaux des employés, @@ -3403,21 +3114,13 @@ End Time,Heure de Fin, Energy Point Leaderboard,Point de classement énergétique, Enter API key in Google Settings.,Entrez la clé API dans les paramètres Google., Enter Supplier,Entrez le fournisseur, -Enter Value,Entrez une Valeur, -Entity Type,Type d'entité, -Error,Erreur, Error in Exotel incoming call,Erreur dans un appel entrant Exotel, Error: {0} is mandatory field,Erreur: {0} est un champ obligatoire, Exception occurred while reconciling {0},Une exception s'est produite lors de la réconciliation {0}, Expected and Discharge dates cannot be less than Admission Schedule date,Les dates prévues et de sortie ne peuvent pas être inférieures à la date du calendrier d'admission, -Expired,Expiré, -Export,Exporter, -Export not allowed. You need {0} role to export.,Pas autorisé à exporter. Vous devez avoir le rôle {0} pour exporter., Failed to add Domain,Impossible d'ajouter le domaine, Fetch Items from Warehouse,Récupérer des articles de l'entrepôt, Fetching...,Aller chercher..., -Field,Champ, -Filters,Filtres, Finding linked payments,Trouver des paiements liés, Fleet Management,Gestion de flotte, Following fields are mandatory to create address:,Les champs suivants sont obligatoires pour créer une adresse:, @@ -3433,28 +3136,17 @@ Future Payment Ref,Paiement futur Ref, Future Payments,Paiements futurs, GST HSN Code does not exist for one or more items,Le code HSN de la TPS n’existe pas pour un ou plusieurs articles, Generate E-Way Bill JSON,Générer E-Way Bill JSON, -Get Items,Obtenir les Articles, Get Outstanding Documents,Obtenez des documents en suspens, -Goal,Objectif, Greater Than Amount,Plus grand que le montant, -Green,Vert, -Group,Groupe, Group By Customer,Regrouper par client, Group By Supplier,Regrouper par fournisseur, -Group Node,Noeud de Groupe, Group Warehouses cannot be used in transactions. Please change the value of {0},Les entrepôts de groupe ne peuvent pas être utilisés dans les transactions. Veuillez modifier la valeur de {0}, -Help,Aidez-moi, -Help Article,Article d’Aide, "Helps you keep tracks of Contracts based on Supplier, Customer and Employee","Vous aide à garder une trace des contrats en fonction du fournisseur, client et employé", Helps you manage appointments with your leads,Vous aide à gérer les rendez-vous avec vos leads, -Home,Accueil, IBAN is not valid,IBAN n'est pas valide, -Import Data from CSV / Excel files.,Importer des données à partir de fichiers CSV / Excel, -In Progress,En cours, Incoming call from {0},Appel entrant du {0}, Incorrect Warehouse,Entrepôt incorrect, Invalid Barcode. There is no Item attached to this barcode.,Code à barres invalide. Il n'y a pas d'article attaché à ce code à barres., -Invalid credentials,les informations d'identification invalides, Issue Priority.,Priorité d'émission., Issue Type.,Type de probleme., "It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Il semble qu'il y a un problème avec la configuration de Stripe sur le serveur. En cas d'erreur, le montant est remboursé sur votre compte.", @@ -3470,13 +3162,11 @@ Latest Age,Dernier âge, Leaves Taken,Feuilles prises, Less Than Amount,Moins que le montant, Liabilities,Passifs, -Loading...,Chargement en Cours ..., Loan Applications from customers and employees.,Demandes de prêt des clients et des employés., Loan Processes,Processus de prêt, Loan Type for interest and penalty rates,Type de prêt pour les taux d'intérêt et de pénalité, Loans,Les prêts, Loans provided to customers and employees.,Prêts accordés aux clients et aux employés., -Location,Lieu, Looks like someone sent you to an incomplete URL. Please ask them to look into it.,On dirait que quelqu'un vous a envoyé vers URL incomplète. Veuillez leur demander d’analyser l’erreur., Make Journal Entry,Faire une écriture de journal, Make Purchase Invoice,Faire la facture d'achat, @@ -3485,12 +3175,6 @@ Mark Work From Home,Mark Work From Home, Master,Maître, Max strength cannot be less than zero.,La force maximale ne peut pas être inférieure à zéro., Maximum attempts for this quiz reached!,Nombre maximal de tentatives pour ce quiz atteint!, -Message,Message, -Missing Values Required,Valeurs Manquantes Requises, -Mobile No,N° Mobile, -Mobile Number,Numéro de Mobile, -Month,Mois, -Name,Nom, Near you,Près de toi, Net Profit/Loss,Résultat net, New Expense,Nouvelle dépense, @@ -3509,10 +3193,8 @@ No outstanding invoices require exchange rate revaluation,Aucune facture en atte No reviews yet,Pas encore d'avis, No views yet,Pas encore de vue, Non stock items,Articles hors stock, -Not Allowed,Non Autorisé, Not allowed to create accounting dimension for {0},Non autorisé à créer une dimension comptable pour {0}, Not permitted. Please disable the Lab Test Template,Pas permis. Veuillez désactiver le modèle de test de laboratoire, -Note,Note, Notes: ,Remarques :, On Converting Opportunity,Sur l'opportunité de conversion, On Purchase Order Submission,Sur soumission de commande, @@ -3520,13 +3202,11 @@ On Sales Order Submission,Envoi de commande client, On Task Completion,En fin de tâche, On {0} Creation,Sur {0} Creation, Only .csv and .xlsx files are supported currently,Seuls les fichiers .csv et .xlsx sont actuellement pris en charge., -Open,Ouvert, Open Contact,Contact ouvert, Open Lead,Ouvrir le Lead, Opening and Closing,Ouverture et fermeture, Operating Cost as per Work Order / BOM,Coût d'exploitation selon l'ordre de fabrication / nomenclature, Order Amount,Montant de la commande, -Page {0} of {1},Page {0} sur {1}, Paid amount cannot be less than {0},Le montant payé ne peut pas être inférieur à {0}, Parent Company must be a group company,La société mère doit être une société du groupe, Passing Score value should be between 0 and 100,La note de passage doit être comprise entre 0 et 100, @@ -3535,11 +3215,9 @@ Pause,Pause, Pay,Payer, Payment Document Type,Type de document de paiement, Payment Name,Nom du paiement, -Pending,En Attente, Performance,Performance, Period based On,Période basée sur, Perpetual inventory required for the company {0} to view this report.,Inventaire permanent requis pour que la société {0} puisse consulter ce rapport., -Phone,Téléphone, Pick List,Liste de sélection, Plaid authentication error,Erreur d'authentification du plaid, Plaid public token error,Erreur de jeton public Plaid, @@ -3570,14 +3248,11 @@ Please set up the Campaign Schedule in the Campaign {0},Configurez le calendrier Please set valid GSTIN No. in Company Address for company {0},Veuillez définir un numéro GSTIN valide dans l'adresse de l'entreprise pour l'entreprise {0}, Please set {0},Veuillez définir {0},customer Please setup a default bank account for company {0},Veuillez configurer un compte bancaire par défaut pour la société {0}., -Please specify,Veuillez spécifier, Please specify a {0},Veuillez spécifier un {0},lead -Priority,Priorité, Priority has been changed to {0}.,La priorité a été changée en {0}., Priority {0} has been repeated.,La priorité {0} a été répétée., Processing XML Files,Traitement des fichiers XML, Profitability,Rentabilité, -Project,Projet, Provide the academic year and set the starting and ending date.,Indiquez l'année universitaire et définissez la date de début et de fin., Public token is missing for this bank,Un jeton public est manquant pour cette banque, Publish 1 Item,Publier 1 élément, @@ -3595,30 +3270,22 @@ Qty of Finished Goods Item,Quantité de produits finis, Quality Inspection required for Item {0} to submit,Inspection de qualité requise pour que l'élément {0} soit envoyé, Quantity to Manufacture,Quantité à fabriquer, Quantity to Manufacture can not be zero for the operation {0},La quantité à fabriquer ne peut pas être nulle pour l'opération {0}, -Quarterly,Trimestriel, -Queued,File d'Attente, -Quick Entry,Écriture Rapide, Quiz {0} does not exist,Le questionnaire {0} n'existe pas, Quotation Amount,Montant du devis, Rate or Discount is required for the price discount.,Le prix ou la remise est requis pour la remise., -Reason,Raison, Reconcile Entries,Réconcilier les entrées, Reconcile this account,Réconcilier ce compte, Reconciled,Réconcilié, Recruitment,Recrutement, -Red,Rouge, Release date must be in the future,La date de sortie doit être dans le futur, Relieving Date must be greater than or equal to Date of Joining,La date de libération doit être supérieure ou égale à la date d'adhésion, -Rename,Renommer, Rename Not Allowed,Renommer non autorisé, Report Item,Élément de rapport, Report this Item,Signaler cet article, Reserved Qty for Subcontract: Raw materials quantity to make subcontracted items.,Quantité réservée pour la sous-traitance: quantité de matières premières pour fabriquer des articles sous-traités., -Reset,Réinitialiser, Reset Service Level Agreement,Réinitialiser l'accord de niveau de service, Resetting Service Level Agreement.,Réinitialisation de l'accord de niveau de service., Return amount cannot be greater unclaimed amount,Le montant du retour ne peut pas être supérieur au montant non réclamé, -Review,La revue, Room,Chambre, Room Type,Type de chambre, Row # ,Ligne #, @@ -3643,47 +3310,36 @@ Row {0}:Sibling Date of Birth cannot be greater than today.,Ligne {0}: la date d Row({0}): {1} is already discounted in {2},Ligne ({0}): {1} est déjà réduit dans {2}., Rows Added in {0},Lignes ajoutées dans {0}, Rows Removed in {0},Lignes supprimées dans {0}, -Save,sauvegarder, Save Item,Enregistrer l'élément, Saved Items,Articles sauvegardés, Search Items ...,Rechercher des articles ..., Search for a payment,Rechercher un paiement, Search for anything ...,Rechercher n'importe quoi ..., -Search results for,Résultats de recherche pour, Select Difference Account,Sélectionnez compte différentiel, Select a Default Priority.,Sélectionnez une priorité par défaut., Select a company,Sélectionnez une entreprise, Select finance book for the item {0} at row {1},Sélectionnez le livre de financement pour l'élément {0} à la ligne {1}., Select only one Priority as Default.,Sélectionnez une seule priorité par défaut., Seller Information,Information du vendeur, -Send,Envoyer, Send a message,Envoyer un message, -Sending,Envoi, Sends Mails to lead or contact based on a Campaign schedule,Envoie des courriers à diriger ou à contacter en fonction d'un calendrier de campagne, Serial Number Created,Numéro de série créé, Serial Numbers Created,Numéros de série créés, Serial no(s) required for serialized item {0},N ° de série requis pour l'article sérialisé {0}, Series,Séries, -Server Error,Erreur du Serveur, Service Level Agreement has been changed to {0}.,L'accord de niveau de service a été remplacé par {0}., Service Level Agreement was reset.,L'accord de niveau de service a été réinitialisé., Service Level Agreement with Entity Type {0} and Entity {1} already exists.,L'accord de niveau de service avec le type d'entité {0} et l'entité {1} existe déjà., Set Meta Tags,Définir les balises méta, Set {0} in company {1},Définissez {0} dans l'entreprise {1}, -Setup,Configuration, Shift Management,Gestion des quarts, Show Future Payments,Afficher les paiements futurs, Show Linked Delivery Notes,Afficher les bons de livraison liés, Show Sales Person,Afficher le vendeur, Show Stock Ageing Data,Afficher les données sur le vieillissement des stocks, Show Warehouse-wise Stock,Afficher le stock entre les magasins, -Size,Taille, Something went wrong while evaluating the quiz.,Quelque chose s'est mal passé lors de l'évaluation du quiz., -Sr,Sr, -Start,Démarrer, Start Date cannot be before the current date,La date de début ne peut pas être antérieure à la date du jour, -Start Time,Heure de Début, -Status,Statut, Status must be Cancelled or Completed,Le statut doit être annulé ou complété, Stock Balance Report,Rapport de solde des stocks, Stock Entry has been already created against this Pick List,Une entrée de stock a déjà été créée dans cette liste de choix, @@ -3692,10 +3348,8 @@ Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and Stores - {0},Magasins - {0}, Student with email {0} does not exist,Étudiant avec le courrier électronique {0} n'existe pas, Submit Review,Poster un commentaire, -Submitted,Valider, Supplier Addresses And Contacts,Adresses et contacts des fournisseurs, Synchronize this account,Synchroniser ce compte, -Tag,Étiquette, Target Location is required while receiving Asset {0} from an employee,L'emplacement cible est requis lors de la réception de l'élément {0} d'un employé, Target Location is required while transferring Asset {0},L'emplacement cible est requis lors du transfert de l'élément {0}, Target Location or To Employee is required while receiving Asset {0},L'emplacement cible ou l'employé est requis lors de la réception de l'élément {0}, @@ -3703,7 +3357,6 @@ Task's {0} End Date cannot be after Project's End Date.,La date de fin {0} de la Task's {0} Start Date cannot be after Project's End Date.,La date de début {0} de la tâche ne peut pas être postérieure à la date de fin du projet., Tax Account not specified for Shopify Tax {0},Compte de taxe non spécifié pour Shopify Tax {0}, Tax Total,Total de la taxe, -Template,Modèle, The Campaign '{0}' already exists for the {1} '{2}',La campagne '{0}' existe déjà pour le {1} '{2}'., The difference between from time and To Time must be a multiple of Appointment,La différence entre from time et To Time doit être un multiple de Appointment, The field Asset Account cannot be blank,Le champ Compte d'actif ne peut pas être vide, @@ -3720,19 +3373,13 @@ This bank account is already synchronized,Ce compte bancaire est déjà synchron This bank transaction is already fully reconciled,Cette transaction bancaire est déjà totalement réconciliée, This page keeps track of items you want to buy from sellers.,Cette page répertorie les articles que vous souhaitez acheter auprès des vendeurs., This page keeps track of your items in which buyers have showed some interest.,Cette page conserve une trace de vos articles pour lesquels les acheteurs ont manifesté un certain intérêt., -Thursday,Jeudi, -Title,Titre, "To allow over billing, update ""Over Billing Allowance"" in Accounts Settings or the Item.","Pour autoriser la facturation excédentaire, mettez à jour "Provision de facturation excédentaire" dans les paramètres de compte ou le poste.", "To allow over receipt / delivery, update ""Over Receipt/Delivery Allowance"" in Stock Settings or the Item.","Pour autoriser le dépassement de réception / livraison, mettez à jour "Limite de dépassement de réception / livraison" dans les paramètres de stock ou le poste.", -Total,Total, Total Payment Request amount cannot be greater than {0} amount,Le montant total de la demande de paiement ne peut être supérieur à {0}., Total payments amount can't be greater than {},Le montant total des paiements ne peut être supérieur à {}, -Totals,Totaux, Transactions already retreived from the statement,Transactions déjà extraites de la déclaration, Transfer Material to Supplier,Transfert de matériel au fournisseur, Transport Receipt No and Date are mandatory for your chosen Mode of Transport,Le numéro de reçu de transport et la date sont obligatoires pour le mode de transport choisi, -Tuesday,Mardi, -Type,Type, Unable to find the time slot in the next {0} days for the operation {1}.,Impossible de trouver l'intervalle de temps dans les {0} jours suivants pour l'opération {1}., Unable to update remote activity,Impossible de mettre à jour l'activité à distance, Unknown Caller,Appelant inconnu, @@ -3740,13 +3387,10 @@ Unlink external integrations,Dissocier les intégrations externes, Unpublish Item,Annuler la publication, Unreconciled,Non réconcilié, Unsupported GST Category for E-Way Bill JSON generation,Catégorie GST non prise en charge pour la génération e-Way Bill JSON, -Update,Mettre à Jour, Update Taxes for Items,Mettre à jour les taxes pour les articles, "Upload a bank statement, link or reconcile a bank account","Télécharger un relevé bancaire, un lien ou un rapprochement d'un compte bancaire", Upload a statement,Télécharger une déclaration, Use a name that is different from previous project name,Utilisez un nom différent du nom du projet précédent, -User {0} is disabled,Utilisateur {0} est désactivé, -Users and Permissions,Utilisateurs et Autorisations, Valuation Rate required for Item {0} at row {1},Taux de valorisation requis pour le poste {0} à la ligne {1}, Values Out Of Sync,Valeurs désynchronisées, Vehicle Type is required if Mode of Transport is Road,Le type de véhicule est requis si le mode de transport est la route, @@ -3755,15 +3399,11 @@ Verify Email,Vérifier les courriels, View,Vue, View all issues from {0},Afficher tous les problèmes de {0}, View call log,Voir le journal des appels, -Warehouse,Entrepôt, Warehouse not found against the account {0},Entrepôt introuvable sur le compte {0}, -Welcome to {0},Bienvenue sur {0}, Why do think this Item should be removed?,Pourquoi pensez-vous que cet élément devrait être supprimé?, Work Order {0}: Job Card not found for the operation {1},Bon de travail {0}: carte de travail non trouvée pour l'opération {1}, Workday {0} has been repeated.,La journée de travail {0} a été répétée., XML Files Processed,Fichiers XML traités, -Year,Année, -Yearly,Annuel, You are not allowed to enroll for this course,Vous n'êtes pas autorisé à vous inscrire à ce cours, You are not enrolled in program {0},Vous n'êtes pas inscrit au programme {0}, You can Feature upto 8 items.,Vous pouvez présenter jusqu'à 8 éléments., @@ -3776,7 +3416,6 @@ Your Featured Items,Vos articles en vedette, Your Items,Vos articles, Your Profile,Votre profil, Your rating:,Votre note :, -and,et, e-Way Bill already exists for this document,e-Way Bill existe déjà pour ce document, woocommerce - {0},woocommerce - {0}, {0} Coupon used are {1}. Allowed quantity is exhausted,Le {0} coupon utilisé est {1}. La quantité autorisée est épuisée, @@ -3788,7 +3427,6 @@ woocommerce - {0},woocommerce - {0}, {0} is not a company bank account,{0} n'est pas un compte bancaire d'entreprise, {0} is not a group node. Please select a group node as parent cost center,{0} n'est pas un nœud de groupe. Veuillez sélectionner un nœud de groupe comme centre de coûts parent, {0} is not the default supplier for any items.,{0} n'est le fournisseur par défaut d'aucun élément., -{0} is required,{0} est nécessaire, {0}: {1} must be less than {2},{0}: {1} doit être inférieur à {2}, {} is required to generate E-Way Bill JSON,{} est requis pour générer e-Way Bill JSON, "Invalid lost reason {0}, please create a new lost reason","Motif perdu non valide {0}, veuillez créer un nouveau motif perdu", @@ -3797,20 +3435,6 @@ Total Expense,Dépense totale, Total Expense This Year,Dépenses totales cette année, Total Income,Revenu total, Total Income This Year,Revenu total cette année, -Barcode,code à barre, -Clear,Clair, -Comments,Commentaires, -DocType,DocType, -Download,Télécharger, -Left,Parti, -Link,Lien, -New,Nouveau, -Print,Impression, -Reference Name,Nom de référence, -Refresh,Actualiser, -Success,Succès, -Time,Temps, -Value,Valeur, Actual,Réel, Add to Cart,Ajouter au Panier, Days Since Last Order,Jours depuis la dernière commande, @@ -3824,10 +3448,6 @@ Sales Person,Vendeur, To date cannot be before From date,La date de fin ne peut être antérieure à la date de début, Write Off,Reprise, {0} Created,{0} Créé, -Email Id,Identifiant Email, -No,Non, -Reference Doctype,DocType de la Référence, -Yes,Oui, Actual ,Réel, Add to cart,Ajouter au Panier, Budget,Budget, @@ -3838,16 +3458,13 @@ Download as JSON,Télécharger en JSON, End date can not be less than start date,La date de Fin ne peut pas être antérieure à la Date de Début, For Default Supplier (Optional),Pour le fournisseur par défaut (facultatif), From date cannot be greater than To date,La Date Initiale ne peut pas être postérieure à la Date Finale, -Group by,Grouper Par, In stock,En stock, Item name,Libellé de l'article, Minimum Qty,Quantité minimum, More details,Plus de détails, Nature of Supplies,Nature des fournitures, -No Items found.,Aucun article trouvé., No students found,Aucun étudiant trouvé, Not in stock,En rupture, -Not permitted,Pas permis, Open Issues ,Tickets ouverts, Open Projects ,Projets ouverts, Open To Do ,ToDo ouvertes, @@ -3872,8 +3489,6 @@ Write off,Reprise, hours,heures, received from,reçu de, to,à, -Cards,Cartes, -Percentage,Pourcentage, Failed to setup defaults for country {0}. Please contact support@erpnext.com,Échec de la configuration des paramètres par défaut pour le pays {0}. Veuillez contacter support@erpnext.com, Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.,Ligne # {0}: l'article {1} n'est pas un article sérialisé / en lot. Il ne peut pas avoir de numéro de série / de lot contre lui., Please set {0},Veuillez définir {0}, @@ -3911,9 +3526,6 @@ Note: Item {0} added multiple times,Remarque: l'élément {0} a été ajouté pl YouTube,Youtube, Vimeo,Vimeo, Publish Date,Date de publication, -Duration,Durée, -Advanced Settings,Réglages avancés, -Path,Chemin, Components,Composants, Verified By,Vérifié Par, Invalid naming series (. missing) for {0},Masque de numérotation non valide (. Manquante) pour {0}, @@ -4230,7 +3842,6 @@ Is Finance Cost Adjustment,Est un ajustement des coûts financiers, Is Income Tax Liability,Est une dette d'impôt sur le revenu, Is Income Tax Expense,Est une dépense d'impôt sur le revenu, Cash Flow Mapping Accounts,Comptes du Mapping des Flux de Trésorerie, -account,Compte, Cash Flow Mapping Template,Modèle de Mapping des Flux de Trésorerie, Cash Flow Mapping Template Details,Détails du Modèle de Mapping des Flux de Trésorerie, POS-CLO-,POS-CLO-, @@ -4474,7 +4085,6 @@ Payment Schedule,Calendrier de paiement, Invoice Portion,Pourcentage de facturation, Payment Amount,Montant du paiement, Payment Term Name,Nom du terme de paiement, -Due Date Based On,Date d'échéance basée sur, Day(s) after invoice date,Jour (s) après la date de la facture, Day(s) after the end of the invoice month,Jour (s) après la fin du mois de facture, Month(s) after the end of the invoice month,Mois (s) après la fin du mois de la facture, @@ -4769,7 +4379,6 @@ Asset Account,Compte d'actif, (including),(compris), ACC-SH-.YYYY.-,ACC-SH-.YYYY.-, Folio no.,No. de Folio, -Address and Contacts,Adresse et Contacts, Contact List,Liste de contacts, Hidden list maintaining the list of contacts linked to Shareholder,Liste cachée maintenant la liste des contacts liés aux actionnaires, Specify conditions to calculate shipping amount,Spécifier les conditions pour calculer le montant de la livraison, @@ -5157,9 +4766,6 @@ Supplier Scorecard Scoring Criteria,Critères de Notation de la Fiche d'Évaluat Score,Score, Supplier Scorecard Scoring Standing,Classement de la Fiche d'Évaluation Fournisseur, Standing Name,Nom du Classement, -Purple,Violet, -Yellow,jaune, -Orange,orange, Min Grade,Note Minimale, Max Grade,Note Maximale, Warn Purchase Orders,Avertir lors de Bons de Commande, @@ -5194,7 +4800,6 @@ Unverified,Non vérifié, Customer Details,Détails du client, Phone Number,Numéro de téléphone, Skype ID,ID Skype, -Linked Documents,Documents liés, Appointment With,Rendez-vous avec, Calendar Event,Événement de calendrier, Appointment Booking Settings,Paramètres de réservation de rendez-vous, @@ -5212,7 +4817,6 @@ Success Settings,Paramètres de réussite, Success Redirect URL,URL de redirection réussie, "Leave blank for home.\nThis is relative to site URL, for example ""about"" will redirect to ""https://yoursitename.com/about""","Laissez vide pour la maison. Ceci est relatif à l'URL du site, par exemple "about" redirigera vers "https://yoursitename.com/about"", Appointment Booking Slots,Horaires de prise de rendez-vous, -Day Of Week,Jour de la Semaine, From Time ,Horaire de Début, Campaign Email Schedule,Calendrier des e-mails de campagne, Send After (days),Envoyer après (jours), @@ -5255,7 +4859,6 @@ Campaign Name,Nom de la Campagne, Follow Up,Suivre, Next Contact By,Contact Suivant Par, Next Contact Date,Date du Prochain Contact, -Ends On,Se termine le, Address & Contact,Adresse & Contact, Mobile No.,N° Mobile., Lead Type,Type de Lead, @@ -5302,8 +4905,6 @@ Social Media Post,Publication sur les réseaux sociaux, Post Status,Statut du message, Posted,Publié, Share On,Partager sur, -Twitter,Twitter, -LinkedIn,LinkedIn, Twitter Post Id,Identifiant de publication Twitter, LinkedIn Post Id,Identifiant de publication LinkedIn, Tweet,Tweet, @@ -5853,7 +5454,6 @@ Require Result Value,Nécessite la Valeur du Résultat, Normal Test Template,Modèle de Test Normal, Patient Demographics,Démographie du Patient, HLC-PAT-.YYYY.-,HLC-PAT-. AAAA.-, -Middle Name (optional),Prénom (facultatif), Inpatient Status,Statut d'hospitalisation, "If ""Link Customer to Patient"" is checked in Healthcare Settings and an existing Customer is not selected then, a Customer will be created for this Patient for recording transactions in Accounts module.","Si «Lier le client au patient» est coché dans les paramètres de soins de santé et qu'un client existant n'est pas sélectionné, un client sera créé pour ce patient pour enregistrer les transactions dans le module Comptes.", Personal and Social History,Antécédents Personnels et Sociaux, @@ -6180,7 +5780,6 @@ Maintenance Schedule Detail,Détails de l'Échéancier d'Entretien, Scheduled Date,Date Prévue, Actual Date,Date Réelle, Maintenance Schedule Item,Article de Calendrier d'Entretien, -Random,aléatoire, No of Visits,Nb de Visites, MAT-MVS-.YYYY.-,MAT-MVS-. AAAA.-, Maintenance Date,Date de l'Entretien, @@ -6430,7 +6029,6 @@ Member Since,Membre depuis, Payment ID,ID de paiement, Membership Settings,Paramètres d'adhésion, Enable RazorPay For Memberships,Activer RazorPay pour les adhésions, -RazorPay Settings,Paramètres de RazorPay, Billing Cycle,Cycle de facturation, Billing Frequency,Fréquence de facturation, "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.","Le nombre de cycles de facturation pour lesquels le client doit être facturé. Par exemple, si un client achète un abonnement d'un an qui doit être facturé sur une base mensuelle, cette valeur doit être de 12.", @@ -7009,7 +6607,7 @@ Leave blank to use the standard Delivery Note format,Laissez vide pour utiliser Send with Attachment,Envoyer avec pièce jointe, Delay between Delivery Stops,Délai entre les arrêts de livraison, Delivery Stop,Étape de Livraison, -Lock,Fermer à clé, +Lock,Verrouiller, Visited,Visité, Order Information,Informations sur la commande, Contact Information,Informations de contact, @@ -8191,7 +7789,6 @@ Topics updated,Sujets mis à jour, Academic Term and Program,Terme académique et programme, Please remove this item and try to submit again or update the posting time.,Veuillez supprimer cet élément et réessayer de le valider ou mettre à jour l'heure de publication., Failed to Authenticate the API key.,Échec de l'authentification de la clé API., -Invalid Credentials,Les informations d'identification invalides, URL can only be a string,L'URL ne peut être qu'une chaîne, "Here is your webhook secret, this will be shown to you only once.","Voici votre secret de webhook, il ne vous sera montré qu'une seule fois.", The payment for this membership is not paid. To generate invoice fill the payment details,"Le paiement de cette adhésion n'est pas payé. Pour générer une facture, remplissez les détails du paiement", @@ -8756,7 +8353,6 @@ Journal Energy Point,Historique des points d'énergies, Billing Address Details,Adresse de facturation (détails) Supplier Address Details,Adresse Fournisseur (détails) Retail,Commerce, -Users,Utilisateurs, Permission Manager,Gestion des permissions, Fetch Timesheet,Récuprer les temps saisis, Get Supplier Group Details,Appliquer les informations depuis le Groupe de fournisseur, From 55dbcee36a2acf4aa41c66147c263a85ef606f81 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 20 Oct 2023 17:16:54 +0530 Subject: [PATCH 184/280] refactor: gain_loss posting date fields in the allocation table --- .../payment_reconciliation_allocation.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index ec718aa70d..2fddd85732 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -151,11 +151,16 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "fieldname": "gain_loss_posting_date", + "fieldtype": "Date", + "label": "Difference Posting Date" } ], "istable": 1, "links": [], - "modified": "2023-09-03 07:52:33.684217", + "modified": "2023-10-23 10:44:56.066303", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From 5323bb7beeb6526d16bcb19fc2f3acd3a95927e6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 22 Oct 2023 08:59:52 +0530 Subject: [PATCH 185/280] refactor: introduce fields in popup --- .../payment_reconciliation/payment_reconciliation.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index d9f00befa9..7599b5e852 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -229,6 +229,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.data = []; const dialog = new frappe.ui.Dialog({ title: __("Select Difference Account"), + size: 'extra-large', fields: [ { fieldname: "allocation", @@ -252,6 +253,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo in_list_view: 1, read_only: 1 }, { + fieldtype:'Date', + fieldname:"gain_loss_posting_date", + label: __("Posting Date"), + in_list_view: 1, + reqd: 1, + }, { + fieldtype:'Link', options: 'Account', in_list_view: 1, @@ -285,6 +293,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo args.forEach(d => { frappe.model.set_value("Payment Reconciliation Allocation", d.docname, "difference_account", d.difference_account); + }); this.reconcile_payment_entries(); @@ -300,6 +309,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo 'reference_name': d.reference_name, 'difference_amount': d.difference_amount, 'difference_account': d.difference_account, + 'gain_loss_posting_date': d.gain_loss_posting_date }); } }); From b099590b2c1dcd041b833af50e99eb3e7988c595 Mon Sep 17 00:00:00 2001 From: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com> Date: Mon, 23 Oct 2023 07:10:07 +0100 Subject: [PATCH 186/280] fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (#37499) * fix: account for case-insensitive database primary key for parameter names * chore: linting --- .../convert_qi_parameter_to_link_field.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py index efbb96c100..e53bdf8f19 100644 --- a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py +++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py @@ -3,23 +3,24 @@ import frappe def execute(): frappe.reload_doc("stock", "doctype", "quality_inspection_parameter") + params = set() - # get all distinct parameters from QI readigs table - reading_params = frappe.db.get_all( - "Quality Inspection Reading", fields=["distinct specification"] - ) - reading_params = [d.specification for d in reading_params] + # get all parameters from QI readings table + for (p,) in frappe.db.get_all( + "Quality Inspection Reading", fields=["specification"], as_list=True + ): + params.add(p.strip()) - # get all distinct parameters from QI Template as some may be unused in QI - template_params = frappe.db.get_all( - "Item Quality Inspection Parameter", fields=["distinct specification"] - ) - template_params = [d.specification for d in template_params] + # get all parameters from QI Template as some may be unused in QI + for (p,) in frappe.db.get_all( + "Item Quality Inspection Parameter", fields=["specification"], as_list=True + ): + params.add(p.strip()) - params = list(set(reading_params + template_params)) + # because db primary keys are case insensitive, so duplicates will cause an exception + params = set({x.casefold(): x for x in params}.values()) for parameter in params: - if not frappe.db.exists("Quality Inspection Parameter", parameter): - frappe.get_doc( - {"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter} - ).insert(ignore_permissions=True) + frappe.get_doc( + {"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter} + ).insert(ignore_permissions=True) From 17ebc1ea8096a83e882bedd00f358a4bd799eff4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 23 Oct 2023 11:56:28 +0530 Subject: [PATCH 187/280] fix: validate so item with qtn --- erpnext/selling/doctype/sales_order/sales_order.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b91002eb86..76e2f8b78a 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -216,7 +216,15 @@ class SalesOrder(SellingController): def validate_with_previous_doc(self): super(SalesOrder, self).validate_with_previous_doc( - {"Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]}} + { + "Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]}, + "Quotation Item": { + "ref_dn_field": "quotation_item", + "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]], + "is_child_table": True, + "allow_duplicate_prev_row_id": True, + }, + } ) if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")): From 7e600a6494d7f07c6fd2b8f1cc71857801a2573c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 22 Oct 2023 20:26:45 +0530 Subject: [PATCH 188/280] refactor: pass gain loss posting date to controller --- .../payment_reconciliation/payment_reconciliation.js | 2 ++ .../payment_reconciliation/payment_reconciliation.py | 2 ++ .../payment_reconciliation_allocation.json | 1 + erpnext/accounts/utils.py | 4 +++- erpnext/controllers/accounts_controller.py | 6 ++++-- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 7599b5e852..fc90c3dec0 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -293,6 +293,8 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo args.forEach(d => { frappe.model.set_value("Payment Reconciliation Allocation", d.docname, "difference_account", d.difference_account); + frappe.model.set_value("Payment Reconciliation Allocation", d.docname, + "gain_loss_posting_date", d.gain_loss_posting_date); }); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 3285a529d2..1626f25f3e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -328,6 +328,7 @@ class PaymentReconciliation(Document): res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"]) res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") + res.update({"gain_loss_posting_date": pay.get("posting_date")}) if pay.get("amount") == 0: entries.append(res) @@ -434,6 +435,7 @@ class PaymentReconciliation(Document): "allocated_amount": flt(row.get("allocated_amount")), "difference_amount": flt(row.get("difference_amount")), "difference_account": row.get("difference_account"), + "difference_posting_date": row.get("gain_loss_posting_date"), "cost_center": row.get("cost_center"), } ) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 2fddd85732..5b8556e7c8 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -19,6 +19,7 @@ "is_advance", "section_break_5", "difference_amount", + "gain_loss_posting_date", "column_break_7", "difference_account", "exchange_rate", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 555ed4ffa2..f2691fb980 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -679,7 +679,9 @@ def update_reference_in_payment_entry( if not skip_ref_details_update_for_pe: payment_entry.set_missing_ref_details() payment_entry.set_amounts() - payment_entry.make_exchange_gain_loss_journal() + payment_entry.make_exchange_gain_loss_journal( + frappe._dict({"difference_posting_date": d.difference_posting_date}) + ) if not do_not_save: payment_entry.save(ignore_permissions=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cc5d643c14..6efe631a29 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1178,7 +1178,9 @@ class AccountsController(TransactionBase): self.name, arg.get("referenced_row"), ): - posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date") + posting_date = arg.get("difference_posting_date") or frappe.db.get_value( + arg.voucher_type, arg.voucher_no, "posting_date" + ) je = create_gain_loss_journal( self.company, posting_date, @@ -1261,7 +1263,7 @@ class AccountsController(TransactionBase): je = create_gain_loss_journal( self.company, - self.posting_date, + args.get("difference_posting_date") if args else self.posting_date, self.party_type, self.party, party_account, From 514d5434a3ae24e2c7839fbd76a115d6c0841513 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 23 Oct 2023 12:32:10 +0530 Subject: [PATCH 189/280] test: varying posting date for gain loss journal --- .../tests/test_accounts_controller.py | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 391258fde7..97d3c5c32d 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -7,7 +7,7 @@ import frappe from frappe import qb from frappe.query_builder.functions import Sum from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowdate +from frappe.utils import add_days, flt, getdate, nowdate from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry @@ -614,6 +614,73 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) + def test_15_gain_loss_on_different_posting_date(self): + # Invoice in Foreign Currency + si = self.create_sales_invoice( + posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1 + ) + # Payment + pe = ( + self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75) + .save() + .submit() + ) + + # There should be outstanding in both currencies + si.reload() + self.assertEqual(si.outstanding_amount, 2) + self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0) + + # Reconcile the remaining amount + pr = frappe.get_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Customer" + pr.party = self.customer + pr.receivable_payable_account = self.debit_usd + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1) + pr.reconcile() + + # Exchange Gain/Loss Journal should've been created. + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertNotEqual(exc_je_for_si, []) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_pe), 1) + self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0]) + + self.assertEqual( + frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"), + getdate(add_days(nowdate(), 1)), + ) + + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # There should be no outstanding + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Cancel Payment + pe.reload() + pe.cancel() + + si.reload() + self.assertEqual(si.outstanding_amount, 2) + self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertEqual(exc_je_for_si, []) + self.assertEqual(exc_je_for_pe, []) + def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) From 2b64e1ca8b3e9162d4320552e1104385f010841f Mon Sep 17 00:00:00 2001 From: Imesha Sudasingha Date: Mon, 23 Oct 2023 15:28:52 +0530 Subject: [PATCH 190/280] chore: typo in description (#37636) chore: typo in description --- erpnext/stock/doctype/item/item.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 54491bbee3..c13d3ebe0f 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -379,7 +379,7 @@ "options": "fa fa-rss" }, { - "description": "Will also apply for variants unless overrridden", + "description": "Will also apply for variants unless overridden", "fieldname": "reorder_levels", "fieldtype": "Table", "label": "Reorder level based on Warehouse", @@ -961,4 +961,4 @@ "states": [], "title_field": "item_name", "track_changes": 1 -} \ No newline at end of file +} From a432290a828478265a8a463d05aea818c2b75914 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 23 Oct 2023 16:26:00 +0530 Subject: [PATCH 191/280] fix: ignore qty msg if From Voucher is set --- .../stock_reservation_entry.py | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index c7a9e16d0e..81e9dfa69b 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -833,13 +833,15 @@ def create_stock_reservation_entries_for_so_items( # Skip if Non-Stock Item. if not is_stock_item: - frappe.msgprint( - _("Row #{0}: Stock cannot be reserved for a non-stock Item {1}").format( - item.idx, frappe.bold(item.item_code) - ), - title=_("Stock Reservation"), - indicator="yellow", - ) + if not from_voucher_type: + frappe.msgprint( + _("Row #{0}: Stock cannot be reserved for a non-stock Item {1}").format( + item.idx, frappe.bold(item.item_code) + ), + title=_("Stock Reservation"), + indicator="yellow", + ) + item.db_set("reserve_stock", 0) continue @@ -858,13 +860,15 @@ def create_stock_reservation_entries_for_so_items( # Stock is already reserved for the item, notify the user and skip the item. if unreserved_qty <= 0: - frappe.msgprint( - _("Row #{0}: Stock is already reserved for the Item {1}.").format( - item.idx, frappe.bold(item.item_code) - ), - title=_("Stock Reservation"), - indicator="yellow", - ) + if not from_voucher_type: + frappe.msgprint( + _("Row #{0}: Stock is already reserved for the Item {1}.").format( + item.idx, frappe.bold(item.item_code) + ), + title=_("Stock Reservation"), + indicator="yellow", + ) + continue available_qty_to_reserve = get_available_qty_to_reserve(item.item_code, item.warehouse) @@ -872,7 +876,7 @@ def create_stock_reservation_entries_for_so_items( # No stock available to reserve, notify the user and skip the item. if available_qty_to_reserve <= 0: frappe.msgprint( - _("Row #{0}: No available stock to reserve for the Item {1} in Warehouse {2}.").format( + _("Row #{0}: Stock not available to reserve for the Item {1} in Warehouse {2}.").format( item.idx, frappe.bold(item.item_code), frappe.bold(item.warehouse) ), title=_("Stock Reservation"), @@ -898,7 +902,9 @@ def create_stock_reservation_entries_for_so_items( # Partial Reservation if qty_to_be_reserved < unreserved_qty: - if not item.get("qty_to_reserve") or qty_to_be_reserved < flt(item.get("qty_to_reserve")): + if not from_voucher_type and ( + not item.get("qty_to_reserve") or qty_to_be_reserved < flt(item.get("qty_to_reserve")) + ): msg = _("Row #{0}: Only {1} available to reserve for the Item {2}").format( item.idx, frappe.bold(str(qty_to_be_reserved / item.conversion_factor) + " " + item.uom), From adf313a6d3308f957b4876574e455ca750b22106 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 23 Oct 2023 17:52:59 +0530 Subject: [PATCH 192/280] test: add test case for auto-reservation from PR --- .../test_stock_reservation_entry.py | 89 ++++++++++++++++++- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 9ea35ecacb..f4c74a8aac 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -5,6 +5,7 @@ from random import randint import frappe from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import today from erpnext.selling.doctype.sales_order.sales_order import create_pick_list, make_delivery_note from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order @@ -28,10 +29,6 @@ class TestStockReservationEntry(FrappeTestCase): items={self.sr_item.name: self.sr_item}, warehouse=self.warehouse, qty=100 ) - def tearDown(self) -> None: - cancel_all_stock_reservation_entries() - return super().tearDown() - @change_settings("Stock Settings", {"allow_negative_stock": 0}) def test_validate_stock_reservation_settings(self) -> None: from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( @@ -568,6 +565,90 @@ class TestStockReservationEntry(FrappeTestCase): # Test - 3: Reserved Serial/Batch Nos should be equal to Picked Serial/Batch Nos. self.assertSetEqual(picked_sb_details, reserved_sb_details) + @change_settings( + "Stock Settings", + { + "allow_negative_stock": 0, + "enable_stock_reservation": 1, + "auto_reserve_serial_and_batch": 1, + "pick_serial_and_batch_based_on": "FIFO", + "auto_reserve_stock_for_sales_order_on_purchase": 1, + }, + ) + def test_stock_reservation_from_purchase_receipt(self): + from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt + from erpnext.selling.doctype.sales_order.sales_order import make_material_request + from erpnext.stock.doctype.material_request.material_request import make_purchase_order + + items_details = create_items() + create_material_receipt(items_details, self.warehouse, qty=10) + + item_list = [] + for item_code, properties in items_details.items(): + item_list.append( + { + "item_code": item_code, + "warehouse": self.warehouse, + "qty": randint(11, 100), + "uom": properties.stock_uom, + "rate": randint(10, 400), + } + ) + + so = make_sales_order( + item_list=item_list, + warehouse=self.warehouse, + ) + + mr = make_material_request(so.name) + mr.schedule_date = today() + mr.save().submit() + + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.save().submit() + + pr = make_purchase_receipt(po.name) + pr.save().submit() + + for item in pr.items: + sre, status, reserved_qty = frappe.db.get_value( + "Stock Reservation Entry", + { + "from_voucher_type": "Purchase Receipt", + "from_voucher_no": pr.name, + "from_voucher_detail_no": item.name, + }, + ["name", "status", "reserved_qty"], + ) + + # Test - 1: SRE status should be `Reserved`. + self.assertEqual(status, "Reserved") + + # Test - 2: SRE Reserved Qty should be equal to PR Item Qty. + self.assertEqual(reserved_qty, item.qty) + + if item.serial_and_batch_bundle: + sb_details = frappe.db.get_all( + "Serial and Batch Entry", + filters={"parent": item.serial_and_batch_bundle}, + fields=["serial_no", "batch_no", "qty"], + as_list=True, + ) + reserved_sb_details = frappe.db.get_all( + "Serial and Batch Entry", + filters={"parent": sre}, + fields=["serial_no", "batch_no", "qty"], + as_list=True, + ) + + # Test - 3: Reserved Serial/Batch Nos should be equal to PR Item Serial/Batch Nos. + self.assertEqual(set(sb_details), set(reserved_sb_details)) + + def tearDown(self) -> None: + cancel_all_stock_reservation_entries() + return super().tearDown() + def create_items() -> dict: items_properties = [ From 24788ddcc085fb825d2b14145a82ced02842f512 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 23 Oct 2023 18:00:58 +0530 Subject: [PATCH 193/280] chore: add SRE link in PR Connections --- .../doctype/purchase_receipt/purchase_receipt_dashboard.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py index b3ae7b58b4..71489fbb49 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py @@ -10,6 +10,7 @@ def get_data(): "Landed Cost Voucher": "receipt_document", "Auto Repeat": "reference_document", "Purchase Receipt": "return_against", + "Stock Reservation Entry": "from_voucher_no", }, "internal_links": { "Material Request": ["items", "material_request"], @@ -18,7 +19,10 @@ def get_data(): "Quality Inspection": ["items", "quality_inspection"], }, "transactions": [ - {"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]}, + { + "label": _("Related"), + "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset", "Stock Reservation Entry"], + }, { "label": _("Reference"), "items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"], From 3bfb7b79f297ff1ab6c80c810431ecebd6bedecb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 23 Oct 2023 18:23:45 +0530 Subject: [PATCH 194/280] refactor: Remove expense included in valuation accounts (#37632) * refactor: Remove expense included in valuation accounts * test: Deprecate tests * test: Depricate tests * test: Depricate tests --- .../purchase_invoice/purchase_invoice.py | 39 +-------- .../sales_invoice/test_sales_invoice.py | 36 -------- erpnext/manufacturing/doctype/bom/bom.py | 8 +- ...se_account_in_landed_cost_voucher_taxes.py | 42 ---------- erpnext/setup/doctype/company/company.js | 3 - erpnext/setup/doctype/company/company.json | 20 +---- erpnext/setup/doctype/company/company.py | 3 - .../purchase_receipt/purchase_receipt.py | 14 +--- .../purchase_receipt/test_purchase_receipt.py | 82 ------------------- .../doctype/stock_entry/test_stock_entry.py | 12 +-- .../subcontracting_receipt.py | 6 +- 11 files changed, 17 insertions(+), 248 deletions(-) delete mode 100644 erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2f08b65ac6..97ee5cc93b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -585,13 +585,12 @@ class PurchaseInvoice(BuyingController): def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) + self.asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") + if self.auto_accounting_for_stock: self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") - self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - self.asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") else: self.stock_received_but_not_billed = None - self.expenses_included_in_valuation = None self.negative_expense_to_be_booked = 0.0 gl_entries = [] @@ -913,40 +912,6 @@ class PurchaseInvoice(BuyingController): ) ) - # If asset is bought through this document and not linked to PR - if self.update_stock and item.landed_cost_voucher_amount: - expenses_included_in_asset_valuation = self.get_company_default( - "expenses_included_in_asset_valuation" - ) - # Amount added through landed-cost-voucher - gl_entries.append( - self.get_gl_dict( - { - "account": expenses_included_in_asset_valuation, - "against": expense_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - - gl_entries.append( - self.get_gl_dict( - { - "account": expense_account, - "against": expenses_included_in_asset_valuation, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - # update gross amount of asset bought through this document assets = frappe.db.get_all( "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 16477324e6..231b3bf7fe 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2500,12 +2500,6 @@ class TestSalesInvoice(FrappeTestCase): "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1", ) - frappe.db.set_value( - "Company", - "_Test Company 1", - "expenses_included_in_valuation", - "Expenses Included In Valuation - _TC1", - ) # begin test si = create_sales_invoice( @@ -2545,36 +2539,6 @@ class TestSalesInvoice(FrappeTestCase): frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock) - def test_sle_for_target_warehouse(self): - se = make_stock_entry( - item_code="138-CMS Shoe", - target="Finished Goods - _TC", - company="_Test Company", - qty=1, - basic_rate=500, - ) - - si = frappe.copy_doc(test_records[0]) - si.update_stock = 1 - si.set_warehouse = "Finished Goods - _TC" - si.set_target_warehouse = "Stores - _TC" - si.get("items")[0].warehouse = "Finished Goods - _TC" - si.get("items")[0].target_warehouse = "Stores - _TC" - si.insert() - si.submit() - - sles = frappe.get_all( - "Stock Ledger Entry", filters={"voucher_no": si.name}, fields=["name", "actual_qty"] - ) - - # check if both SLEs are created - self.assertEqual(len(sles), 2) - self.assertEqual(sum(d.actual_qty for d in sles), 0.0) - - # tear down - si.cancel() - se.cancel() - def test_internal_transfer_gl_entry(self): si = create_sales_invoice( company="_Test Company with perpetual inventory", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 023166849d..229f8853ff 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1196,12 +1196,12 @@ def get_children(parent=None, is_root=False, **filters): def add_additional_cost(stock_entry, work_order): # Add non stock items cost in the additional cost stock_entry.additional_costs = [] - expenses_included_in_valuation = frappe.get_cached_value( - "Company", work_order.company, "expenses_included_in_valuation" + default_expense_account = frappe.get_cached_value( + "Company", work_order.company, "default_expense_account" ) - add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation) - add_operations_cost(stock_entry, work_order, expenses_included_in_valuation) + add_non_stock_items_cost(stock_entry, work_order, default_expense_account) + add_operations_cost(stock_entry, work_order, default_expense_account) def add_non_stock_items_cost(stock_entry, work_order, expense_account): diff --git a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py b/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py deleted file mode 100644 index 9588e026d3..0000000000 --- a/erpnext/patches/v12_0/set_expense_account_in_landed_cost_voucher_taxes.py +++ /dev/null @@ -1,42 +0,0 @@ -import frappe - - -def execute(): - frappe.reload_doctype("Landed Cost Taxes and Charges") - - company_account_map = frappe._dict( - frappe.db.sql( - """ - SELECT name, expenses_included_in_valuation from `tabCompany` - """ - ) - ) - - for company, account in company_account_map.items(): - frappe.db.sql( - """ - UPDATE - `tabLanded Cost Taxes and Charges` t, `tabLanded Cost Voucher` l - SET - t.expense_account = %s - WHERE - l.docstatus = 1 - AND l.company = %s - AND t.parent = l.name - """, - (account, company), - ) - - frappe.db.sql( - """ - UPDATE - `tabLanded Cost Taxes and Charges` t, `tabStock Entry` s - SET - t.expense_account = %s - WHERE - s.docstatus = 1 - AND s.company = %s - AND t.parent = s.name - """, - (account, company), - ) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 4973dab505..23b93dc161 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -221,7 +221,6 @@ erpnext.company.setup_queries = function(frm) { ["cost_center", {}], ["round_off_cost_center", {}], ["depreciation_cost_center", {}], - ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], @@ -236,8 +235,6 @@ erpnext.company.setup_queries = function(frm) { $.each([ ["stock_adjustment_account", {"root_type": "Expense", "account_type": "Stock Adjustment"}], - ["expenses_included_in_valuation", - {"root_type": "Expense", "account_type": "Expenses Included in Valuation"}], ["stock_received_but_not_billed", {"root_type": "Liability", "account_type": "Stock Received But Not Billed"}], ["service_received_but_not_billed", diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 24d7da45b8..b9ff3dddd1 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -80,7 +80,6 @@ "accumulated_depreciation_account", "depreciation_expense_account", "series_for_depreciation_entry", - "expenses_included_in_asset_valuation", "column_break_40", "disposal_account", "depreciation_cost_center", @@ -103,11 +102,10 @@ "enable_provisional_accounting_for_non_stock_items", "default_inventory_account", "stock_adjustment_account", - "default_in_transit_warehouse", "column_break_32", "stock_received_but_not_billed", "default_provisional_account", - "expenses_included_in_valuation", + "default_in_transit_warehouse", "dashboard_tab" ], "fields": [ @@ -469,14 +467,6 @@ "no_copy": 1, "options": "Account" }, - { - "fieldname": "expenses_included_in_valuation", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Expenses Included In Valuation", - "no_copy": 1, - "options": "Account" - }, { "fieldname": "accumulated_depreciation_account", "fieldtype": "Link", @@ -496,12 +486,6 @@ "fieldtype": "Data", "label": "Series for Asset Depreciation Entry (Journal Entry)" }, - { - "fieldname": "expenses_included_in_asset_valuation", - "fieldtype": "Link", - "label": "Expenses Included In Asset Valuation", - "options": "Account" - }, { "fieldname": "column_break_40", "fieldtype": "Column Break" @@ -782,7 +766,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-09-10 21:53:13.860791", + "modified": "2023-10-23 10:19:24.322898", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index b05696ad96..3413702c5a 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -92,7 +92,6 @@ class Company(NestedSet): ["Default Income Account", "default_income_account"], ["Stock Received But Not Billed Account", "stock_received_but_not_billed"], ["Stock Adjustment Account", "stock_adjustment_account"], - ["Expense Included In Valuation Account", "expenses_included_in_valuation"], ] for account in accounts: @@ -384,7 +383,6 @@ class Company(NestedSet): "depreciation_expense_account": "Depreciation", "capital_work_in_progress_account": "Capital Work in Progress", "asset_received_but_not_billed": "Asset Received But Not Billed", - "expenses_included_in_asset_valuation": "Expenses Included In Asset Valuation", "default_expense_account": "Cost of Goods Sold", } @@ -394,7 +392,6 @@ class Company(NestedSet): "stock_received_but_not_billed": "Stock Received But Not Billed", "default_inventory_account": "Stock", "stock_adjustment_account": "Stock Adjustment", - "expenses_included_in_valuation": "Expenses Included In Valuation", } ) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 9fdb01a662..d89d8057a8 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -491,7 +491,6 @@ class PurchaseReceipt(BuyingController): return # divisional loss adjustment - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") valuation_amount_as_per_doc = ( flt(outgoing_amount, d.precision("base_net_amount")) + flt(item.landed_cost_voucher_amount) @@ -505,13 +504,10 @@ class PurchaseReceipt(BuyingController): ) if divisional_loss: - if self.is_return or flt(item.item_tax_amount): - loss_account = expenses_included_in_valuation - else: - loss_account = ( - self.get_company_default("default_expense_account", ignore_validation=True) - or stock_asset_rbnb - ) + loss_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or stock_asset_rbnb + ) cost_center = item.cost_center or frappe.get_cached_value( "Company", self.company, "cost_center" @@ -684,10 +680,8 @@ class PurchaseReceipt(BuyingController): if negative_expense_to_be_booked and valuation_tax: # Backward compatibility: - # If expenses_included_in_valuation account has been credited in against PI # and charges added via Landed Cost Voucher, # post valuation related charges on "Stock Received But Not Billed" - # introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 1af7b9aefc..e998b842d1 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -957,88 +957,6 @@ class TestPurchaseReceipt(FrappeTestCase): pr1.reload() pr1.cancel() - def test_stock_transfer_from_purchase_receipt(self): - pr1 = make_purchase_receipt( - warehouse="Work In Progress - TCP1", company="_Test Company with perpetual inventory" - ) - - pr = make_purchase_receipt( - company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1 - ) - - pr.supplier_warehouse = "" - pr.items[0].from_warehouse = "Work In Progress - TCP1" - - pr.submit() - - gl_entries = get_gl_entries("Purchase Receipt", pr.name) - sl_entries = get_sl_entries("Purchase Receipt", pr.name) - - self.assertFalse(gl_entries) - - expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5} - - for sle in sl_entries: - self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) - - pr.cancel() - pr1.cancel() - - def test_stock_transfer_from_purchase_receipt_with_valuation(self): - create_warehouse( - "_Test Warehouse for Valuation", - company="_Test Company with perpetual inventory", - properties={"account": "_Test Account Stock In Hand - TCP1"}, - ) - - pr1 = make_purchase_receipt( - warehouse="_Test Warehouse for Valuation - TCP1", - company="_Test Company with perpetual inventory", - ) - - pr = make_purchase_receipt( - company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1 - ) - - pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1" - pr.supplier_warehouse = "" - - pr.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account Shipping Charges - TCP1", - "category": "Valuation and Total", - "cost_center": "Main - TCP1", - "description": "Test", - "rate": 9, - }, - ) - - pr.submit() - - gl_entries = get_gl_entries("Purchase Receipt", pr.name) - sl_entries = get_sl_entries("Purchase Receipt", pr.name) - - expected_gle = [ - ["Stock In Hand - TCP1", 272.5, 0.0], - ["_Test Account Stock In Hand - TCP1", 0.0, 250.0], - ["_Test Account Shipping Charges - TCP1", 0.0, 22.5], - ] - - expected_sle = {"_Test Warehouse for Valuation - TCP1": -5, "Stores - TCP1": 5} - - for sle in sl_entries: - self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) - - for i, gle in enumerate(gl_entries): - self.assertEqual(gle.account, expected_gle[i][0]) - self.assertEqual(gle.debit, expected_gle[i][1]) - self.assertEqual(gle.credit, expected_gle[i][2]) - - pr.cancel() - pr1.cancel() - def test_po_to_pi_and_po_to_pr_worflow_full(self): """Test following behaviour: - Create PO diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index cc8a108bc9..3e0610ef6e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -449,9 +449,7 @@ class TestStockEntry(FrappeTestCase): repack.posting_date = nowdate() repack.posting_time = nowtime() - expenses_included_in_valuation = frappe.get_value( - "Company", company, "expenses_included_in_valuation" - ) + default_expense_account = frappe.get_value("Company", company, "default_expense_account") items = get_multiple_items() repack.items = [] @@ -462,12 +460,12 @@ class TestStockEntry(FrappeTestCase): "additional_costs", [ { - "expense_account": expenses_included_in_valuation, + "expense_account": default_expense_account, "description": "Actual Operating Cost", "amount": 1000, }, { - "expense_account": expenses_included_in_valuation, + "expense_account": default_expense_account, "description": "Additional Operating Cost", "amount": 200, }, @@ -506,9 +504,7 @@ class TestStockEntry(FrappeTestCase): self.check_gl_entries( "Stock Entry", repack.name, - sorted( - [[stock_in_hand_account, 1200, 0.0], ["Expenses Included In Valuation - TCP1", 0.0, 1200.0]] - ), + sorted([[stock_in_hand_account, 1200, 0.0], ["Cost of Goods Sold - TCP1", 0.0, 1200.0]]), ) def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 6aecaf98a5..7e06444e1e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -410,7 +410,6 @@ class SubcontractingReceipt(SubcontractingController): def make_item_gl_entries(self, gl_entries, warehouse_account=None): stock_rbnb = self.get_company_default("stock_received_but_not_billed") - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") warehouse_with_no_account = [] @@ -482,10 +481,7 @@ class SubcontractingReceipt(SubcontractingController): divisional_loss = flt(item.amount - stock_value_diff, item.precision("amount")) if divisional_loss: - if self.is_return: - loss_account = expenses_included_in_valuation - else: - loss_account = item.expense_account + loss_account = item.expense_account self.add_gl_entry( gl_entries=gl_entries, From 6942ab10125cfaf07c526df53a7c88bffcc5b9da Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 23 Oct 2023 19:12:55 +0530 Subject: [PATCH 195/280] chore: patch to update `From Voucher` details --- erpnext/patches.txt | 1 + .../v15_0/update_sre_from_voucher_details.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 erpnext/patches/v15_0/update_sre_from_voucher_details.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d59fe0ec4c..53bddb562c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -340,5 +340,6 @@ erpnext.patches.v14_0.update_invoicing_period_in_subscription execute:frappe.delete_doc("Page", "welcome-to-erpnext") erpnext.patches.v15_0.delete_payment_gateway_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item +erpnext.patches.v15_0.update_sre_from_voucher_details # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger \ No newline at end of file diff --git a/erpnext/patches/v15_0/update_sre_from_voucher_details.py b/erpnext/patches/v15_0/update_sre_from_voucher_details.py new file mode 100644 index 0000000000..a9653ccbf4 --- /dev/null +++ b/erpnext/patches/v15_0/update_sre_from_voucher_details.py @@ -0,0 +1,15 @@ +import frappe +from frappe.query_builder.functions import IfNull + + +def execute(): + sre = frappe.qb.DocType("Stock Reservation Entry") + ( + frappe.qb.update(sre) + .set(sre.from_voucher_type, "Pick List") + .set(sre.from_voucher_no, sre.against_pick_list) + .set(sre.from_voucher_detail_no, sre.against_pick_list_item) + .where( + (IfNull(sre.against_pick_list, "") != "") & (IfNull(sre.against_pick_list_item, "") != "") + ) + ).run() From 9ef26e1df00311e248989eb823413448fa757d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Mon, 23 Oct 2023 19:41:55 +0200 Subject: [PATCH 196/280] chore: Fix typo followup to #37636 --- erpnext/translations/af.csv | 2 +- erpnext/translations/am.csv | 2 +- erpnext/translations/ar.csv | 2 +- erpnext/translations/bg.csv | 2 +- erpnext/translations/bn.csv | 2 +- erpnext/translations/bs.csv | 2 +- erpnext/translations/ca.csv | 2 +- erpnext/translations/cs.csv | 2 +- erpnext/translations/cz.csv | 2 +- erpnext/translations/da.csv | 2 +- erpnext/translations/de.csv | 2 +- erpnext/translations/el.csv | 2 +- erpnext/translations/es.csv | 2 +- erpnext/translations/et.csv | 2 +- erpnext/translations/fa.csv | 2 +- erpnext/translations/fi.csv | 2 +- erpnext/translations/fr.csv | 2 +- erpnext/translations/gu.csv | 2 +- erpnext/translations/he.csv | 2 +- erpnext/translations/hi.csv | 2 +- erpnext/translations/hr.csv | 2 +- erpnext/translations/hu.csv | 2 +- erpnext/translations/id.csv | 2 +- erpnext/translations/is.csv | 2 +- erpnext/translations/it.csv | 2 +- erpnext/translations/ja.csv | 2 +- erpnext/translations/km.csv | 2 +- erpnext/translations/kn.csv | 2 +- erpnext/translations/ko.csv | 2 +- erpnext/translations/ku.csv | 2 +- erpnext/translations/lo.csv | 2 +- erpnext/translations/lt.csv | 2 +- erpnext/translations/lv.csv | 2 +- erpnext/translations/mk.csv | 2 +- erpnext/translations/ml.csv | 2 +- erpnext/translations/mr.csv | 2 +- erpnext/translations/ms.csv | 2 +- erpnext/translations/my.csv | 2 +- erpnext/translations/nl.csv | 2 +- erpnext/translations/no.csv | 2 +- erpnext/translations/pl.csv | 2 +- erpnext/translations/ps.csv | 2 +- erpnext/translations/pt-BR.csv | 2 +- erpnext/translations/pt.csv | 2 +- erpnext/translations/ro.csv | 2 +- erpnext/translations/ru.csv | 2 +- erpnext/translations/rw.csv | 2 +- erpnext/translations/si.csv | 2 +- erpnext/translations/sk.csv | 2 +- erpnext/translations/sl.csv | 2 +- erpnext/translations/sq.csv | 2 +- erpnext/translations/sr.csv | 2 +- erpnext/translations/sv.csv | 2 +- erpnext/translations/sw.csv | 2 +- erpnext/translations/ta.csv | 2 +- erpnext/translations/te.csv | 2 +- erpnext/translations/th.csv | 2 +- erpnext/translations/tr.csv | 2 +- erpnext/translations/uk.csv | 2 +- erpnext/translations/ur.csv | 2 +- erpnext/translations/uz.csv | 2 +- erpnext/translations/vi.csv | 2 +- erpnext/translations/zh-TW.csv | 2 +- erpnext/translations/zh.csv | 2 +- erpnext/translations/zh_tw.csv | 2 +- 65 files changed, 65 insertions(+), 65 deletions(-) diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index d4b823d995..f45731468d 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -7052,7 +7052,7 @@ Moving Average,Beweeg gemiddeld, Warranty Period (in days),Garantie Periode (in dae), Auto re-order,Outo herbestel, Reorder level based on Warehouse,Herbestel vlak gebaseer op Warehouse, -Will also apply for variants unless overrridden,Sal ook aansoek doen vir variante tensy dit oortree word, +Will also apply for variants unless overridden,Sal ook aansoek doen vir variante tensy dit oortree word, Units of Measure,Eenhede van maatreël, Will also apply for variants,Sal ook aansoek doen vir variante, Serial Nos and Batches,Serial Nos and Batches, diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv index 764868d8dc..0453d5d685 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -7052,7 +7052,7 @@ Moving Average,በመውሰድ ላይ አማካኝ, Warranty Period (in days),(ቀናት ውስጥ) የዋስትና ክፍለ ጊዜ, Auto re-order,ራስ-ዳግም-ትዕዛዝ, Reorder level based on Warehouse,መጋዘን ላይ የተመሠረተ አስይዝ ደረጃ, -Will also apply for variants unless overrridden,overrridden በስተቀር ደግሞ ተለዋጮች ማመልከት ይሆን, +Will also apply for variants unless overridden,overridden በስተቀር ደግሞ ተለዋጮች ማመልከት ይሆን, Units of Measure,ይለኩ አሃዶች, Will also apply for variants,በተጨማሪም ተለዋጮች ማመልከት ይሆን, Serial Nos and Batches,ተከታታይ ቁጥሮች እና ቡድኖች, diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index 4e03a18a5c..67b409e7ef 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -7052,7 +7052,7 @@ Moving Average,المتوسط المتحرك, Warranty Period (in days),فترة الضمان (بالأيام), Auto re-order,إعادة ترتيب تلقائي, Reorder level based on Warehouse,مستوى إعادة الطلب بناء على مستودع, -Will also apply for variants unless overrridden,سوف تطبق أيضا على المتغيرات الا اذا تم التغير فوقها, +Will also apply for variants unless overridden,سوف تطبق أيضا على المتغيرات الا اذا تم التغير فوقها, Units of Measure,وحدات القياس, Will also apply for variants,سوف تطبق أيضا على المتغيرات, Serial Nos and Batches,الرقم التسلسلي ودفعات, diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv index 8dff755941..787f81ee7f 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -7052,7 +7052,7 @@ Moving Average,Пълзяща средна стойност, Warranty Period (in days),Гаранционен срок (в дни), Auto re-order,Автоматична повторна поръчка, Reorder level based on Warehouse,Пренареждане равнище въз основа на Warehouse, -Will also apply for variants unless overrridden,"Ще се прилага и за варианти, освен ако overrridden", +Will also apply for variants unless overridden,"Ще се прилага и за варианти, освен ако overridden", Units of Measure,Мерни единици за измерване, Will also apply for variants,Ще се прилага и за варианти, Serial Nos and Batches,Серийни номера и партиди, diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv index 8a698dfca4..69fd08cb51 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -7052,7 +7052,7 @@ Moving Average,চলন্ত গড়, Warranty Period (in days),(দিন) ওয়্যারেন্টি সময়কাল, Auto re-order,অটো পুনরায় আদেশ, Reorder level based on Warehouse,গুদাম উপর ভিত্তি রেকর্ডার স্তর, -Will also apply for variants unless overrridden,Overrridden তবে এছাড়াও ভিন্নতা জন্য আবেদন করতে হবে, +Will also apply for variants unless overridden,Overrridden তবে এছাড়াও ভিন্নতা জন্য আবেদন করতে হবে, Units of Measure,পরিমাপ ইউনিট, Will also apply for variants,এছাড়াও ভিন্নতা জন্য আবেদন করতে হবে, Serial Nos and Batches,সিরিয়াল আমরা এবং ব্যাচ, diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv index 7ba4a883cb..ef680a36ad 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Jamstveni period (u danima), Auto re-order,Autorefiniš reda, Reorder level based on Warehouse,Nivo Ponovno red zasnovan na Skladište, -Will also apply for variants unless overrridden,Primjenjivat će se i za varijante osim overrridden, +Will also apply for variants unless overridden,Primjenjivat će se i za varijante osim overridden, Units of Measure,Jedinice mjere, Will also apply for variants,Primjenjivat će se i za varijante, Serial Nos and Batches,Serijski brojevi i Paketi, diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv index cce1f3a13b..fa545a4c19 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -7052,7 +7052,7 @@ Moving Average,Mitjana Mòbil, Warranty Period (in days),Període de garantia (en dies), Auto re-order,Acte reordenar, Reorder level based on Warehouse,Nivell de comanda basat en Magatzem, -Will also apply for variants unless overrridden,També s'aplicarà per a les variants menys overrridden, +Will also apply for variants unless overridden,També s'aplicarà per a les variants menys overridden, Units of Measure,Unitats de mesura, Will also apply for variants,També s'aplicarà per a les variants, Serial Nos and Batches,Nº de sèrie i lots, diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv index 1072ed31fc..7fb1679f9c 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -7052,7 +7052,7 @@ Moving Average,Klouzavý průměr, Warranty Period (in days),Záruční doba (ve dnech), Auto re-order,Automatické znovuobjednání, Reorder level based on Warehouse,Úroveň Změna pořadí na základě Warehouse, -Will also apply for variants unless overrridden,"Bude platit i pro varianty, pokud nebude přepsáno", +Will also apply for variants unless overridden,"Bude platit i pro varianty, pokud nebude přepsáno", Units of Measure,Jednotky měření, Will also apply for variants,Bude platit i pro varianty, Serial Nos and Batches,Sériové čísla a dávky, diff --git a/erpnext/translations/cz.csv b/erpnext/translations/cz.csv index 270a7104ba..96de062059 100644 --- a/erpnext/translations/cz.csv +++ b/erpnext/translations/cz.csv @@ -1991,7 +1991,7 @@ Maintenance start date can not be before delivery date for Serial No {0},Datum z Actual End Date,Skutečné datum ukončen$1, Applicable To (Role),Vztahující se na (Role) Purpose,Účel, -Will also apply for variants unless overrridden,"Bude platit i pro varianty, pokud nebude přepsáno" +Will also apply for variants unless overridden,"Bude platit i pro varianty, pokud nebude přepsáno" Advances,Zálohy, Approving User cannot be same as user the rule is Applicable To,Schválení Uživatel nemůže být stejná jako uživatel pravidlo se vztahuje na, No of Requested SMS,Počet žádaným SMS, diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv index 138da5d1f2..4eb39609f2 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -7052,7 +7052,7 @@ Moving Average,Glidende gennemsnit, Warranty Period (in days),Garantiperiode (i dage), Auto re-order,Auto genbestil, Reorder level based on Warehouse,Genbestil niveau baseret på Warehouse, -Will also apply for variants unless overrridden,"Vil også gælde for varianter, medmindre overrridden", +Will also apply for variants unless overridden,"Vil også gælde for varianter, medmindre overridden", Units of Measure,Måleenheder, Will also apply for variants,Vil også gælde for varianter, Serial Nos and Batches,Serienummer og partier, diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 79b9574239..c8fc2cf5c4 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -7074,7 +7074,7 @@ Moving Average,Gleitender Durchschnitt, Warranty Period (in days),Garantiefrist (in Tagen), Auto re-order,Automatische Nachbestellung, Reorder level based on Warehouse,Meldebestand auf Basis des Lagers, -Will also apply for variants unless overrridden,"Gilt auch für Varianten, sofern nicht außer Kraft gesetzt", +Will also apply for variants unless overridden,"Gilt auch für Varianten, sofern nicht außer Kraft gesetzt", Units of Measure,Maßeinheiten, Will also apply for variants,Gilt auch für Varianten, Serial Nos and Batches,Seriennummern und Chargen, diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv index 7a83cc5dcd..21fb435901 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -7052,7 +7052,7 @@ Moving Average,Κινητός μέσος, Warranty Period (in days),Περίοδος εγγύησης (σε ημέρες), Auto re-order,Αυτόματη εκ νέου προκειμένου, Reorder level based on Warehouse,Αναδιάταξη επίπεδο με βάση Αποθήκης, -Will also apply for variants unless overrridden,Θα ισχύουν επίσης για τις παραλλαγές εκτός αν υπάρχει υπέρβαση, +Will also apply for variants unless overridden,Θα ισχύουν επίσης για τις παραλλαγές εκτός αν υπάρχει υπέρβαση, Units of Measure,Μονάδες μέτρησης, Will also apply for variants,Θα ισχύουν επίσης για τις παραλλαγές, Serial Nos and Batches,Σειριακοί αριθμοί και παρτίδες, diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv index fadf7a78e2..2abe418707 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -7052,7 +7052,7 @@ Moving Average,Precio medio variable, Warranty Period (in days),Período de garantía (en días), Auto re-order,Ordenar Automáticamente, Reorder level based on Warehouse,Nivel de reabastecimiento basado en almacén, -Will also apply for variants unless overrridden,También se aplicará para las variantes menos que se sobre escriba, +Will also apply for variants unless overridden,También se aplicará para las variantes menos que se sobre escriba, Units of Measure,Unidades de medida, Will also apply for variants,También se aplicará para las variantes, Serial Nos and Batches,Números de serie y lotes, diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv index 4e26a82f85..a4a8736006 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -7052,7 +7052,7 @@ Moving Average,Libisev keskmine, Warranty Period (in days),Garantii Periood (päeva), Auto re-order,Auto ümber korraldada, Reorder level based on Warehouse,Reorder tasandil põhineb Warehouse, -Will also apply for variants unless overrridden,"Kehtib ka variante, kui overrridden", +Will also apply for variants unless overridden,"Kehtib ka variante, kui overridden", Units of Measure,Mõõtühikud, Will also apply for variants,Kehtib ka variandid, Serial Nos and Batches,Serial Nos ning partiid, diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv index 530965d8cf..bd40c8b396 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -7052,7 +7052,7 @@ Moving Average,میانگین متحرک, Warranty Period (in days),دوره گارانتی (در روز), Auto re-order,خودکار دوباره سفارش, Reorder level based on Warehouse,سطح تغییر مجدد ترتیب بر اساس انبار, -Will also apply for variants unless overrridden,همچنین برای انواع اعمال می شود مگر اینکه overrridden, +Will also apply for variants unless overridden,همچنین برای انواع اعمال می شود مگر اینکه overridden, Units of Measure,واحدهای اندازه گیری, Will also apply for variants,همچنین برای انواع اعمال می شود, Serial Nos and Batches,سریال شماره و دسته, diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv index 6e9380cf74..33cf1574ae 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -7052,7 +7052,7 @@ Moving Average,Liukuva keskiarvo, Warranty Period (in days),Takuuaika (päivinä), Auto re-order,Auto re-order, Reorder level based on Warehouse,Varastoon perustuva täydennystilaustaso, -Will also apply for variants unless overrridden,"Sovelletaan myös tuotemalleissa, ellei kumota", +Will also apply for variants unless overridden,"Sovelletaan myös tuotemalleissa, ellei kumota", Units of Measure,Mittayksiköt, Will also apply for variants,Sovelletaan myös tuotemalleissa, Serial Nos and Batches,Sarjanumerot ja Erät, diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index d3875c1132..d15af74d47 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -6650,7 +6650,7 @@ Moving Average,Moyenne Mobile, Warranty Period (in days),Période de Garantie (en jours), Auto re-order,Re-commande auto, Reorder level based on Warehouse,Niveau de réapprovisionnement basé sur l’Entrepôt, -Will also apply for variants unless overrridden,S'appliquera également pour des variantes sauf si remplacé, +Will also apply for variants unless overridden,S'appliquera également pour des variantes sauf si remplacé, Units of Measure,Unités de Mesure, Will also apply for variants,S'appliquera également pour les variantes, Serial Nos and Batches,N° de Série et Lots, diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv index e2de8ce96d..06a3cc64af 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -7052,7 +7052,7 @@ Moving Average,ખસેડવું સરેરાશ, Warranty Period (in days),(દિવસોમાં) વોરંટી સમયગાળા, Auto re-order,ઓટો ફરી ઓર્ડર, Reorder level based on Warehouse,વેરહાઉસ પર આધારિત પુનઃક્રમાંકિત કરો સ્તર, -Will also apply for variants unless overrridden,Overrridden સિવાય પણ ચલો માટે લાગુ પડશે, +Will also apply for variants unless overridden,Overrridden સિવાય પણ ચલો માટે લાગુ પડશે, Units of Measure,માપવા એકમો, Will also apply for variants,પણ ચલો માટે લાગુ પડશે, Serial Nos and Batches,સીરીયલ સંખ્યા અને બૅચેસ, diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv index 6cd900a7cf..d5fcab6454 100644 --- a/erpnext/translations/he.csv +++ b/erpnext/translations/he.csv @@ -7052,7 +7052,7 @@ Moving Average,ממוצע נע, Warranty Period (in days),תקופת אחריות (בימים), Auto re-order,רכב מחדש כדי, Reorder level based on Warehouse,רמת הזמנה חוזרת המבוסס על מחסן, -Will also apply for variants unless overrridden,תחול גם לגרסות אלא אם overrridden, +Will also apply for variants unless overridden,תחול גם לגרסות אלא אם overridden, Units of Measure,יחידות מידה, Will also apply for variants,תחול גם לגרסות, Serial Nos and Batches,מספרים וסידורים סדרתיים, diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv index 388b502c11..a5caa666c2 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -7052,7 +7052,7 @@ Moving Average,चलायमान औसत, Warranty Period (in days),वारंटी अवधि (दिनों में), Auto re-order,ऑटो पुनः आदेश, Reorder level based on Warehouse,गोदाम के आधार पर पुन: व्यवस्थित स्तर, -Will also apply for variants unless overrridden,Overrridden जब तक भी वेरिएंट के लिए लागू होगी, +Will also apply for variants unless overridden,Overrridden जब तक भी वेरिएंट के लिए लागू होगी, Units of Measure,मापन की इकाई, Will also apply for variants,यह भी वेरिएंट के लिए लागू होगी, Serial Nos and Batches,सीरियल नंबर और बैचों, diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv index b44babc7eb..2834602248 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -7052,7 +7052,7 @@ Moving Average,Prosječna ponderirana cijena, Warranty Period (in days),Jamstveni period (u danima), Auto re-order,Automatski reorganiziraj, Reorder level based on Warehouse,Razina redoslijeda na temelju Skladište, -Will also apply for variants unless overrridden,Također će zatražiti varijante osim overrridden, +Will also apply for variants unless overridden,Također će zatražiti varijante osim overridden, Units of Measure,Mjerne jedinice, Will also apply for variants,Također će podnijeti zahtjev za varijante, Serial Nos and Batches,Serijski brojevi i serije, diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv index 4ea5b9a714..a262c8a994 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -7052,7 +7052,7 @@ Moving Average,Mozgóátlag, Warranty Period (in days),Garancia hossza (napokban), Auto re-order,Auto újra-rendelés, Reorder level based on Warehouse,Raktárkészleten alapuló újrerendelési szint, -Will also apply for variants unless overrridden,"Változatokra is alkalmazni fogja, hacsak nem kerül fellülírásra", +Will also apply for variants unless overridden,"Változatokra is alkalmazni fogja, hacsak nem kerül fellülírásra", Units of Measure,Mértékegységek, Will also apply for variants,Változatokra is alkalmazni fogja, Serial Nos and Batches,Sorszámok és kötegek, diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv index d84c3fdcd6..c4e50fdfe5 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Masa Garansi (dalam hari), Auto re-order,Auto re-order, Reorder level based on Warehouse,Tingkat Re-Order berdasarkan Gudang, -Will also apply for variants unless overrridden,Juga akan berlaku untuk varian kecuali tertimpa, +Will also apply for variants unless overridden,Juga akan berlaku untuk varian kecuali tertimpa, Units of Measure,Satuan ukur, Will also apply for variants,Juga akan berlaku untuk varian, Serial Nos and Batches,Nomor Seri dan Partai, diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv index 7687e4a680..50c06ecfbb 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Ábyrgðartímabilið (í dögum), Auto re-order,Auto endurraða, Reorder level based on Warehouse,Uppröðun stigi byggist á Lager, -Will also apply for variants unless overrridden,Mun einnig gilda um afbrigði nema overrridden, +Will also apply for variants unless overridden,Mun einnig gilda um afbrigði nema overridden, Units of Measure,Mælieiningar, Will also apply for variants,Mun einnig gilda fyrir afbrigði, Serial Nos and Batches,Raðnúmer og lotur, diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv index b88cadad36..37608958c3 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -7052,7 +7052,7 @@ Moving Average,Media Mobile, Warranty Period (in days),Periodo di garanzia (in giorni), Auto re-order,Auto riordino, Reorder level based on Warehouse,Livello di riordino sulla base di Magazzino, -Will also apply for variants unless overrridden,Si applica anche per le varianti meno overrridden, +Will also apply for variants unless overridden,Si applica anche per le varianti meno overridden, Units of Measure,Unità di misura, Will also apply for variants,Si applica anche per le varianti, Serial Nos and Batches,Numero e lotti seriali, diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv index 11455bdf7d..888ec800c9 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -7052,7 +7052,7 @@ Moving Average,移動平均, Warranty Period (in days),保証期間(日数), Auto re-order,自動再注文, Reorder level based on Warehouse,倉庫ごとの再注文レベル, -Will also apply for variants unless overrridden,上書きされない限り、バリエーションについても適用されます, +Will also apply for variants unless overridden,上書きされない限り、バリエーションについても適用されます, Units of Measure,測定の単位, Will also apply for variants,バリエーションについても適用されます, Serial Nos and Batches,シリアル番号とバッチ, diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv index 46dcabaa86..d2003c004e 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -7052,7 +7052,7 @@ Moving Average,ជាមធ្យមការផ្លាស់ប្តូរ, Warranty Period (in days),ការធានារយៈពេល (នៅក្នុងថ្ងៃ), Auto re-order,ការបញ្ជាទិញជាថ្មីម្តងទៀតដោយស្វ័យប្រវត្តិ, Reorder level based on Warehouse,កម្រិតនៃការរៀបចំដែលមានមូលដ្ឋានលើឃ្លាំង, -Will also apply for variants unless overrridden,ក៏នឹងអនុវត្តសម្រាប់វ៉ារ្យ៉ង់បានទេលុះត្រាតែ overrridden, +Will also apply for variants unless overridden,ក៏នឹងអនុវត្តសម្រាប់វ៉ារ្យ៉ង់បានទេលុះត្រាតែ overridden, Units of Measure,ឯកតារង្វាស់, Will also apply for variants,ក៏នឹងអនុវត្តសម្រាប់វ៉ារ្យ៉ង់, Serial Nos and Batches,សៀរៀល nos និងជំនាន់, diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv index 18e44a1bb2..72066711ca 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -7052,7 +7052,7 @@ Moving Average,ಸರಾಸರಿ ಮೂವಿಂಗ್, Warranty Period (in days),( ದಿನಗಳಲ್ಲಿ ) ಖಾತರಿ ಅವಧಿಯ, Auto re-order,ಆಟೋ ಪುನಃ ಸಲುವಾಗಿ, Reorder level based on Warehouse,ವೇರ್ಹೌಸ್ ಆಧರಿಸಿ ಮರುಕ್ರಮಗೊಳಿಸಿ ಮಟ್ಟದ, -Will also apply for variants unless overrridden,Overrridden ಹೊರತು ಸಹ ರೂಪಾಂತರಗಳು ಅನ್ವಯವಾಗುವುದು, +Will also apply for variants unless overridden,Overrridden ಹೊರತು ಸಹ ರೂಪಾಂತರಗಳು ಅನ್ವಯವಾಗುವುದು, Units of Measure,ಮಾಪನದ ಘಟಕಗಳಿಗೆ, Will also apply for variants,ಸಹ ರೂಪಾಂತರಗಳು ಅನ್ವಯವಾಗುವುದು, Serial Nos and Batches,ಸೀರಿಯಲ್ ಸೂಲ ಮತ್ತು ಬ್ಯಾಚ್, diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv index 788655c044..99119256c1 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -7052,7 +7052,7 @@ Moving Average,움직임 평균, Warranty Period (in days),(일) 보증 기간, Auto re-order,자동 재 주문, Reorder level based on Warehouse,웨어 하우스를 기반으로 재정렬 수준, -Will also apply for variants unless overrridden,overrridden가 아니면 변형 적용됩니다, +Will also apply for variants unless overridden,overridden가 아니면 변형 적용됩니다, Units of Measure,측정 단위, Will also apply for variants,또한 변형 적용됩니다, Serial Nos and Batches,일련 번호 및 배치, diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv index a7fcf4e1ed..8fec05993d 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Period Warranty (di rojên), Auto re-order,Auto re-da, Reorder level based on Warehouse,asta DIRTYHERTZ li ser Warehouse, -Will also apply for variants unless overrridden,jî wê ji bo Guhertoyên serî heta overrridden, +Will also apply for variants unless overridden,jî wê ji bo Guhertoyên serî heta overridden, Units of Measure,Yekîneyên Measure, Will also apply for variants,jî wê ji bo Guhertoyên serî, Serial Nos and Batches,Serial Nos û lekerên, diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv index 9b74f655b2..0831788651 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -7052,7 +7052,7 @@ Moving Average,ການເຄື່ອນຍ້າຍໂດຍສະເລ່ Warranty Period (in days),ໄລຍະເວລາຮັບປະກັນ (ໃນວັນເວລາ), Auto re-order,Auto Re: ຄໍາສັ່ງ, Reorder level based on Warehouse,ລະດັບລໍາດັບຂຶ້ນຢູ່ກັບຄັງສິນຄ້າ, -Will also apply for variants unless overrridden,ຍັງຈະນໍາໃຊ້ສໍາລັບການ variants ເວັ້ນເສຍແຕ່ວ່າ overrridden, +Will also apply for variants unless overridden,ຍັງຈະນໍາໃຊ້ສໍາລັບການ variants ເວັ້ນເສຍແຕ່ວ່າ overridden, Units of Measure,ຫົວຫນ່ວຍວັດແທກ, Will also apply for variants,ຍັງຈະນໍາໃຊ້ສໍາລັບການ variants, Serial Nos and Batches,Serial Nos ແລະສໍາຫລັບຂະບວນ, diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv index 2c87256d2a..82152754e8 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -7052,7 +7052,7 @@ Moving Average,slenkamasis vidurkis, Warranty Period (in days),Garantinis laikotarpis (dienomis), Auto re-order,Auto naujo užsakymas, Reorder level based on Warehouse,Pertvarkyti lygį remiantis Warehouse, -Will also apply for variants unless overrridden,Bus taikoma variantų nebent overrridden, +Will also apply for variants unless overridden,Bus taikoma variantų nebent overridden, Units of Measure,Matavimo vienetai, Will also apply for variants,Bus taikoma variantų, Serial Nos and Batches,Eilės Nr ir Partijos, diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv index 2cfa1306f1..8c4526ca52 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Garantijas periods (dienās), Auto re-order,Auto re-pasūtīt, Reorder level based on Warehouse,Pārkārtot līmenis balstās uz Noliktava, -Will also apply for variants unless overrridden,"Attieksies arī uz variantiem, ja vien overrridden", +Will also apply for variants unless overridden,"Attieksies arī uz variantiem, ja vien overridden", Units of Measure,Mērvienību, Will also apply for variants,Attieksies arī uz variantiem, Serial Nos and Batches,Sērijas Nr un Partijām, diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv index f01b1b0cf2..a622524bf1 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -7052,7 +7052,7 @@ Moving Average,Се движат просек, Warranty Period (in days),Гарантниот период (во денови), Auto re-order,Автоматско повторно цел, Reorder level based on Warehouse,Ниво врз основа на промените редоследот Магацински, -Will also apply for variants unless overrridden,"Ќе се казни и варијанти, освен ако overrridden", +Will also apply for variants unless overridden,"Ќе се казни и варијанти, освен ако overridden", Units of Measure,На мерните единици, Will also apply for variants,Ќе се применуваат и за варијанти, Serial Nos and Batches,Сериски броеви и Пакетите, diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv index 59d3160d39..777d5c64ff 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -7052,7 +7052,7 @@ Moving Average,ശരാശരി നീക്കുന്നു, Warranty Period (in days),(ദിവസങ്ങളിൽ) വാറന്റി കാലാവധി, Auto re-order,ഓട്ടോ റീ-ഓർഡർ, Reorder level based on Warehouse,വെയർഹൗസ് അടിസ്ഥാനമാക്കിയുള്ള പുനഃക്രമീകരിക്കുക തലത്തിൽ, -Will also apply for variants unless overrridden,കൂടാതെ overrridden അവയൊഴിച്ച് മോഡലുകൾക്കാണ് ബാധകമാകും, +Will also apply for variants unless overridden,കൂടാതെ overridden അവയൊഴിച്ച് മോഡലുകൾക്കാണ് ബാധകമാകും, Units of Measure,അളവിന്റെ യൂണിറ്റുകൾ, Will also apply for variants,കൂടാതെ മോഡലുകൾക്കാണ് ബാധകമാകും, Serial Nos and Batches,സീരിയൽ എണ്ണം ബാച്ചുകളും, diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv index ff339b6d9f..624f1ab481 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -7052,7 +7052,7 @@ Moving Average,हलवित/Moving सरासरी, Warranty Period (in days),(दिवस मध्ये) वॉरंटी कालावधी, Auto re-order,ऑटो पुन्हा आदेश, Reorder level based on Warehouse,वखारवर आधारित पुन्हा क्रमवारी लावा पातळी, -Will also apply for variants unless overrridden,Overrridden आहेत तोपर्यंत देखील रूपे लागू राहील, +Will also apply for variants unless overridden,Overrridden आहेत तोपर्यंत देखील रूपे लागू राहील, Units of Measure,माप युनिट, Will also apply for variants,तसेच रूपे लागू राहील, Serial Nos and Batches,सिरियल क्र आणि बॅच, diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv index 2258a1804f..75e150a88d 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -7052,7 +7052,7 @@ Moving Average,Purata bergerak, Warranty Period (in days),Tempoh jaminan (dalam hari), Auto re-order,Auto semula perintah, Reorder level based on Warehouse,Tahap pesanan semula berdasarkan Warehouse, -Will also apply for variants unless overrridden,Juga akan memohon varian kecuali overrridden, +Will also apply for variants unless overridden,Juga akan memohon varian kecuali overridden, Units of Measure,Unit ukuran, Will also apply for variants,Juga akan memohon varian, Serial Nos and Batches,Serial Nos dan Kelompok, diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv index dc5ab12f43..36cd8740d7 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -7052,7 +7052,7 @@ Moving Average,ပျမ်းမျှ Moving, Warranty Period (in days),(ရက်) ကိုအာမခံကာလ, Auto re-order,မော်တော်ကားပြန်လည်အမိန့်, Reorder level based on Warehouse,ဂိုဒေါင်အပေါ်အခြေခံပြီး reorder level ကို, -Will also apply for variants unless overrridden,စ overrridden မဟုတ်လျှင်မျိုးကွဲလျှောက်ထားလိမ့်မည်ဟု, +Will also apply for variants unless overridden,စ overridden မဟုတ်လျှင်မျိုးကွဲလျှောက်ထားလိမ့်မည်ဟု, Units of Measure,တိုင်း၏ယူနစ်, Will also apply for variants,စမျိုးကွဲလျှောက်ထားလိမ့်မည်ဟု, Serial Nos and Batches,serial Nos နှင့် batch, diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index ad11eae08b..5859833861 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Garantieperiode (in dagen), Auto re-order,Auto re-order, Reorder level based on Warehouse,Bestelniveau gebaseerd op Warehouse, -Will also apply for variants unless overrridden,Geldt ook voor varianten tenzij overrridden, +Will also apply for variants unless overridden,Geldt ook voor varianten tenzij overridden, Units of Measure,Meeteenheden, Will also apply for variants,Geldt ook voor varianten, Serial Nos and Batches,Serienummers en batches, diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv index b136e97954..a3236ac084 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -7052,7 +7052,7 @@ Moving Average,Glidende gjennomsnitt, Warranty Period (in days),Garantiperioden (i dager), Auto re-order,Auto re-order, Reorder level based on Warehouse,Omgjøre nivå basert på Warehouse, -Will also apply for variants unless overrridden,Vil også gjelde for varianter med mindre overrridden, +Will also apply for variants unless overridden,Vil også gjelde for varianter med mindre overridden, Units of Measure,Måleenheter, Will also apply for variants,Vil også gjelde for varianter, Serial Nos and Batches,Serienummer og partier, diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv index 9be56f36f7..df41e39862 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -6987,7 +6987,7 @@ Moving Average,Średnia Ruchoma, Warranty Period (in days),Okres gwarancji (w dniach), Auto re-order,Automatyczne ponowne zamówienie, Reorder level based on Warehouse,Zmiana kolejności w oparciu o poziom Magazynu, -Will also apply for variants unless overrridden,"Również zostanie zastosowany do wariantów, chyba że zostanie nadpisany", +Will also apply for variants unless overridden,"Również zostanie zastosowany do wariantów, chyba że zostanie nadpisany", Units of Measure,Jednostki miary, Will also apply for variants,Również zastosowanie do wariantów, Serial Nos and Batches,Numery seryjne i partie, diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv index 8033752f78..5a0b2a50cb 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -7052,7 +7052,7 @@ Moving Average,حرکت اوسط, Warranty Period (in days),ګرنټی د دورې (په ورځو), Auto re-order,د موټرونو د بيا نظم, Reorder level based on Warehouse,ترمیمي په کچه د پر بنسټ د ګدام, -Will also apply for variants unless overrridden,مګر overrridden به د بېرغونو هم تر غوږو, +Will also apply for variants unless overridden,مګر overridden به د بېرغونو هم تر غوږو, Units of Measure,د اندازه کولو واحدونه, Will also apply for variants,به هم د بېرغونو درخواست, Serial Nos and Batches,سریال وځيري او دستو, diff --git a/erpnext/translations/pt-BR.csv b/erpnext/translations/pt-BR.csv index 9823470afb..bc5b616080 100644 --- a/erpnext/translations/pt-BR.csv +++ b/erpnext/translations/pt-BR.csv @@ -7052,7 +7052,7 @@ Moving Average,Média Móvel, Warranty Period (in days),Período de Garantia (em dias), Auto re-order,Reposição Automática, Reorder level based on Warehouse,Nível de reposição baseado no Armazén, -Will also apply for variants unless overrridden,Também se aplica a variantes a não ser que seja sobrescrito, +Will also apply for variants unless overridden,Também se aplica a variantes a não ser que seja sobrescrito, Units of Measure,Unidades de Medida, Will also apply for variants,Também se aplica às variantes, Serial Nos and Batches,Números de Série e Lotes, diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv index c2afe3216d..e6846c6a37 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -7052,7 +7052,7 @@ Moving Average,Média Móvel, Warranty Period (in days),Período de Garantia (em dias), Auto re-order,Voltar a Pedir Autom., Reorder level based on Warehouse,Nível de reencomenda no Armazém, -Will also apply for variants unless overrridden,Também se aplica para as variantes a menos que seja anulado, +Will also apply for variants unless overridden,Também se aplica para as variantes a menos que seja anulado, Units of Measure,Unidades de medida, Will also apply for variants,Também se aplicará para as variantes, Serial Nos and Batches,Números de série e lotes, diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv index 0cb3f84278..ac7e598e2d 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -7052,7 +7052,7 @@ Moving Average,Mutarea medie, Warranty Period (in days),Perioada de garanție (în zile), Auto re-order,Re-comandă automată, Reorder level based on Warehouse,Nivel pentru re-comanda bazat pe Magazie, -Will also apply for variants unless overrridden,Se va aplica și pentru variantele cu excepția cazului în overrridden, +Will also apply for variants unless overridden,Se va aplica și pentru variantele cu excepția cazului în overridden, Units of Measure,Unitati de masura, Will also apply for variants,"Va aplică, de asemenea pentru variante", Serial Nos and Batches,Numere și loturi seriale, diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index da4e1bef82..52c29982f3 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -6985,7 +6985,7 @@ Moving Average,Скользящее среднее, Warranty Period (in days),Гарантийный период (дней), Auto re-order,Автоматический перезаказ, Reorder level based on Warehouse,Уровень переупорядочивания на основе склада, -Will also apply for variants unless overrridden,"Будет также применяться для модификаций, если не отменено", +Will also apply for variants unless overridden,"Будет также применяться для модификаций, если не отменено", Units of Measure,Единицы измерения, Will also apply for variants,Также применять к модификациям, Serial Nos and Batches,Серийные номера и партии, diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv index ae0cb3a908..f035d57985 100644 --- a/erpnext/translations/rw.csv +++ b/erpnext/translations/rw.csv @@ -7052,7 +7052,7 @@ Moving Average,Impuzandengo, Warranty Period (in days),Igihe cya garanti (muminsi), Auto re-order,Ongera utumire, Reorder level based on Warehouse,Urwego rwo kwisubiramo rushingiye kububiko, -Will also apply for variants unless overrridden,Uzasaba kandi kubitandukanye keretse birenze, +Will also apply for variants unless overridden,Uzasaba kandi kubitandukanye keretse birenze, Units of Measure,Ibipimo, Will also apply for variants,Uzasaba kandi kubitandukanye, Serial Nos and Batches,Urutonde Nomero, diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv index fa6fbf090a..4047263492 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -7052,7 +7052,7 @@ Moving Average,වෙනස්වන සාමාන්යය, Warranty Period (in days),වගකීම් කාලය (දින තුළ), Auto re-order,වාහන නැවත අනුපිළිවෙලට, Reorder level based on Warehouse,ගබඩාව මත පදනම් මොහොත මට්ටමේ, -Will also apply for variants unless overrridden,ද overrridden මිස ප්රභේද සඳහා අයදුම් කරනු ඇත, +Will also apply for variants unless overridden,ද overridden මිස ප්රභේද සඳහා අයදුම් කරනු ඇත, Units of Measure,නු ඒකක, Will also apply for variants,ද ප්රභේද සඳහා අයදුම් කරනු ඇත, Serial Nos and Batches,අනුක්රමික අංක සහ කාණ්ඩ, diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv index 0e51158b27..98e1663f1c 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -7052,7 +7052,7 @@ Moving Average,Klouzavý průměr, Warranty Period (in days),Záruční doba (ve dnech), Auto re-order,Auto re-order, Reorder level based on Warehouse,Úroveň Zmena poradia na základe Warehouse, -Will also apply for variants unless overrridden,"Bude platiť aj pre varianty, pokiaľ nebude prepísané", +Will also apply for variants unless overridden,"Bude platiť aj pre varianty, pokiaľ nebude prepísané", Units of Measure,merné jednotky, Will also apply for variants,Bude platiť aj pre varianty, Serial Nos and Batches,Sériové čísla a dávky, diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv index 7c4e11b63f..5380714bdc 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Garancijski rok (v dnevih), Auto re-order,Auto re-order, Reorder level based on Warehouse,Raven Preureditev temelji na Warehouse, -Will also apply for variants unless overrridden,Bo veljalo tudi za variante razen overrridden, +Will also apply for variants unless overridden,Bo veljalo tudi za variante razen overridden, Units of Measure,Merske enote, Will also apply for variants,Bo veljalo tudi za variante, Serial Nos and Batches,Serijska št in Serije, diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv index 0f10795113..2a893d272b 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Mesatare, Warranty Period (in days),Garanci Periudha (në ditë), Auto re-order,Auto ri-qëllim, Reorder level based on Warehouse,Niveli Reorder bazuar në Magazina, -Will also apply for variants unless overrridden,Gjithashtu do të aplikojë për variantet nëse overrridden, +Will also apply for variants unless overridden,Gjithashtu do të aplikojë për variantet nëse overridden, Units of Measure,Njësitë e masës, Will also apply for variants,Gjithashtu do të aplikojë për variantet, Serial Nos and Batches,Serial Nr dhe Batches, diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv index 38116ecc9e..c1e5eb0eea 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -7052,7 +7052,7 @@ Moving Average,Мовинг Авераге, Warranty Period (in days),Гарантни период (у данима), Auto re-order,Ауто поново реда, Reorder level based on Warehouse,Промени редослед ниво на основу Варехоусе, -Will also apply for variants unless overrridden,Ће конкурисати и за варијанте осим оверрридден, +Will also apply for variants unless overridden,Ће конкурисати и за варијанте осим оверрридден, Units of Measure,Мерних јединица, Will also apply for variants,Ће конкурисати и за варијанте, Serial Nos and Batches,Сериал Нос анд Пакети, diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv index c4d46ea130..8b4ab068eb 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -7052,7 +7052,7 @@ Moving Average,Rörligt medelvärde, Warranty Period (in days),Garantitiden (i dagar), Auto re-order,Auto återbeställning, Reorder level based on Warehouse,Beställningsnivå baserat på Warehouse, -Will also apply for variants unless overrridden,Kommer också att ansöka om varianter såvida inte överskriden, +Will also apply for variants unless overridden,Kommer också att ansöka om varianter såvida inte överskriden, Units of Measure,Måttenheter, Will also apply for variants,Kommer också att ansöka om varianter, Serial Nos and Batches,Serienummer och partier, diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv index ad144f04d6..fa2287c3bb 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -7052,7 +7052,7 @@ Moving Average,Kusonga Wastani, Warranty Period (in days),Kipindi cha udhamini (katika siku), Auto re-order,Rejesha upya, Reorder level based on Warehouse,Weka upya ngazi kulingana na Ghala, -Will also apply for variants unless overrridden,Pia itatumika kwa vipengee isipokuwa imeingizwa, +Will also apply for variants unless overridden,Pia itatumika kwa vipengee isipokuwa imeingizwa, Units of Measure,Units of Measure, Will also apply for variants,Pia itatumika kwa vipengee, Serial Nos and Batches,Serial Nos na Batches, diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv index 5ccabc044d..6eaae34a6e 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -7052,7 +7052,7 @@ Moving Average,சராசரியாக நகர்கிறது, Warranty Period (in days),உத்தரவாதத்தை காலம் (நாட்கள்), Auto re-order,வாகன மறு ஒழுங்கு, Reorder level based on Warehouse,கிடங்கில் அடிப்படையில் மறுவரிசைப்படுத்துக நிலை, -Will also apply for variants unless overrridden,Overrridden வரை கூட வகைகளில் விண்ணப்பிக்க, +Will also apply for variants unless overridden,Overrridden வரை கூட வகைகளில் விண்ணப்பிக்க, Units of Measure,அளவின் அலகுகள், Will also apply for variants,கூட வகைகளில் விண்ணப்பிக்க, Serial Nos and Batches,சீரியல் எண்கள் மற்றும் தொகுப்புகளும், diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv index 8472163af5..d3f739af8b 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -7052,7 +7052,7 @@ Moving Average,మూవింగ్ సగటు, Warranty Period (in days),(రోజుల్లో) వారంటీ వ్యవధి, Auto re-order,ఆటో క్రమాన్ని, Reorder level based on Warehouse,వేర్హౌస్ ఆధారంగా క్రమాన్ని స్థాయి, -Will also apply for variants unless overrridden,Overrridden తప్ప కూడా రూపాంతరాలు వర్తిస్తాయని, +Will also apply for variants unless overridden,Overrridden తప్ప కూడా రూపాంతరాలు వర్తిస్తాయని, Units of Measure,యూనిట్స్ ఆఫ్ మెజర్, Will also apply for variants,కూడా రూపాంతరాలు వర్తిస్తాయని, Serial Nos and Batches,సీరియల్ Nos మరియు ఇస్తున్న, diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv index dcd632bb3c..a065595898 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -7052,7 +7052,7 @@ Moving Average,ค่าเฉลี่ยเคลื่อนที่, Warranty Period (in days),ระยะเวลารับประกัน (วัน), Auto re-order,Auto สั่งซื้อใหม่, Reorder level based on Warehouse,ระดับสั่งซื้อใหม่บนพื้นฐานของคลังสินค้า, -Will also apply for variants unless overrridden,นอกจากนี้ยังจะใช้สำหรับสายพันธุ์เว้นแต่ overrridden, +Will also apply for variants unless overridden,นอกจากนี้ยังจะใช้สำหรับสายพันธุ์เว้นแต่ overridden, Units of Measure,หน่วยวัด, Will also apply for variants,นอกจากนี้ยังจะใช้สำหรับสายพันธุ์, Serial Nos and Batches,หมายเลขและชุดเลขที่ผลิตภัณฑ์, diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index 3708246a9d..9e916f0315 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -7051,7 +7051,7 @@ Moving Average,Hareketli Ortalama, Warranty Period (in days),Garanti Süresi (gün), Auto re-order,Otomatik Yeniden Sipariş, Reorder level based on Warehouse,Depo bazlı Yeniden sipariş seviyesi, -Will also apply for variants unless overrridden,Geçersiz kılınmadığı sürece varyantlar için de geçerli olacaktır., +Will also apply for variants unless overridden,Geçersiz kılınmadığı sürece varyantlar için de geçerli olacaktır., Units of Measure,Ölçü Birimleri, Will also apply for variants,Varyantlar için de geçerli olacak, Serial Nos and Batches,Seri No ve Batches (Parti), diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv index 1752330754..8d1fb04fdb 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, Warranty Period (in days),Гарантійний термін (в днях), Auto re-order,Авто-дозамовлення, Reorder level based on Warehouse,Рівень перезамовлення по складу, -Will also apply for variants unless overrridden,"Буде також застосовуватися для варіантів, якщо не перевказано", +Will also apply for variants unless overridden,"Буде також застосовуватися для варіантів, якщо не перевказано", Units of Measure,одиниці виміру, Will also apply for variants,Буде також застосовуватися для варіантів, Serial Nos and Batches,Серійні номери і Порції, diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv index 504cc8de0c..649c1c7759 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -7052,7 +7052,7 @@ Moving Average,حرکت پذیری اوسط, Warranty Period (in days),(دن میں) وارنٹی مدت, Auto re-order,آٹو دوبارہ آرڈر, Reorder level based on Warehouse,گودام کی بنیاد پر ترتیب کی سطح کو منتخب, -Will also apply for variants unless overrridden,overrridden جب تک بھی مختلف حالتوں کے لئے لاگو ہوں گے, +Will also apply for variants unless overridden,overridden جب تک بھی مختلف حالتوں کے لئے لاگو ہوں گے, Units of Measure,پیمائش کی اکائیوں, Will also apply for variants,بھی مختلف حالتوں کے لئے لاگو ہوں گے, Serial Nos and Batches,سیریل نمبر اور بیچوں, diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv index 6b25e7b152..5ca51ccb0f 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -7052,7 +7052,7 @@ Moving Average,O'rtacha harakatlanuvchi, Warranty Period (in days),Kafolat muddati (kunlar), Auto re-order,Avtomatik buyurtma, Reorder level based on Warehouse,Qoidalarga asoslangan qayta tartiblash, -Will also apply for variants unless overrridden,"Variantlar uchun ham qo'llanilmaydi, agar bekor qilinsa", +Will also apply for variants unless overridden,"Variantlar uchun ham qo'llanilmaydi, agar bekor qilinsa", Units of Measure,O'lchov birliklari, Will also apply for variants,"Shuningdek, variantlar uchun ham qo'llaniladi", Serial Nos and Batches,Seriya nos va paketlar, diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv index e576ef7119..7a005fa414 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -7052,7 +7052,7 @@ Moving Average,Di chuyển trung bình, Warranty Period (in days),Thời gian bảo hành (trong...ngày), Auto re-order,Auto lại trật tự, Reorder level based on Warehouse,mức đèn đỏ mua vật tư (phải bổ xung hoặc đặt mua thêm), -Will also apply for variants unless overrridden,Cũng sẽ được áp dụng cho các biến thể trừ phần bị ghi đèn, +Will also apply for variants unless overridden,Cũng sẽ được áp dụng cho các biến thể trừ phần bị ghi đèn, Units of Measure,Đơn vị đo lường, Will also apply for variants,Cũng sẽ được áp dụng cho các biến thể, Serial Nos and Batches,Số hàng loạt và hàng loạt, diff --git a/erpnext/translations/zh-TW.csv b/erpnext/translations/zh-TW.csv index 699d802206..0f76e97f97 100644 --- a/erpnext/translations/zh-TW.csv +++ b/erpnext/translations/zh-TW.csv @@ -2964,7 +2964,7 @@ Replace BOM,更換BOM, Code {0} already exist,代碼{0}已經存在 Sales orders are not available for production,銷售訂單不可用於生產 Fixed Asset Depreciation Settings,固定資產折舊設置 -Will also apply for variants unless overrridden,同時將申請變種,除非overrridden, +Will also apply for variants unless overridden,同時將申請變種,除非overridden, Advances,進展 Manufacture against Material Request,對製造材料要求 Assessment Group: ,評估組: diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index 70ec81af26..1d8a261005 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -7052,7 +7052,7 @@ Moving Average,移动平均, Warranty Period (in days),保修期限(天数), Auto re-order,自动重订货, Reorder level based on Warehouse,根据仓库订货点水平, -Will also apply for variants unless overrridden,除非手动指定,否则会同时应用于变体, +Will also apply for variants unless overridden,除非手动指定,否则会同时应用于变体, Units of Measure,计量单位, Will also apply for variants,会同时应用于变体, Serial Nos and Batches,序列号和批号, diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv index 75157f02fc..8ecbaaa9c5 100644 --- a/erpnext/translations/zh_tw.csv +++ b/erpnext/translations/zh_tw.csv @@ -7485,7 +7485,7 @@ Moving Average,移動平均線, Warranty Period (in days),保修期限(天數), Auto re-order,自動重新排序, Reorder level based on Warehouse,根據倉庫訂貨點水平, -Will also apply for variants unless overrridden,同時將申請變種,除非overrridden, +Will also apply for variants unless overridden,同時將申請變種,除非overridden, Units of Measure,測量的單位, Will also apply for variants,同時將申請變種, Serial Nos and Batches,序列號和批號, From 89f484282a90516c117c31624fb2cc5eab6fb840 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 22 Oct 2023 10:58:39 +0530 Subject: [PATCH 197/280] refactor: exc rate on foreign currency JE from Bank Reconciliation --- .../bank_reconciliation_tool.py | 89 ++++++++++++++----- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 9a7a9a31d5..777e315298 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -18,6 +18,7 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s get_entries, ) from erpnext.accounts.utils import get_account_currency, get_balance_on +from erpnext.setup.utils import get_exchange_rate class BankReconciliationTool(Document): @@ -144,29 +145,74 @@ def create_journal_entry_bts( ) company = frappe.get_value("Account", company_account, "company") + company_default_currency = frappe.get_cached_value("Company", company, "default_currency") + company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency") + second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency") + + is_multi_currency = ( + True + if company_default_currency != company_account_currency + or company_default_currency != second_account_currency + else False + ) accounts = [] - # Multi Currency? - accounts.append( - { - "account": second_account, - "credit_in_account_currency": bank_transaction.deposit, - "debit_in_account_currency": bank_transaction.withdrawal, - "party_type": party_type, - "party": party, - "cost_center": get_default_cost_center(company), - } - ) + second_account_dict = { + "account": second_account, + "account_currency": second_account_currency, + "credit_in_account_currency": bank_transaction.deposit, + "debit_in_account_currency": bank_transaction.withdrawal, + "party_type": party_type, + "party": party, + "cost_center": get_default_cost_center(company), + } - accounts.append( - { - "account": company_account, - "bank_account": bank_transaction.bank_account, - "credit_in_account_currency": bank_transaction.withdrawal, - "debit_in_account_currency": bank_transaction.deposit, - "cost_center": get_default_cost_center(company), - } - ) + company_account_dict = { + "account": company_account, + "account_currency": company_account_currency, + "bank_account": bank_transaction.bank_account, + "credit_in_account_currency": bank_transaction.withdrawal, + "debit_in_account_currency": bank_transaction.deposit, + "cost_center": get_default_cost_center(company), + } + + if is_multi_currency: + exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date) + withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal)) + deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit)) + + if second_account_currency != company_default_currency: + exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date) + second_account_dict.update( + { + "exchange_rate": exc_rate, + "credit": deposit_in_company_currency, + "debit": withdrawal_in_company_currency, + } + ) + else: + second_account_dict.update( + { + "exchange_rate": 1, + "credit": deposit_in_company_currency, + "debit": withdrawal_in_company_currency, + "credit_in_account_currency": deposit_in_company_currency, + "debit_in_account_currency": withdrawal_in_company_currency, + } + ) + + if company_account_currency != company_default_currency: + exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date) + company_account_dict.update( + { + "exchange_rate": exc_rate, + "credit": withdrawal_in_company_currency, + "debit": deposit_in_company_currency, + } + ) + + accounts.append(second_account_dict) + accounts.append(company_account_dict) journal_entry_dict = { "voucher_type": entry_type, @@ -176,6 +222,9 @@ def create_journal_entry_bts( "cheque_no": reference_number, "mode_of_payment": mode_of_payment, } + if is_multi_currency: + journal_entry_dict.update({"multi_currency": True}) + journal_entry = frappe.new_doc("Journal Entry") journal_entry.update(journal_entry_dict) journal_entry.set("accounts", accounts) From 23df4205f8abfca6764d84665cb9877703c1a470 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Oct 2023 11:32:21 +0530 Subject: [PATCH 198/280] fix: overallocation on Payment with PO/SO --- erpnext/accounts/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f2691fb980..1c7052f8ff 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -645,7 +645,7 @@ def update_reference_in_payment_entry( "outstanding_amount": d.outstanding_amount, "allocated_amount": d.allocated_amount, "exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(), - "exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation + "exchange_gain_loss": d.exchange_gain_loss, "account": d.account, } @@ -658,22 +658,21 @@ def update_reference_in_payment_entry( existing_row.reference_doctype, existing_row.reference_name ).set_total_advance_paid() - original_row = existing_row.as_dict().copy() - existing_row.update(reference_details) + if d.allocated_amount <= existing_row.allocated_amount: + existing_row.allocated_amount -= d.allocated_amount - if d.allocated_amount < original_row.allocated_amount: new_row = payment_entry.append("references") new_row.docstatus = 1 for field in list(reference_details): - new_row.set(field, original_row[field]) + new_row.set(field, reference_details[field]) - new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount else: new_row = payment_entry.append("references") new_row.docstatus = 1 new_row.update(reference_details) payment_entry.flags.ignore_validate_update_after_submit = True + payment_entry.clear_unallocated_reference_document_rows() payment_entry.setup_party_account_field() payment_entry.set_missing_values() if not skip_ref_details_update_for_pe: From 946228d783cb58cd2809f4b0bc0a854c49cc4060 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Oct 2023 14:03:21 +0530 Subject: [PATCH 199/280] test: overalloction on reconciliation when PO is involved --- .../test_payment_reconciliation.py | 183 +++++++++++++++++- 1 file changed, 178 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 1d843abde1..48d1cf2cc2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -14,6 +14,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.party import get_party_account +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.stock.doctype.item.test_item import create_item test_dependencies = ["Item"] @@ -151,6 +152,64 @@ class TestPaymentReconciliation(FrappeTestCase): payment.posting_date = posting_date return payment + def create_purchase_invoice( + self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False + ): + """ + Helper function to populate default values in sales invoice + """ + pinv = make_purchase_invoice( + qty=qty, + rate=rate, + company=self.company, + customer=self.supplier, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + update_stock=0, + currency="INR", + is_pos=0, + is_return=0, + return_against=None, + income_account=self.income_account, + expense_account=self.expense_account, + do_not_save=do_not_save, + do_not_submit=do_not_submit, + ) + return pinv + + def create_purchase_order( + self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False + ): + """ + Helper function to populate default values in sales invoice + """ + pord = create_purchase_order( + qty=qty, + rate=rate, + company=self.company, + customer=self.supplier, + item_code=self.item, + item_name=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + debit_to=self.debit_to, + parent_cost_center=self.cost_center, + update_stock=0, + currency="INR", + is_pos=0, + is_return=0, + return_against=None, + income_account=self.income_account, + expense_account=self.expense_account, + do_not_save=do_not_save, + do_not_submit=do_not_submit, + ) + return pord + def clear_old_entries(self): doctype_list = [ "GL Entry", @@ -163,13 +222,11 @@ class TestPaymentReconciliation(FrappeTestCase): for doctype in doctype_list: qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run() - def create_payment_reconciliation(self): + def create_payment_reconciliation(self, party_is_customer=True): pr = frappe.new_doc("Payment Reconciliation") pr.company = self.company - pr.party_type = ( - self.party_type if hasattr(self, "party_type") and self.party_type else "Customer" - ) - pr.party = self.customer + pr.party_type = "Customer" if party_is_customer else "Supplier" + pr.party = self.customer if party_is_customer else self.supplier pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company) pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() return pr @@ -931,6 +988,7 @@ class TestPaymentReconciliation(FrappeTestCase): if invoice.invoice_number == pi.name: invoices.append(invoice.as_dict()) break + for payment in pr.payments: if payment.reference_name == pi_return.name: payments.append(payment.as_dict()) @@ -941,6 +999,121 @@ class TestPaymentReconciliation(FrappeTestCase): # Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit. pr.reconcile() + def test_reconciliation_from_purchase_order_to_multiple_invoices(self): + """ + Reconciling advance payment from PO/SO to multiple invoices should not cause overallocation + """ + + self.supplier = "_Test Supplier" + + pi1 = self.create_purchase_invoice(qty=10, rate=100) + pi2 = self.create_purchase_invoice(qty=10, rate=100) + po = self.create_purchase_order(qty=20, rate=100) + pay = get_payment_entry(po.doctype, po.name) + # Overpay Puchase Order + pay.paid_amount = 3000 + pay.save().submit() + # assert total allocated and unallocated before reconciliation + self.assertEqual( + ( + pay.references[0].reference_doctype, + pay.references[0].reference_name, + pay.references[0].allocated_amount, + ), + (po.doctype, po.name, 2000), + ) + self.assertEqual(pay.total_allocated_amount, 2000) + self.assertEqual(pay.unallocated_amount, 1000) + self.assertEqual(pay.difference_amount, 0) + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 2) + self.assertEqual(len(pr.payments), 2) + + for x in pr.payments: + self.assertEqual((x.reference_type, x.reference_name), (pay.doctype, pay.name)) + + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + # partial allocation on pi1 and full allocate on pi2 + pr.allocation[0].allocated_amount = 100 + pr.reconcile() + + # assert references and total allocated and unallocated amount + pay.reload() + self.assertEqual(len(pay.references), 3) + self.assertEqual( + ( + pay.references[0].reference_doctype, + pay.references[0].reference_name, + pay.references[0].allocated_amount, + ), + (po.doctype, po.name, 900), + ) + self.assertEqual( + ( + pay.references[1].reference_doctype, + pay.references[1].reference_name, + pay.references[1].allocated_amount, + ), + (pi1.doctype, pi1.name, 100), + ) + self.assertEqual( + ( + pay.references[2].reference_doctype, + pay.references[2].reference_name, + pay.references[2].allocated_amount, + ), + (pi2.doctype, pi2.name, 1000), + ) + self.assertEqual(pay.total_allocated_amount, 2000) + self.assertEqual(pay.unallocated_amount, 1000) + self.assertEqual(pay.difference_amount, 0) + + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 2) + + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # assert references and total allocated and unallocated amount + pay.reload() + self.assertEqual(len(pay.references), 3) + # PO references should be removed now + self.assertEqual( + ( + pay.references[0].reference_doctype, + pay.references[0].reference_name, + pay.references[0].allocated_amount, + ), + (pi1.doctype, pi1.name, 100), + ) + self.assertEqual( + ( + pay.references[1].reference_doctype, + pay.references[1].reference_name, + pay.references[1].allocated_amount, + ), + (pi2.doctype, pi2.name, 1000), + ) + self.assertEqual( + ( + pay.references[2].reference_doctype, + pay.references[2].reference_name, + pay.references[2].allocated_amount, + ), + (pi1.doctype, pi1.name, 900), + ) + self.assertEqual(pay.total_allocated_amount, 2000) + self.assertEqual(pay.unallocated_amount, 1000) + self.assertEqual(pay.difference_amount, 0) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From 547993f80103fa192563a82447c39fe122918767 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Oct 2023 14:57:05 +0530 Subject: [PATCH 200/280] refactor(test): make use of utility methods --- .../test_payment_reconciliation.py | 79 ++++++++++++------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 48d1cf2cc2..71bc498b49 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -86,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase): self.customer5 = make_customer("_Test PR Customer 5", "EUR") def create_account(self): - account_name = "Debtors EUR" - if not frappe.db.get_value( - "Account", filters={"account_name": account_name, "company": self.company} - ): - acc = frappe.new_doc("Account") - acc.account_name = account_name - acc.parent_account = "Accounts Receivable - _PR" - acc.company = self.company - acc.account_currency = "EUR" - acc.account_type = "Receivable" - acc.insert() - else: - name = frappe.db.get_value( - "Account", - filters={"account_name": account_name, "company": self.company}, - fieldname="name", - pluck=True, - ) - acc = frappe.get_doc("Account", name) - self.debtors_eur = acc.name + accounts = [ + { + "attribute": "debtors_eur", + "account_name": "Debtors EUR", + "parent_account": "Accounts Receivable - _PR", + "account_currency": "EUR", + "account_type": "Receivable", + }, + { + "attribute": "creditors_usd", + "account_name": "Payable USD", + "parent_account": "Accounts Payable - _PR", + "account_currency": "USD", + "account_type": "Payable", + }, + ] + + for x in accounts: + x = frappe._dict(x) + if not frappe.db.get_value( + "Account", filters={"account_name": x.account_name, "company": self.company} + ): + acc = frappe.new_doc("Account") + acc.account_name = x.account_name + acc.parent_account = x.parent_account + acc.company = self.company + acc.account_currency = x.account_currency + acc.account_type = x.account_type + acc.insert() + else: + name = frappe.db.get_value( + "Account", + filters={"account_name": x.account_name, "company": self.company}, + fieldname="name", + pluck=True, + ) + acc = frappe.get_doc("Account", name) + setattr(self, x.attribute, acc.name) def create_sales_invoice( self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False @@ -963,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(pr.allocation[0].difference_amount, 0) def test_reconciliation_purchase_invoice_against_return(self): - pi = make_purchase_invoice( - supplier="_Test Supplier USD", currency="USD", conversion_rate=50 - ).submit() + self.supplier = "_Test Supplier USD" + pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True) + pi.supplier = self.supplier + pi.currency = "USD" + pi.conversion_rate = 50 + pi.credit_to = self.creditors_usd + pi.save().submit() pi_return = frappe.get_doc(pi.as_dict()) pi_return.name = None @@ -975,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase): pi_return.items[0].qty = -pi_return.items[0].qty pi_return.submit() - self.company = "_Test Company" - self.party_type = "Supplier" - self.customer = "_Test Supplier USD" - - pr = self.create_payment_reconciliation() + pr = frappe.get_doc("Payment Reconciliation") + pr.company = self.company + pr.party_type = "Supplier" + pr.party = self.supplier + pr.receivable_payable_account = self.creditors_usd + pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate() pr.get_unreconciled_entries() invoices = [] From 4dff2c7a0dad1de840a2b1f53d51e9fe1682fa7f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 24 Oct 2023 08:09:22 +0530 Subject: [PATCH 201/280] chore: fix flakiness `test_sales_order_partial_advance_payment` --- erpnext/selling/doctype/sales_order/test_sales_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 83689a2b0b..d8b5878aa3 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1784,10 +1784,10 @@ class TestSalesOrder(FrappeTestCase): si.submit() pe.load_from_db() - self.assertEqual(pe.references[0].reference_name, si.name) - self.assertEqual(pe.references[0].allocated_amount, 200) - self.assertEqual(pe.references[1].reference_name, so.name) - self.assertEqual(pe.references[1].allocated_amount, 300) + self.assertEqual(pe.references[0].reference_name, so.name) + self.assertEqual(pe.references[0].allocated_amount, 300) + self.assertEqual(pe.references[1].reference_name, si.name) + self.assertEqual(pe.references[1].allocated_amount, 200) def test_delivered_item_material_request(self): "SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO." From 8e523961dc8dae52b90e8f0c98aeee37f3880d9a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 24 Oct 2023 14:13:26 +0530 Subject: [PATCH 202/280] fix(patch): `update_sre_from_voucher_details` (#37649) --- .../v15_0/update_sre_from_voucher_details.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v15_0/update_sre_from_voucher_details.py b/erpnext/patches/v15_0/update_sre_from_voucher_details.py index a9653ccbf4..06ba553e3a 100644 --- a/erpnext/patches/v15_0/update_sre_from_voucher_details.py +++ b/erpnext/patches/v15_0/update_sre_from_voucher_details.py @@ -3,13 +3,16 @@ from frappe.query_builder.functions import IfNull def execute(): - sre = frappe.qb.DocType("Stock Reservation Entry") - ( - frappe.qb.update(sre) - .set(sre.from_voucher_type, "Pick List") - .set(sre.from_voucher_no, sre.against_pick_list) - .set(sre.from_voucher_detail_no, sre.against_pick_list_item) - .where( - (IfNull(sre.against_pick_list, "") != "") & (IfNull(sre.against_pick_list_item, "") != "") - ) - ).run() + columns = frappe.db.get_table_columns("Stock Reservation Entry") + + if set(["against_pick_list", "against_pick_list_item"]).issubset(set(columns)): + sre = frappe.qb.DocType("Stock Reservation Entry") + ( + frappe.qb.update(sre) + .set(sre.from_voucher_type, "Pick List") + .set(sre.from_voucher_no, sre.against_pick_list) + .set(sre.from_voucher_detail_no, sre.against_pick_list_item) + .where( + (IfNull(sre.against_pick_list, "") != "") & (IfNull(sre.against_pick_list_item, "") != "") + ) + ).run() From 11d956fa18d4cfec89e2bc8c93dd43dad739f02e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 24 Oct 2023 14:28:19 +0530 Subject: [PATCH 203/280] fix: Purchase Receipt GL Entries (#37642) * fix: Purchase Receipt GL Entries * chore: cleanup * test: set cwip account --- erpnext/assets/doctype/asset/test_asset.py | 1 + erpnext/controllers/stock_controller.py | 7 ------- .../doctype/purchase_receipt/purchase_receipt.py | 14 +++++++------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 99824b7f67..d69f5ef0b7 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1789,6 +1789,7 @@ def create_asset_category(): "fixed_asset_account": "_Test Fixed Asset - _TC", "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", "depreciation_expense_account": "_Test Depreciations - _TC", + "capital_work_in_progress_account": "CWIP Account - _TC", }, ) asset_category.append( diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a40976b8dd..a7330ec63c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -76,8 +76,6 @@ class StockController(AccountsController): gl_entries = self.get_gl_entries(warehouse_account) make_gl_entries(gl_entries, from_repost=from_repost) - update_regional_gl_entries(gl_entries, self) - def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -1226,8 +1224,3 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entries.append(repost_entry) return repost_entries - - -@erpnext.allow_regional -def update_regional_gl_entries(gl_list, doc): - return diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 029d89c179..91344eaa5c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -542,17 +542,19 @@ class PurchaseReceipt(BuyingController): d, gl_entries, self.posting_date, d.get("provisional_expense_account") ) elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return): - is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) remarks = self.get("remarks") or _("Accounting Entry for {0}").format( - "Asset" if is_asset_pr else "Stock" + "Asset" if d.is_fixed_asset else "Stock" ) - if not (erpnext.is_perpetual_inventory_enabled(self.company) or is_asset_pr): - return + if not ( + (erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items) + or d.is_fixed_asset + ): + continue stock_asset_rbnb = ( self.get_company_default("asset_received_but_not_billed") - if is_asset_pr + if d.is_fixed_asset else self.get_company_default("stock_received_but_not_billed") ) landed_cost_entries = get_item_account_wise_additional_cost(self.name) @@ -758,8 +760,6 @@ class PurchaseReceipt(BuyingController): pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) update_billing_percentage(pr_doc, update_modified=update_modified) - self.load_from_db() - def reserve_stock_for_sales_order(self): if self.is_return or not cint( frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order_on_purchase") From d92eb0c6037065d9a6f1bf9899ae0f15fba8ff0f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 24 Oct 2023 15:25:03 +0530 Subject: [PATCH 204/280] Update initiate_release.yml --- .github/workflows/initiate_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml index 70347738f2..e51c1943fd 100644 --- a/.github/workflows/initiate_release.yml +++ b/.github/workflows/initiate_release.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - version: ["13", "14"] + version: ["13", "14", "15"] steps: - uses: octokit/request-action@v2.x From 92cbe580e6fb6380a8d2c1e3420b09680826cb76 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 24 Oct 2023 15:28:55 +0530 Subject: [PATCH 205/280] fix: incorrect process loss validation for multiple finished items (#37576) --- .../stock/doctype/stock_entry/stock_entry.py | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index a2cae7ff8d..35f6230cd0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -438,31 +438,37 @@ class StockEntry(StockController): item_code.append(item.item_code) def validate_fg_completed_qty(self): - item_wise_qty = {} - if self.purpose == "Manufacture" and self.work_order: - for d in self.items: - if d.is_finished_item: - if self.process_loss_qty: - d.qty = self.fg_completed_qty - self.process_loss_qty + if self.purpose != "Manufacture": + return - item_wise_qty.setdefault(d.item_code, []).append(d.qty) + fg_qty = defaultdict(float) + for d in self.items: + if d.is_finished_item: + fg_qty[d.item_code] += flt(d.qty) + + if not fg_qty: + return precision = frappe.get_precision("Stock Entry Detail", "qty") - for item_code, qty_list in item_wise_qty.items(): - total = flt(sum(qty_list), precision) + fg_item = list(fg_qty.keys())[0] + fg_item_qty = flt(fg_qty[fg_item], precision) + fg_completed_qty = flt(self.fg_completed_qty, precision) - if (self.fg_completed_qty - total) > 0 and not self.process_loss_qty: - self.process_loss_qty = flt(self.fg_completed_qty - total, precision) - self.process_loss_percentage = flt(self.process_loss_qty * 100 / self.fg_completed_qty) + for d in self.items: + if not fg_qty.get(d.item_code): + continue - if self.process_loss_qty: - total += flt(self.process_loss_qty, precision) + if (fg_completed_qty - fg_item_qty) > 0: + self.process_loss_qty = fg_completed_qty - fg_item_qty - if self.fg_completed_qty != total: + if not self.process_loss_qty: + continue + + if fg_completed_qty != (flt(fg_item_qty) + flt(self.process_loss_qty, precision)): frappe.throw( - _("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format( - frappe.bold(item_code), frappe.bold(total), frappe.bold(self.fg_completed_qty) - ) + _( + "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table." + ).format(frappe.bold(self.process_loss_qty), frappe.bold(d.item_code)) ) def validate_difference_account(self): From 74a0d6408a2082a2a039cd55547e56206e7c70bd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 25 Oct 2023 09:45:05 +0530 Subject: [PATCH 206/280] refactor: handle bank transaction in foreign currency --- .../bank_reconciliation_tool.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 777e315298..7e2f763137 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -131,7 +131,7 @@ def create_journal_entry_bts( bank_transaction = frappe.db.get_values( "Bank Transaction", bank_transaction_name, - fieldname=["name", "deposit", "withdrawal", "bank_account"], + fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"], as_dict=True, )[0] company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") @@ -149,10 +149,12 @@ def create_journal_entry_bts( company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency") second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency") + # determine if multi-currency Journal or not is_multi_currency = ( True if company_default_currency != company_account_currency or company_default_currency != second_account_currency + or company_default_currency != bank_transaction.currency else False ) @@ -176,11 +178,16 @@ def create_journal_entry_bts( "cost_center": get_default_cost_center(company), } + # convert transaction amount to company currency if is_multi_currency: - exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date) + exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date) withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal)) deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit)) + else: + withdrawal_in_company_currency = bank_transaction.withdrawal + deposit_in_company_currency = bank_transaction.deposit + # if second account is of foreign currency, convert and set debit and credit fields. if second_account_currency != company_default_currency: exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date) second_account_dict.update( @@ -188,6 +195,8 @@ def create_journal_entry_bts( "exchange_rate": exc_rate, "credit": deposit_in_company_currency, "debit": withdrawal_in_company_currency, + "credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0, + "debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0, } ) else: @@ -201,6 +210,7 @@ def create_journal_entry_bts( } ) + # if company account is of foreign currency, convert and set debit and credit fields. if company_account_currency != company_default_currency: exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date) company_account_dict.update( @@ -210,6 +220,16 @@ def create_journal_entry_bts( "debit": deposit_in_company_currency, } ) + else: + company_account_dict.update( + { + "exchange_rate": 1, + "credit": withdrawal_in_company_currency, + "debit": deposit_in_company_currency, + "credit_in_account_currency": withdrawal_in_company_currency, + "debit_in_account_currency": deposit_in_company_currency, + } + ) accounts.append(second_account_dict) accounts.append(company_account_dict) From 7be578485e2cafd124c98cc8394c67430c2b31b8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 25 Oct 2023 12:42:35 +0530 Subject: [PATCH 207/280] fix: force delete removed report (#37668) --- erpnext/patches.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 53bddb562c..bc2497c513 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -316,7 +316,7 @@ erpnext.patches.v14_0.update_closing_balances #14-07-2023 execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts erpnext.patches.v14_0.update_subscription_details -execute:frappe.delete_doc_if_exists("Report", "Tax Detail") +execute:frappe.delete_doc("Report", "Tax Detail", force=True) erpnext.patches.v15_0.enable_all_leads erpnext.patches.v14_0.update_company_in_ldc erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes @@ -342,4 +342,4 @@ erpnext.patches.v15_0.delete_payment_gateway_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item erpnext.patches.v15_0.update_sre_from_voucher_details # below migration patch should always run last -erpnext.patches.v14_0.migrate_gl_to_payment_ledger \ No newline at end of file +erpnext.patches.v14_0.migrate_gl_to_payment_ledger From 886102d462650243a7040177c585c734082e3734 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 12:46:39 +0530 Subject: [PATCH 208/280] chore: fixed test cases related to Internal Transfer (backport #37659) (#37662) * chore: fixed test cases related to Internal Transfer (#37659) (cherry picked from commit 72d32a49012329d33fd4ecea70988fbfbfce566f) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts * chore: fix conflicts * chore: fix test cases --------- Co-authored-by: rohitwaghchaure --- .../sales_invoice/test_sales_invoice.py | 45 +++++++ .../purchase_receipt/test_purchase_receipt.py | 113 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 231b3bf7fe..21cc253959 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2539,6 +2539,37 @@ class TestSalesInvoice(FrappeTestCase): frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock) + def test_sle_for_target_warehouse(self): + se = make_stock_entry( + item_code="138-CMS Shoe", + target="Finished Goods - _TC", + company="_Test Company", + qty=1, + basic_rate=500, + ) + + si = frappe.copy_doc(test_records[0]) + si.customer = "_Test Internal Customer 3" + si.update_stock = 1 + si.set_warehouse = "Finished Goods - _TC" + si.set_target_warehouse = "Stores - _TC" + si.get("items")[0].warehouse = "Finished Goods - _TC" + si.get("items")[0].target_warehouse = "Stores - _TC" + si.insert() + si.submit() + + sles = frappe.get_all( + "Stock Ledger Entry", filters={"voucher_no": si.name}, fields=["name", "actual_qty"] + ) + + # check if both SLEs are created + self.assertEqual(len(sles), 2) + self.assertEqual(sum(d.actual_qty for d in sles), 0.0) + + # tear down + si.cancel() + se.cancel() + def test_internal_transfer_gl_entry(self): si = create_sales_invoice( company="_Test Company with perpetual inventory", @@ -3662,6 +3693,20 @@ def create_internal_parties(): allowed_to_interact_with="_Test Company with perpetual inventory", ) + create_internal_customer( + customer_name="_Test Internal Customer 3", + represents_company="_Test Company", + allowed_to_interact_with="_Test Company", + ) + + account = create_account( + account_name="Unrealized Profit", + parent_account="Current Liabilities - _TC", + company="_Test Company", + ) + + frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account) + create_internal_supplier( supplier_name="_Test Internal Supplier", represents_company="Wind Power LLC", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e998b842d1..146cbff1aa 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -957,6 +957,119 @@ class TestPurchaseReceipt(FrappeTestCase): pr1.reload() pr1.cancel() + def test_stock_transfer_from_purchase_receipt(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + pr1 = make_purchase_receipt( + warehouse="Stores - TCP1", company="_Test Company with perpetual inventory" + ) + + dn1 = create_delivery_note( + item_code=pr1.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=5, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + ) + + pr = make_inter_company_purchase_receipt(dn1.name) + pr.items[0].from_warehouse = "Work In Progress - TCP1" + pr.items[0].warehouse = "Stores - TCP1" + pr.submit() + + gl_entries = get_gl_entries("Purchase Receipt", pr.name) + sl_entries = get_sl_entries("Purchase Receipt", pr.name) + + self.assertFalse(gl_entries) + + expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5} + + for sle in sl_entries: + self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) + + pr.cancel() + + def test_stock_transfer_from_purchase_receipt_with_valuation(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + prepare_data_for_internal_transfer() + + create_warehouse( + "_Test Warehouse for Valuation", + company="_Test Company with perpetual inventory", + properties={"account": "_Test Account Stock In Hand - TCP1"}, + ) + + pr1 = make_purchase_receipt( + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + ) + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + dn1 = create_delivery_note( + item_code=pr1.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=5, + rate=50, + warehouse="Stores - TCP1", + target_warehouse="_Test Warehouse for Valuation - TCP1", + ) + + pr = make_inter_company_purchase_receipt(dn1.name) + pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1" + pr.items[0].warehouse = "Stores - TCP1" + + pr.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Shipping Charges - TCP1", + "category": "Valuation and Total", + "cost_center": "Main - TCP1", + "description": "Test", + "rate": 9, + }, + ) + + pr.submit() + + gl_entries = get_gl_entries("Purchase Receipt", pr.name) + sl_entries = get_sl_entries("Purchase Receipt", pr.name) + + expected_gle = [ + ["Stock In Hand - TCP1", 272.5, 0.0], + ["_Test Account Stock In Hand - TCP1", 0.0, 250.0], + ["_Test Account Shipping Charges - TCP1", 0.0, 22.5], + ] + + expected_sle = {"_Test Warehouse for Valuation - TCP1": -5, "Stores - TCP1": 5} + + for sle in sl_entries: + self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty) + + for i, gle in enumerate(gl_entries): + self.assertEqual(gle.account, expected_gle[i][0]) + self.assertEqual(gle.debit, expected_gle[i][1]) + self.assertEqual(gle.credit, expected_gle[i][2]) + + pr.cancel() + def test_po_to_pi_and_po_to_pr_worflow_full(self): """Test following behaviour: - Create PO From 5deba1b6f9b03ce5d078d624e339f6b0209a1555 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 25 Oct 2023 12:50:16 +0530 Subject: [PATCH 209/280] fix: copy all child fields to item variant --- erpnext/stock/doctype/item/item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 9e281990b5..d8935fe203 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -255,7 +255,7 @@ class Item(Document): # add item taxes from template for d in template.get("taxes"): - self.append("taxes", {"item_tax_template": d.item_tax_template}) + self.append("taxes", d) # copy re-order table if empty if not self.get("reorder_levels"): From d436a407390c0e0d89c66445539bbb95784be7eb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 25 Oct 2023 13:06:03 +0530 Subject: [PATCH 210/280] fix: only update if variant table empty --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 8c6fd84bc4..d1999070f8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -268,7 +268,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): if not item: item = frappe.get_doc("Item", args.get("item_code")) - if item.variant_of: + if item.variant_of and not item.taxes: item.update_template_tables() item_defaults = get_item_defaults(item.name, args.company) From 2bcff4c7f2264d2408a1f98bddc78041b167a632 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 25 Oct 2023 13:24:34 +0530 Subject: [PATCH 211/280] chore: fixed test case non_internal_transfer_delivery_note (#37671) --- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d06819208e..1eecf6dc2a 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1230,16 +1230,16 @@ class TestDeliveryNote(FrappeTestCase): frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) - def non_internal_transfer_delivery_note(self): + def test_non_internal_transfer_delivery_note(self): from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse dn = create_delivery_note(do_not_submit=True) - warehouse = create_warehouse("Internal Transfer Warehouse", dn.company) - dn.items[0].db_set("target_warehouse", "warehouse") + warehouse = create_warehouse("Internal Transfer Warehouse", company=dn.company) + dn.items[0].db_set("target_warehouse", warehouse) dn.reload() - self.assertEqual(dn.items[0].target_warehouse, warehouse.name) + self.assertEqual(dn.items[0].target_warehouse, warehouse) dn.save() dn.reload() From d69b0d76dd2d89fe0c1b9a634923cf8716a37c11 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 25 Oct 2023 13:59:37 +0530 Subject: [PATCH 212/280] fix: status for over delivery or billing --- erpnext/controllers/status_updater.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 73a248fb53..d09001c8fc 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -47,15 +47,15 @@ status_map = { ], [ "To Bill", - "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1", + "eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1", ], [ "To Deliver", - "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note", + "eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note", ], [ "Completed", - "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1", + "eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed >= 100 and self.docstatus == 1", ], ["Cancelled", "eval:self.docstatus==2"], ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], From 8ffa2bfe25d8fae317d77705aec82a92dc269874 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 25 Oct 2023 11:38:09 +0530 Subject: [PATCH 213/280] refactor: rename field `Over Order Allowance` to `Blanket Order Allowance` --- .../buying_settings/buying_settings.json | 18 +++++++++--------- .../doctype/blanket_order/blanket_order.py | 2 +- .../blanket_order/test_blanket_order.py | 6 +++--- .../selling_settings/selling_settings.json | 16 ++++++++-------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 71cb01b188..059999245d 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -16,7 +16,7 @@ "transaction_settings_section", "po_required", "pr_required", - "over_order_allowance", + "blanket_order_allowance", "column_break_12", "maintain_same_rate", "set_landed_cost_based_on_purchase_invoice_rate", @@ -159,19 +159,19 @@ "fieldtype": "Check", "label": "Set Landed Cost Based on Purchase Invoice Rate" }, - { - "default": "0", - "description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.", - "fieldname": "over_order_allowance", - "fieldtype": "Float", - "label": "Over Order Allowance (%)" - }, { "default": "0", "description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.", "fieldname": "use_transaction_date_exchange_rate", "fieldtype": "Check", "label": "Use Transaction Date Exchange Rate" + }, + { + "default": "0", + "description": "Percentage you are allowed to order beyond the Blanket Order quantity.", + "fieldname": "blanket_order_allowance", + "fieldtype": "Float", + "label": "Blanket Order Allowance (%)" } ], "icon": "fa fa-cog", @@ -179,7 +179,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-10-16 16:22:03.201078", + "modified": "2023-10-25 14:03:32.520418", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py index 32f1c365ad..0135a4f971 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py @@ -107,7 +107,7 @@ def validate_against_blanket_order(order_doc): allowance = flt( frappe.db.get_single_value( "Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings", - "over_order_allowance", + "blanket_order_allowance", ) ) for bo_name, item_data in order_data.items(): diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index 58f3c95059..e9fc25b5bc 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -63,7 +63,7 @@ class TestBlanketOrder(FrappeTestCase): po1.currency = get_company_currency(po1.company) self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty)) - def test_over_order_allowance(self): + def test_blanket_order_allowance(self): # Sales Order bo = make_blanket_order(blanket_order_type="Selling", quantity=100) @@ -74,7 +74,7 @@ class TestBlanketOrder(FrappeTestCase): so.items[0].qty = 110 self.assertRaises(frappe.ValidationError, so.submit) - frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10) + frappe.db.set_single_value("Selling Settings", "blanket_order_allowance", 10) so.submit() # Purchase Order @@ -87,7 +87,7 @@ class TestBlanketOrder(FrappeTestCase): po.items[0].qty = 110 self.assertRaises(frappe.ValidationError, po.submit) - frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10) + frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10) po.submit() diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 6855012d5f..d6829ce24b 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -25,7 +25,7 @@ "so_required", "dn_required", "sales_update_frequency", - "over_order_allowance", + "blanket_order_allowance", "column_break_5", "allow_multiple_items", "allow_against_multiple_purchase_orders", @@ -183,12 +183,6 @@ "fieldtype": "Check", "label": "Allow Sales Order Creation For Expired Quotation" }, - { - "description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.", - "fieldname": "over_order_allowance", - "fieldtype": "Float", - "label": "Over Order Allowance (%)" - }, { "default": "0", "fieldname": "dont_reserve_sales_order_qty_on_sales_return", @@ -200,6 +194,12 @@ "fieldname": "allow_negative_rates_for_items", "fieldtype": "Check", "label": "Allow Negative rates for Items" + }, + { + "description": "Percentage you are allowed to sell beyond the Blanket Order quantity.", + "fieldname": "blanket_order_allowance", + "fieldtype": "Float", + "label": "Blanket Order Allowance (%)" } ], "icon": "fa fa-cog", @@ -207,7 +207,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-14 20:33:05.693667", + "modified": "2023-10-25 14:03:03.966701", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From fcfcf6957e07ad5afc281e5f43154ca1170e14c4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 25 Oct 2023 11:52:49 +0530 Subject: [PATCH 214/280] chore: patch to rename field `over_order_allowance` --- erpnext/patches.txt | 1 + .../v14_0/rename_over_order_allowance_field.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 erpnext/patches/v14_0/rename_over_order_allowance_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 53bddb562c..4c574bf33f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -341,5 +341,6 @@ execute:frappe.delete_doc("Page", "welcome-to-erpnext") erpnext.patches.v15_0.delete_payment_gateway_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item erpnext.patches.v15_0.update_sre_from_voucher_details +erpnext.patches.v14_0.rename_over_order_allowance_field # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger \ No newline at end of file diff --git a/erpnext/patches/v14_0/rename_over_order_allowance_field.py b/erpnext/patches/v14_0/rename_over_order_allowance_field.py new file mode 100644 index 0000000000..a81fe888c2 --- /dev/null +++ b/erpnext/patches/v14_0/rename_over_order_allowance_field.py @@ -0,0 +1,15 @@ +from frappe.model.utils.rename_field import rename_field + + +def execute(): + rename_field( + "Buying Settings", + "over_order_allowance", + "blanket_order_allowance", + ) + + rename_field( + "Selling Settings", + "over_order_allowance", + "blanket_order_allowance", + ) From 8e3b9ec87977b54cfdee4b65283723d20e91f274 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 25 Oct 2023 18:06:23 +0530 Subject: [PATCH 215/280] feat: allow return of components for SCO that don't have SCR created --- .../doctype/subcontracting_order/subcontracting_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index f2b395ac10..47d870447f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -107,7 +107,7 @@ frappe.ui.form.on('Subcontracting Order', { get_materials_from_supplier: function (frm) { let sco_rm_details = []; - if (frm.doc.status != "Closed" && frm.doc.supplied_items && frm.doc.per_received > 0) { + if (frm.doc.status != "Closed" && frm.doc.supplied_items) { frm.doc.supplied_items.forEach(d => { if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) { sco_rm_details.push(d.name); From 3290df5593009a4e237e7903feb078961f15ae12 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 25 Oct 2023 18:28:30 +0530 Subject: [PATCH 216/280] fix: consider returned qty while calculating unsupplied qty --- .../stock/doctype/stock_entry/stock_entry.py | 28 ++++++++++++++++--- .../subcontracting_order.js | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 35f6230cd0..c41349fcfb 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1020,14 +1020,34 @@ class StockEntry(StockController): & (se.docstatus == 1) & (se_detail.item_code == se_item.item_code) & ( - (se.purchase_order == self.purchase_order) + ((se.purchase_order == self.purchase_order) & (se_detail.po_detail == se_item.po_detail)) if self.subcontract_data.order_doctype == "Purchase Order" - else (se.subcontracting_order == self.subcontracting_order) + else ( + (se.subcontracting_order == self.subcontracting_order) + & (se_detail.sco_rm_detail == se_item.sco_rm_detail) + ) ) ) - ).run()[0][0] + ).run()[0][0] or 0 - if flt(total_supplied, precision) > flt(total_allowed, precision): + total_returned = 0 + if self.subcontract_data.order_doctype == "Subcontracting Order": + total_returned = ( + frappe.qb.from_(se) + .inner_join(se_detail) + .on(se.name == se_detail.parent) + .select(Sum(se_detail.transfer_qty)) + .where( + (se.purpose == "Material Transfer") + & (se.docstatus == 1) + & (se.is_return == 1) + & (se_detail.item_code == se_item.item_code) + & (se_detail.sco_rm_detail == se_item.sco_rm_detail) + & (se.subcontracting_order == self.subcontracting_order) + ) + ).run()[0][0] or 0 + + if flt(total_supplied - total_returned, precision) > flt(total_allowed, precision): frappe.throw( _("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format( se_item.idx, diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 47d870447f..587a3b4ebf 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -193,7 +193,7 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll } has_unsupplied_items() { - return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty); + return this.frm.doc['supplied_items'].some(item => item.required_qty > (item.supplied_qty - item.returned_qty)); } make_subcontracting_receipt() { From 46ea8685590e81266beb1cccbe72925f28bc42ba Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 Oct 2023 22:58:24 +0530 Subject: [PATCH 217/280] fix(plaid): Do not sync pending transactions --- .../doctype/bank_transaction/bank_transaction.py | 1 - .../doctype/plaid_settings/plaid_settings.py | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 6a47562412..4649d23162 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -89,7 +89,6 @@ class BankTransaction(StatusUpdater): - 0 > a: Error: already over-allocated - clear means: set the latest transaction date as clearance date """ - gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account") remaining_amount = self.unallocated_amount for payment_entry in self.payment_entries: if payment_entry.allocated_amount == 0.0: diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 11d5f6a9c4..eb99345991 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document -from frappe.utils import add_months, formatdate, getdate, today +from frappe.utils import add_months, formatdate, getdate, sbool, today from plaid.errors import ItemError from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account @@ -237,8 +237,6 @@ def new_bank_transaction(transaction): deposit = abs(amount) withdrawal = 0.0 - status = "Pending" if transaction["pending"] == True else "Settled" - tags = [] if transaction["category"]: try: @@ -247,13 +245,14 @@ def new_bank_transaction(transaction): except KeyError: pass - if not frappe.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])): + if not frappe.db.exists( + "Bank Transaction", dict(transaction_id=transaction["transaction_id"]) + ) and not sbool(transaction["pending"]): try: new_transaction = frappe.get_doc( { "doctype": "Bank Transaction", "date": getdate(transaction["date"]), - "status": status, "bank_account": bank_account, "deposit": deposit, "withdrawal": withdrawal, From 4bbad7f44819bb9a2db2163879fb8cedfcd83871 Mon Sep 17 00:00:00 2001 From: viralkansodiya15 <98073516+viralpatel15@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:45:47 +0530 Subject: [PATCH 218/280] fix: set docstatus filter to ignore cancel document --- .../stock/doctype/landed_cost_voucher/landed_cost_voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 7f0dc2df9f..5e35c16365 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -205,7 +205,7 @@ class LandedCostVoucher(Document): ) docs = frappe.db.get_all( "Asset", - filters={receipt_document_type: item.receipt_document, "item_code": item.item_code}, + filters={receipt_document_type: item.receipt_document, "item_code": item.item_code, "docstatus":['!=', 2]}, fields=["name", "docstatus"], ) if not docs or len(docs) != item.qty: From 681782121cd0d723f725e4c1c4c30167eb1622ec Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 26 Oct 2023 13:46:50 +0200 Subject: [PATCH 219/280] fix: avoid name clash in delivery stop (#37306) * fix(stock): avoid name clash in delivery stop with Document.lock() * chore(stock): format delivery stop json according to doctype builder --- erpnext/patches.txt | 1 + .../v14_0/migrate_delivery_stop_lock_field.py | 7 + .../doctype/delivery_stop/delivery_stop.json | 956 ++++-------------- .../doctype/delivery_trip/delivery_trip.py | 2 +- .../delivery_trip/test_delivery_trip.py | 4 +- 5 files changed, 180 insertions(+), 790 deletions(-) create mode 100644 erpnext/patches/v14_0/migrate_delivery_stop_lock_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b05ec4e7bf..d7f33adeea 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -342,5 +342,6 @@ erpnext.patches.v15_0.delete_payment_gateway_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item erpnext.patches.v15_0.update_sre_from_voucher_details erpnext.patches.v14_0.rename_over_order_allowance_field +erpnext.patches.v14_0.migrate_delivery_stop_lock_field # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/migrate_delivery_stop_lock_field.py b/erpnext/patches/v14_0/migrate_delivery_stop_lock_field.py new file mode 100644 index 0000000000..c9ec1e113d --- /dev/null +++ b/erpnext/patches/v14_0/migrate_delivery_stop_lock_field.py @@ -0,0 +1,7 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + if frappe.db.has_column("Delivery Stop", "lock"): + rename_field("Delivery Stop", "lock", "locked") diff --git a/erpnext/stock/doctype/delivery_stop/delivery_stop.json b/erpnext/stock/doctype/delivery_stop/delivery_stop.json index 5610a8108a..42560e612e 100644 --- a/erpnext/stock/doctype/delivery_stop/delivery_stop.json +++ b/erpnext/stock/doctype/delivery_stop/delivery_stop.json @@ -1,815 +1,197 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-10-16 16:46:28.166950", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-10-16 16:46:28.166950", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer", + "address", + "locked", + "column_break_6", + "customer_address", + "visited", + "order_information_section", + "delivery_note", + "cb_order", + "grand_total", + "section_break_7", + "contact", + "email_sent_to", + "column_break_7", + "customer_contact", + "section_break_9", + "distance", + "estimated_arrival", + "lat", + "column_break_19", + "uom", + "lng", + "more_information_section", + "details" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Address Name", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "address", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Address Name", + "options": "Address", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Lock", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "locked", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Locked" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_address", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer_address", + "fieldtype": "Small Text", + "label": "Customer Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.docstatus==1", - "fieldname": "visited", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Visited", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.docstatus==1", + "fieldname": "visited", + "fieldtype": "Check", + "label": "Visited", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "order_information_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Order Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "order_information_section", + "fieldtype": "Section Break", + "label": "Order Information" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "delivery_note", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Delivery Note", - "length": 0, - "no_copy": 1, - "options": "Delivery Note", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "delivery_note", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Delivery Note", + "no_copy": 1, + "options": "Delivery Note", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_order", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_order", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Grand Total", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Grand Total", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Contact Information" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Name", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact", + "fieldtype": "Link", + "label": "Contact Name", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "email_sent_to", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email sent to", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "email_sent_to", + "fieldtype": "Data", + "label": "Email sent to", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_contact", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer_contact", + "fieldtype": "Small Text", + "label": "Customer Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Dispatch Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Dispatch Information" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "distance", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Distance", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "2", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "distance", + "fieldtype": "Float", + "label": "Distance", + "precision": "2", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "estimated_arrival", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Estimated Arrival", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "estimated_arrival", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Estimated Arrival" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lat", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Latitude", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "lat", + "fieldtype": "Float", + "hidden": 1, + "label": "Latitude" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_19", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:doc.distance", - "fieldname": "uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.distance", + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lng", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Longitude", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "lng", + "fieldtype": "Float", + "hidden": 1, + "label": "Longitude" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "more_information_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "more_information_section", + "fieldtype": "Section Break", + "label": "More Information" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "details", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "details", + "fieldtype": "Text Editor", + "label": "Details" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-10-16 05:23:25.661542", - "modified_by": "Administrator", - "module": "Stock", - "name": "Delivery Stop", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2023-09-29 09:22:53.435161", + "modified_by": "Administrator", + "module": "Stock", + "name": "Delivery Stop", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index af2f4113e1..c531a8769c 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -170,7 +170,7 @@ class DeliveryTrip(Document): for stop in self.delivery_stops: leg.append(stop.customer_address) - if optimize and stop.lock: + if optimize and stop.locked: route_list.append(leg) leg = [stop.customer_address] diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index ed699e37b8..9b8b46e6e0 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -46,7 +46,7 @@ class TestDeliveryTrip(FrappeTestCase): self.assertEqual(len(route_list[0]), 4) def test_unoptimized_route_list_with_locks(self): - self.delivery_trip.delivery_stops[0].lock = 1 + self.delivery_trip.delivery_stops[0].locked = 1 self.delivery_trip.save() route_list = self.delivery_trip.form_route_list(optimize=False) @@ -65,7 +65,7 @@ class TestDeliveryTrip(FrappeTestCase): self.assertEqual(len(route_list[0]), 4) def test_optimized_route_list_with_locks(self): - self.delivery_trip.delivery_stops[0].lock = 1 + self.delivery_trip.delivery_stops[0].locked = 1 self.delivery_trip.save() route_list = self.delivery_trip.form_route_list(optimize=True) From 1612d7ba3f353e23ad6ae9ba12b995106cddcb9e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 26 Oct 2023 14:03:22 +0200 Subject: [PATCH 220/280] fix(defaults): apply discount and provisonal defaults from item group and brand if available (#37466) --- erpnext/stock/get_item_details.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 8c6fd84bc4..a8eb777342 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -330,8 +330,12 @@ def get_basic_details(args, item, overwrite_warehouse=True): ), "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults), - "discount_account": get_default_discount_account(args, item_defaults), - "provisional_expense_account": get_provisional_account(args, item_defaults), + "discount_account": get_default_discount_account( + args, item_defaults, item_group_defaults, brand_defaults + ), + "provisional_expense_account": get_provisional_account( + args, item_defaults, item_group_defaults, brand_defaults + ), "cost_center": get_default_cost_center( args, item_defaults, item_group_defaults, brand_defaults ), @@ -686,12 +690,22 @@ def get_default_expense_account(args, item, item_group, brand): ) -def get_provisional_account(args, item): - return item.get("default_provisional_account") or args.default_provisional_account +def get_provisional_account(args, item, item_group, brand): + return ( + item.get("default_provisional_account") + or item_group.get("default_provisional_account") + or brand.get("default_provisional_account") + or args.default_provisional_account + ) -def get_default_discount_account(args, item): - return item.get("default_discount_account") or args.discount_account +def get_default_discount_account(args, item, item_group, brand): + return ( + item.get("default_discount_account") + or item_group.get("default_discount_account") + or brand.get("default_discount_account") + or args.discount_account + ) def get_default_deferred_account(args, item, fieldname=None): From dc5d2c740611c687b64066246272428e8f7a4962 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 26 Oct 2023 20:36:52 +0530 Subject: [PATCH 221/280] fix: typerror on TDS payable monthly report --- .../report/tax_withholding_details/tax_withholding_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index f2ec31c70e..eac5426b8b 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -68,7 +68,7 @@ def get_result( tax_amount += entry.credit - entry.debit if net_total_map.get(name): - if voucher_type == "Journal Entry": + if voucher_type == "Journal Entry" and tax_amount and rate: # back calcalute total amount from rate and tax_amount total_amount = grand_total = base_total = tax_amount / (rate / 100) else: From 8d9b90f3f53083e631444a0d39d86bda0b08f479 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 27 Oct 2023 12:37:37 +0530 Subject: [PATCH 222/280] refactor: ignore cancelled GLE's while looking for currency --- erpnext/accounts/party.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 310e41208f..16e73ea52f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -5,7 +5,7 @@ from typing import Optional import frappe -from frappe import _, msgprint, scrub +from frappe import _, msgprint, qb, scrub from frappe.contacts.doctype.address.address import get_company_address, get_default_address from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values @@ -480,11 +480,19 @@ def get_party_account_currency(party_type, party, company): def get_party_gle_currency(party_type, party, company): def generator(): - existing_gle_currency = frappe.db.sql( - """select account_currency from `tabGL Entry` - where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s - limit 1""", - {"company": company, "party_type": party_type, "party": party}, + gl = qb.DocType("GL Entry") + existing_gle_currency = ( + qb.from_(gl) + .select(gl.account_currency) + .where( + (gl.docstatus == 1) + & (gl.company == company) + & (gl.party_type == party_type) + & (gl.party == party) + & (gl.is_cancelled == 0) + ) + .limit(1) + .run() ) return existing_gle_currency[0][0] if existing_gle_currency else None From 48c66b68abe5573c477cbbdc49af22dbde1ea2db Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 27 Oct 2023 17:07:16 +0530 Subject: [PATCH 223/280] fix: typo in function name and msg --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 4 ++-- .../stock_reservation_entry/stock_reservation_entry.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 96e4a55630..3ce121f31f 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -658,7 +658,7 @@ class SerialandBatchBundle(Document): if not available_batches: return - available_batches = get_availabel_batches_qty(available_batches) + available_batches = get_available_batches_qty(available_batches) for batch_no in batches: if batch_no not in available_batches or available_batches[batch_no] < 0: self.throw_error_message( @@ -1074,7 +1074,7 @@ def get_auto_data(**kwargs): return get_auto_batch_nos(kwargs) -def get_availabel_batches_qty(available_batches): +def get_available_batches_qty(available_batches): available_batches_qty = defaultdict(float) for batch in available_batches: available_batches_qty[batch.batch_no] += batch.qty diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 81e9dfa69b..6b39965f9b 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -241,7 +241,7 @@ class StockReservationEntry(Document): if available_qty_to_reserve <= 0: msg = _( - "Row #{0}: Stock not availabe to reserve for Item {1} against Batch {2} in Warehouse {3}." + "Row #{0}: Stock not available to reserve for Item {1} against Batch {2} in Warehouse {3}." ).format( entry.idx, frappe.bold(self.item_code), From d99a56bc2787975016b23602fd42b0f595594ad1 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Fri, 27 Oct 2023 18:54:36 +0530 Subject: [PATCH 224/280] chore: allow wip_composite_asset in the MR PO PR PI flow (#37723) --- .../purchase_invoice/purchase_invoice.py | 1 + erpnext/assets/doctype/asset/asset.json | 16 +++++++++--- .../asset_capitalization.py | 6 +---- .../doctype/purchase_order/purchase_order.py | 2 ++ .../purchase_order_item.json | 16 ++++++++++-- .../material_request/material_request.py | 1 + .../material_request_item.json | 26 ++++++++++++++++--- .../purchase_receipt/purchase_receipt.py | 1 + 8 files changed, 54 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 97ee5cc93b..c398d14183 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1664,6 +1664,7 @@ def make_purchase_receipt(source_name, target_doc=None): "po_detail": "purchase_order_item", "material_request": "material_request", "material_request_item": "material_request_item", + "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty), diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index c7d08e2041..40f51ab570 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -221,11 +221,11 @@ "read_only": 1 }, { + "depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)", "fieldname": "gross_purchase_amount", "fieldtype": "Currency", "label": "Gross Purchase Amount", "options": "Company:company:default_currency", - "read_only": 1, "read_only_depends_on": "eval:!doc.is_existing_asset", "reqd": 1 }, @@ -399,6 +399,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset", "fieldname": "purchase_receipt", "fieldtype": "Link", "label": "Purchase Receipt", @@ -416,6 +417,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset", "fieldname": "purchase_invoice", "fieldtype": "Link", "label": "Purchase Invoice", @@ -479,10 +481,11 @@ "read_only": 1 }, { + "depends_on": "eval.doc.asset_quantity", "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", - "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset" + "read_only": 1 }, { "fieldname": "depr_entry_posting_status", @@ -562,9 +565,14 @@ "link_doctype": "Journal Entry", "link_fieldname": "reference_name", "table_fieldname": "accounts" + }, + { + "group": "Asset Capitalization", + "link_doctype": "Asset Capitalization", + "link_fieldname": "target_asset" } ], - "modified": "2023-10-03 23:28:26.732269", + "modified": "2023-10-27 17:03:46.629617", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -608,4 +616,4 @@ "states": [], "title_field": "asset_name", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 0d6f6b4da1..728764be72 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -876,12 +876,8 @@ def get_items_tagged_to_wip_composite_asset(asset): "amount", ] - pi_items = frappe.get_all( - "Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields - ) - pr_items = frappe.get_all( "Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields ) - return pi_items + pr_items + return pr_items diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 7c40aafbe0..961697c0ac 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -558,6 +558,7 @@ def make_purchase_receipt(source_name, target_doc=None): "material_request_item": "material_request_item", "sales_order": "sales_order", "sales_order_item": "sales_order_item", + "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) @@ -634,6 +635,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions "field_map": { "name": "po_detail", "parent": "purchase_order", + "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, "condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)), diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 6b29984491..b1da97d634 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -86,6 +86,8 @@ "billed_amt", "accounting_details", "expense_account", + "column_break_fyqr", + "wip_composite_asset", "manufacture_details", "manufacturer", "manufacturer_part_no", @@ -896,13 +898,23 @@ "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply TDS" + }, + { + "fieldname": "wip_composite_asset", + "fieldtype": "Link", + "label": "WIP Composite Asset", + "options": "Asset" + }, + { + "fieldname": "column_break_fyqr", + "fieldtype": "Column Break" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-09-13 16:22:40.825092", + "modified": "2023-10-27 15:50:42.655573", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", @@ -915,4 +927,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index a51028da19..ecdec800e5 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -401,6 +401,7 @@ def make_purchase_order(source_name, target_doc=None, args=None): ["uom", "uom"], ["sales_order", "sales_order"], ["sales_order_item", "sales_order_item"], + ["wip_composite_asset", "wip_composite_asset"], ], "postprocess": update_item, "condition": select_item, diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index c585d6c490..9912be145f 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -37,6 +37,10 @@ "rate", "col_break3", "amount", + "accounting_details_section", + "expense_account", + "column_break_glru", + "wip_composite_asset", "manufacture_details", "manufacturer", "manufacturer_part_no", @@ -50,11 +54,10 @@ "lead_time_date", "sales_order", "sales_order_item", + "col_break4", "production_plan", "material_request_plan_item", "job_card_item", - "col_break4", - "expense_account", "section_break_46", "page_break" ], @@ -454,13 +457,28 @@ "label": "Job Card Item", "no_copy": 1, "print_hide": 1 + }, + { + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "column_break_glru", + "fieldtype": "Column Break" + }, + { + "fieldname": "wip_composite_asset", + "fieldtype": "Link", + "label": "WIP Composite Asset", + "options": "Asset" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-07 20:23:31.250252", + "modified": "2023-10-27 15:53:41.444236", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", @@ -471,4 +489,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 91344eaa5c..2a4b6f34b5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1069,6 +1069,7 @@ def make_purchase_invoice(source_name, target_doc=None): "is_fixed_asset": "is_fixed_asset", "asset_location": "asset_location", "asset_category": "asset_category", + "wip_composite_asset": "wip_composite_asset", }, "postprocess": update_item, "filter": lambda d: get_pending_qty(d)[0] <= 0 From fd78f868e1aed2bdb3baa47927f37b239e16a174 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 27 Oct 2023 23:33:04 +0530 Subject: [PATCH 225/280] fix: unsupported operand type(s) for serial and batch bundle in POS Invoice (#37721) --- .../doctype/pos_invoice/test_pos_invoice.py | 36 ++++++++++++++++--- .../serial_and_batch_bundle.py | 9 +++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 887f1eaeb1..982bdc198a 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -771,19 +771,28 @@ class TestPOSInvoice(unittest.TestCase): ) create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02") - make_stock_entry( + se = make_stock_entry( target="_Test Warehouse - _TC", item_code="_BATCH ITEM Test For Reserve", - qty=20, + qty=30, basic_rate=100, - batch_no="TestBatch-RS 02", ) + se.reload() + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + # POS Invoice 1, for the batch without bundle pos_inv1 = create_pos_invoice( - item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02" + item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1 ) + + pos_inv1.items[0].batch_no = batch_no pos_inv1.save() pos_inv1.submit() + pos_inv1.reload() + + self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle) batches = get_auto_batch_nos( frappe._dict( @@ -792,7 +801,24 @@ class TestPOSInvoice(unittest.TestCase): ) for batch in batches: - if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC": + if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC": + self.assertEqual(batch.qty, 15) + + # POS Invoice 2, for the batch with bundle + pos_inv2 = create_pos_invoice( + item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no + ) + pos_inv2.reload() + self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle) + + batches = get_auto_batch_nos( + frappe._dict( + {"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"} + ) + ) + + for batch in batches: + if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC": self.assertEqual(batch.qty, 5) def test_pos_batch_item_qty_validation(self): diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 96e4a55630..8142ba5927 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1301,6 +1301,7 @@ def get_reserved_batches_for_pos(kwargs) -> dict: "POS Invoice", fields=[ "`tabPOS Invoice Item`.batch_no", + "`tabPOS Invoice Item`.qty", "`tabPOS Invoice`.is_return", "`tabPOS Invoice Item`.warehouse", "`tabPOS Invoice Item`.name as child_docname", @@ -1321,9 +1322,6 @@ def get_reserved_batches_for_pos(kwargs) -> dict: if pos_invoice.serial_and_batch_bundle ] - if not ids: - return {} - if ids: for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids): key = (d.batch_no, d.warehouse) @@ -1337,6 +1335,7 @@ def get_reserved_batches_for_pos(kwargs) -> dict: else: pos_batches[key].qty += d.qty + # POS invoices having batch without bundle (to handle old POS invoices) for row in pos_invoices: if not row.batch_no: continue @@ -1346,11 +1345,11 @@ def get_reserved_batches_for_pos(kwargs) -> dict: key = (row.batch_no, row.warehouse) if key in pos_batches: - pos_batches[key] -= row.qty * -1 if row.is_return else row.qty + pos_batches[key]["qty"] -= row.qty * -1 if row.is_return else row.qty else: pos_batches[key] = frappe._dict( { - "qty": (row.qty * -1 if row.is_return else row.qty), + "qty": (row.qty * -1 if not row.is_return else row.qty), "warehouse": row.warehouse, } ) From f276fbba4f84979e12b8091492be7eddbf0caa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Sat, 28 Oct 2023 02:10:28 +0200 Subject: [PATCH 226/280] refactor: remove extraneous disabled filters --- .../profitability_analysis/profitability_analysis.js | 7 ------- erpnext/assets/doctype/asset/asset.js | 1 - .../supplier_quotation_comparison.js | 5 ----- .../report/bom_operations_time/bom_operations_time.js | 2 +- erpnext/public/js/controllers/accounts.js | 1 - erpnext/stock/doctype/item_price/item_price.js | 1 - .../doctype/stock_reconciliation/stock_reconciliation.js | 7 ------- erpnext/support/doctype/issue/issue.js | 7 ------- 8 files changed, 1 insertion(+), 30 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 4a3d9bb479..b6bbd979ed 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -32,13 +32,6 @@ frappe.query_reports["Profitability Analysis"] = { "label": __("Accounting Dimension"), "fieldtype": "Link", "options": "Accounting Dimension", - "get_query": () =>{ - return { - filters: { - "disabled": 0 - } - } - } }, { "fieldname": "fiscal_year", diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index f0e4c82048..d378fbd26a 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', { frm.set_query("item_code", function() { return { "filters": { - "disabled": 0, "is_fixed_asset": 1, "is_stock_item": 0 } diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index fd73b870c5..579c0a65ad 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -44,11 +44,6 @@ frappe.query_reports["Supplier Quotation Comparison"] = { } } } - else { - return { - filters: { "disabled": 0 } - } - } } }, { diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 34edb9d538..8729775dc2 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -12,7 +12,7 @@ frappe.query_reports["BOM Operations Time"] = { "options": "Item", "get_query": () =>{ return { - filters: { "disabled": 0, "is_stock_item": 1 } + filters: { "is_stock_item": 1 } } } }, diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 354552137b..7879173cd1 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -30,7 +30,6 @@ erpnext.accounts.taxes = { filters: { "account_type": account_type, "company": doc.company, - "disabled": 0 } } }); diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js index ce489ff52b..8a4b4eef0a 100644 --- a/erpnext/stock/doctype/item_price/item_price.js +++ b/erpnext/stock/doctype/item_price/item_price.js @@ -6,7 +6,6 @@ frappe.ui.form.on("Item Price", { frm.set_query("item_code", function() { return { filters: { - "disabled": 0, "has_variants": 0 } }; diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 5452692a24..b3998b7c7e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -123,13 +123,6 @@ frappe.ui.form.on("Stock Reconciliation", { fieldname: "item_code", fieldtype: "Link", options: "Item", - "get_query": function() { - return { - "filters": { - "disabled": 0, - } - }; - } }, { label: __("Ignore Empty Stock"), diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index d4daacd4ea..f96823b290 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -1,13 +1,6 @@ frappe.ui.form.on("Issue", { onload: function(frm) { frm.email_field = "raised_by"; - frm.set_query("customer", function () { - return { - filters: { - "disabled": 0 - } - }; - }); frappe.db.get_value("Support Settings", {name: "Support Settings"}, ["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => { From 3a8736374cfd73d828cd98936900cef1ec78fdb7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 29 Oct 2023 10:18:47 +0530 Subject: [PATCH 227/280] fix: fetch asset received but not billed account only when needed --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c398d14183..e1f0f1932e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -585,7 +585,6 @@ class PurchaseInvoice(BuyingController): def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) - self.asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") if self.auto_accounting_for_stock: self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") @@ -937,10 +936,11 @@ class PurchaseInvoice(BuyingController): ) stock_rbnb = ( - self.asset_received_but_not_billed + self.get_company_default("asset_received_but_not_billed") if item.is_fixed_asset else self.stock_received_but_not_billed ) + if not negative_expense_booked_in_pr: gl_entries.append( self.get_gl_dict( From e72afd0bd6c0b6100834888329f5bf985a826b65 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:48:48 +0100 Subject: [PATCH 228/280] fix: make project page translatable --- erpnext/templates/pages/projects.html | 44 +++++++++++++-------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index 9fe4338477..3b8698f4ab 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -14,18 +14,16 @@ {% block style %} {% endblock %} {% block page_content %}
-
Status: {{ doc.status }}
-
Progress: {{ doc.percent_complete }}%
-
Hours Spent: {{ doc.actual_time | round }}
+
{{ _("Status") }}: {{ _(doc.status) }}
+
{{ _("Progress") }}: {{ doc.get_formatted("percent_complete") }}
+
{{ _("Hours Spent") }}: {{ doc.get_formatted("actual_time") }}
@@ -34,7 +32,7 @@
- + @@ -44,39 +42,39 @@
-
Tasks
-
Status
-
End Date
-
Assignment
-
Modified On
+
{{ _("Tasks") }}
+
{{ _("Status") }}
+
{{ _("End Date") }}
+
{{ _("Assignment") }}
+
{{ _("Modified On") }}
{% include "erpnext/templates/includes/projects/project_tasks.html" %}
{% else %} - {{ empty_state('Task')}} + {{ empty_state(_("Task")) }} {% endif %} - + {% if doc.timesheets %}
-
Timesheet
-
Status
-
From
-
To
-
Modified By
-
Modified On
+
{{ _("Timesheet") }}
+
{{ _("Status") }}
+
{{ _("From") }}
+
{{ _("To") }}
+
{{ _("Modified By") }}
+
{{ _("Modified On") }}
{% include "erpnext/templates/includes/projects/project_timesheets.html" %}
{% else %} - {{ empty_state('Timesheet')}} + {{ empty_state(_("Timesheet")) }} {% endif %} {% if doc.attachments %} @@ -113,7 +111,7 @@ {% macro progress_bar(percent_complete) %} {% if percent_complete %} - Project Progress: + {{ _("Project Progress:") }}
Generic Empty State
-

You haven't created a {{ section_name }} yet

+

{{ _("You haven't created a {0} yet").format(section_name) }}

From de58c679918229c15925cd3257cc5ddbc7c20473 Mon Sep 17 00:00:00 2001 From: viralkansodiya15 <98073516+viralpatel15@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:44:17 +0530 Subject: [PATCH 229/280] fix: linter test solve --- .../landed_cost_voucher/landed_cost_voucher.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 5e35c16365..69b4cc1cf1 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -204,10 +204,14 @@ class LandedCostVoucher(Document): "purchase_invoice" if item.receipt_document_type == "Purchase Invoice" else "purchase_receipt" ) docs = frappe.db.get_all( - "Asset", - filters={receipt_document_type: item.receipt_document, "item_code": item.item_code, "docstatus":['!=', 2]}, - fields=["name", "docstatus"], - ) + "Asset", ++ filters={ ++ receipt_document_type: item.receipt_document, ++ "item_code": item.item_code, ++ "docstatus": ["!=", 2], ++ }, + fields=["name", "docstatus"], + ) if not docs or len(docs) != item.qty: frappe.throw( _( From a15484fe3d38923b2e4ae3128b475ea1b6fa364c Mon Sep 17 00:00:00 2001 From: viralkansodiya15 <98073516+viralpatel15@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:01:25 +0530 Subject: [PATCH 230/280] fix: solve linter test and update --- .../landed_cost_voucher/landed_cost_voucher.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 69b4cc1cf1..8bbc660899 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -204,14 +204,14 @@ class LandedCostVoucher(Document): "purchase_invoice" if item.receipt_document_type == "Purchase Invoice" else "purchase_receipt" ) docs = frappe.db.get_all( - "Asset", -+ filters={ -+ receipt_document_type: item.receipt_document, -+ "item_code": item.item_code, -+ "docstatus": ["!=", 2], -+ }, - fields=["name", "docstatus"], - ) + "Asset", + filters={ + receipt_document_type: item.receipt_document, + "item_code": item.item_code, + "docstatus": ["!=", 2], + }, + fields=["name", "docstatus"], + ) if not docs or len(docs) != item.qty: frappe.throw( _( From 500435b856a028bdab7fdbe12647ec0f11287eab Mon Sep 17 00:00:00 2001 From: Didiman1998 <118364772+Didiman1998@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:48:41 +0100 Subject: [PATCH 231/280] fix: make changes that enable gantt view for job cards (#37661) * fix: make changes that enable gantt view for job cards * fix: add fields on listview and remove from json file * fix: undo modified date --------- Co-authored-by: Dietmar Fischer --- erpnext/manufacturing/doctype/job_card/job_card_calendar.js | 4 ++-- erpnext/manufacturing/doctype/job_card/job_card_list.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js index f4877fdca0..9e32085351 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js +++ b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js @@ -10,8 +10,8 @@ frappe.views.calendar["Job Card"] = { }, gantt: { field_map: { - "start": "started_time", - "end": "started_time", + "start": "expected_start_date", + "end": "expected_end_date", "id": "name", "title": "subject", "color": "color", diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js index 5d883bf9fa..99fca9570f 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_list.js +++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js @@ -1,6 +1,6 @@ frappe.listview_settings['Job Card'] = { has_indicator_for_draft: true, - + add_fields: ["expected_start_date", "expected_end_date"], get_indicator: function(doc) { const status_colors = { "Work In Progress": "orange", From afc64ed9eedb1aca2802f5d5e53cc5668359f575 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 30 Oct 2023 15:39:11 +0530 Subject: [PATCH 232/280] fix: ignore permissions while mapping DN Item --- erpnext/selling/doctype/sales_order/sales_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 94f9d6e37c..2f6578ea16 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -831,6 +831,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): "postprocess": update_dn_item, } }, + ignore_permissions=True, ) dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1)) From ca698452382eba85bd940dfb6344ff19d1eab4e4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 30 Oct 2023 16:15:05 +0530 Subject: [PATCH 233/280] chore: add index to posting_date in PLE --- .../doctype/payment_ledger_entry/payment_ledger_entry.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 9cf2ac6c2a..4ae813571a 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -30,7 +30,8 @@ { "fieldname": "posting_date", "fieldtype": "Date", - "label": "Posting Date" + "label": "Posting Date", + "search_index": 1 }, { "fieldname": "account_type", @@ -153,7 +154,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-06-29 12:24:20.500632", + "modified": "2023-10-30 16:15:00.470283", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Ledger Entry", From 056b74b162ed58dd979cd9748129752fb8cab242 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 31 Oct 2023 12:32:52 +0530 Subject: [PATCH 234/280] fix: indexing on Delivery Note Item (#37766) fix: added indexing on Delivery Note Item --- .../doctype/purchase_receipt_item/purchase_receipt_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index f5240a6094..718f007577 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -900,7 +900,8 @@ "label": "Delivery Note Item", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "collapsible": 1, @@ -1089,7 +1090,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-19 10:50:58.071735", + "modified": "2023-10-30 17:32:24.560337", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From d758fc1b8910dfa339f82edf860722414fbfe242 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:28:27 +0530 Subject: [PATCH 235/280] fix: incorrect material request quantity in production plan (backport #37785) (#37789) fix: incorrect material request quantity in production plan (#37785) (cherry picked from commit 25718d9f1b7cda3b87263c2cf885958cbd283947) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 5 +++- .../production_plan/test_production_plan.py | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index ddd9375211..1850d1e09e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1735,7 +1735,10 @@ def get_raw_materials_of_sub_assembly_items( if not item.conversion_factor and item.purchase_uom: item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom) - item_details.setdefault(item.get("item_code"), item) + if details := item_details.get(item.get("item_code")): + details.qty += item.get("qty") + else: + item_details.setdefault(item.get("item_code"), item) return item_details diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 6ab9232788..d414988f41 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1332,6 +1332,33 @@ class TestProductionPlan(FrappeTestCase): self.assertTrue(row.warehouse == mrp_warhouse) self.assertEqual(row.quantity, 12) + def test_mr_qty_for_same_rm_with_different_sub_assemblies(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + bom_tree = { + "Fininshed Goods2 For SUB Test": { + "SubAssembly2 For SUB Test": {"ChildPart2 For SUB Test": {}}, + "SubAssembly3 For SUB Test": {"ChildPart2 For SUB Test": {}}, + } + } + + parent_bom = create_nested_bom(bom_tree, prefix="") + plan = create_production_plan( + item_code=parent_bom.item, + planned_qty=1, + ignore_existing_ordered_qty=1, + do_not_submit=1, + skip_available_sub_assembly_item=1, + warehouse="_Test Warehouse - _TC", + ) + + plan.get_sub_assembly_items() + plan.make_material_request() + + for row in plan.mr_items: + if row.item_code == "ChildPart2 For SUB Test": + self.assertEqual(row.quantity, 2) + def create_production_plan(**args): """ From 139a68fd0f1c1d8893b1f121c13781e25b0a2629 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 31 Oct 2023 12:20:09 +0100 Subject: [PATCH 236/280] test: fix bad test data (#37773) * test(stock): fix bad test data * test(asset): fix bad test data * test(stock): fix bad test data * test(manufacturing): fix bad test data --- .../test_asset_maintenance.py | 139 ++++-------------- .../asset_maintenance/test_records.json | 68 +++++++++ .../doctype/work_order/test_work_order.py | 1 + .../test_records.json | 4 + .../test_repost_item_valuation.py | 4 - 5 files changed, 100 insertions(+), 116 deletions(-) create mode 100644 erpnext/assets/doctype/asset_maintenance/test_records.json diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 23088c9ccf..eac875896b 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -13,25 +13,22 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu class TestAssetMaintenance(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() - create_asset_data() - create_maintenance_team() - - def test_create_asset_maintenance(self): - pr = make_purchase_receipt( + self.pr = make_purchase_receipt( item_code="Photocopier", qty=1, rate=100000.0, location="Test Location" ) + self.asset_name = frappe.db.get_value("Asset", {"purchase_receipt": self.pr.name}, "name") + self.asset_doc = frappe.get_doc("Asset", self.asset_name) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name") - asset_doc = frappe.get_doc("Asset", asset_name) + def test_create_asset_maintenance_with_log(self): month_end_date = get_last_day(nowdate()) purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - asset_doc.available_for_use_date = purchase_date - asset_doc.purchase_date = purchase_date + self.asset_doc.available_for_use_date = purchase_date + self.asset_doc.purchase_date = purchase_date - asset_doc.calculate_depreciation = 1 - asset_doc.append( + self.asset_doc.calculate_depreciation = 1 + self.asset_doc.append( "finance_books", { "expected_value_after_useful_life": 200, @@ -42,97 +39,32 @@ class TestAssetMaintenance(unittest.TestCase): }, ) - asset_doc.save() + self.asset_doc.save() - if not frappe.db.exists("Asset Maintenance", "Photocopier"): - asset_maintenance = frappe.get_doc( - { - "doctype": "Asset Maintenance", - "asset_name": "Photocopier", - "maintenance_team": "Team Awesome", - "company": "_Test Company", - "asset_maintenance_tasks": get_maintenance_tasks(), - } - ).insert() + asset_maintenance = frappe.get_doc( + { + "doctype": "Asset Maintenance", + "asset_name": self.asset_name, + "maintenance_team": "Team Awesome", + "company": "_Test Company", + "asset_maintenance_tasks": get_maintenance_tasks(), + } + ).insert() - next_due_date = calculate_next_due_date(nowdate(), "Monthly") - self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) - - def test_create_asset_maintenance_log(self): - if not frappe.db.exists("Asset Maintenance Log", "Photocopier"): - asset_maintenance_log = frappe.get_doc( - { - "doctype": "Asset Maintenance Log", - "asset_maintenance": "Photocopier", - "task": "Change Oil", - "completion_date": add_days(nowdate(), 2), - "maintenance_status": "Completed", - } - ).insert() - asset_maintenance = frappe.get_doc("Asset Maintenance", "Photocopier") - next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly") + next_due_date = calculate_next_due_date(nowdate(), "Monthly") self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) - -def create_asset_data(): - if not frappe.db.exists("Asset Category", "Equipment"): - create_asset_category() - - if not frappe.db.exists("Location", "Test Location"): - frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() - - if not frappe.db.exists("Item", "Photocopier"): - meta = frappe.get_meta("Asset") - naming_series = meta.get_field("naming_series").options - frappe.get_doc( + asset_maintenance_log = frappe.get_doc( { - "doctype": "Item", - "item_code": "Photocopier", - "item_name": "Photocopier", - "item_group": "All Item Groups", - "company": "_Test Company", - "is_fixed_asset": 1, - "is_stock_item": 0, - "asset_category": "Equipment", - "auto_create_assets": 1, - "asset_naming_series": naming_series, + "doctype": "Asset Maintenance Log", + "asset_maintenance": self.asset_name, + "task": "Change Oil", + "completion_date": add_days(nowdate(), 2), + "maintenance_status": "Completed", } ).insert() - - -def create_maintenance_team(): - user_list = ["marcus@abc.com", "thalia@abc.com", "mathias@abc.com"] - if not frappe.db.exists("Role", "Technician"): - frappe.get_doc({"doctype": "Role", "role_name": "Technician"}).insert() - for user in user_list: - if not frappe.db.get_value("User", user): - frappe.get_doc( - { - "doctype": "User", - "email": user, - "first_name": user, - "new_password": "password", - "roles": [{"doctype": "Has Role", "role": "Technician"}], - } - ).insert() - - if not frappe.db.exists("Asset Maintenance Team", "Team Awesome"): - frappe.get_doc( - { - "doctype": "Asset Maintenance Team", - "maintenance_manager": "marcus@abc.com", - "maintenance_team_name": "Team Awesome", - "company": "_Test Company", - "maintenance_team_members": get_maintenance_team(user_list), - } - ).insert() - - -def get_maintenance_team(user_list): - return [ - {"team_member": user, "full_name": user, "maintenance_role": "Technician"} - for user in user_list[1:] - ] + next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly") + self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) def get_maintenance_tasks(): @@ -156,23 +88,6 @@ def get_maintenance_tasks(): ] -def create_asset_category(): - asset_category = frappe.new_doc("Asset Category") - asset_category.asset_category_name = "Equipment" - asset_category.total_number_of_depreciations = 3 - asset_category.frequency_of_depreciation = 3 - asset_category.append( - "accounts", - { - "company_name": "_Test Company", - "fixed_asset_account": "_Test Fixed Asset - _TC", - "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", - "depreciation_expense_account": "_Test Depreciations - _TC", - }, - ) - asset_category.insert() - - def set_depreciation_settings_in_company(): company = frappe.get_doc("Company", "_Test Company") company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC" diff --git a/erpnext/assets/doctype/asset_maintenance/test_records.json b/erpnext/assets/doctype/asset_maintenance/test_records.json new file mode 100644 index 0000000000..8306fad6cb --- /dev/null +++ b/erpnext/assets/doctype/asset_maintenance/test_records.json @@ -0,0 +1,68 @@ +[ + { + "doctype": "Asset Category", + "asset_category_name": "Equipment", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 3, + "accounts": [ + { + "company_name": "_Test Company", + "fixed_asset_account": "_Test Fixed Asset - _TC", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", + "depreciation_expense_account": "_Test Depreciations - _TC" + } + ] + }, + { + "doctype": "Location", + "location_name": "Test Location" + }, + { + "doctype": "Role", + "role_name": "Technician" + }, + { + "doctype": "User", + "email": "marcus@abc.com", + "first_name": "marcus@abc.com", + "new_password": "password", + "roles": [{"doctype": "Has Role", "role": "Technician"}] + }, + { + "doctype": "User", + "email": "thalia@abc.com", + "first_name": "thalia@abc.com", + "new_password": "password", + "roles": [{"doctype": "Has Role", "role": "Technician"}] + }, + { + "doctype": "User", + "email": "mathias@abc.com", + "first_name": "mathias@abc.com", + "new_password": "password", + "roles": [{"doctype": "Has Role", "role": "Technician"}] + }, + { + "doctype": "Asset Maintenance Team", + "maintenance_manager": "marcus@abc.com", + "maintenance_team_name": "Team Awesome", + "company": "_Test Company", + "maintenance_team_members": [ + {"team_member": "marcus@abc.com", "full_name": "marcus@abc.com", "maintenance_role": "Technician"}, + {"team_member": "thalia@abc.com", "full_name": "thalia@abc.com", "maintenance_role": "Technician"}, + {"team_member": "mathias@abc.com", "full_name": "mathias@abc.com", "maintenance_role": "Technician"} + ] + }, + { + "doctype": "Item", + "item_code": "Photocopier", + "item_name": "Photocopier", + "item_group": "All Item Groups", + "company": "_Test Company", + "is_fixed_asset": 1, + "is_stock_item": 0, + "asset_category": "Equipment", + "auto_create_assets": 1, + "asset_naming_series": "ABC.###" + } +] diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index c828c878eb..0ae7657c42 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -494,6 +494,7 @@ class TestWorkOrder(FrappeTestCase): "from_time": row.from_time, "to_time": row.to_time, "time_in_mins": row.time_in_mins, + "completed_qty": 0, }, ) diff --git a/erpnext/stock/doctype/quality_inspection_template/test_records.json b/erpnext/stock/doctype/quality_inspection_template/test_records.json index 980f49a80a..2da99f616a 100644 --- a/erpnext/stock/doctype/quality_inspection_template/test_records.json +++ b/erpnext/stock/doctype/quality_inspection_template/test_records.json @@ -1,4 +1,8 @@ [ + { + "doctype": "Quality Inspection Parameter", + "parameter" : "_Test Param" + }, { "quality_inspection_template_name" : "_Test Quality Inspection Template", "doctype": "Quality Inspection Template", 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 1853f45f58..623e8fafe9 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 @@ -137,8 +137,6 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): item_code="_Test Item", warehouse="_Test Warehouse - _TC", based_on="Item and Warehouse", - voucher_type="Sales Invoice", - voucher_no="SI-1", posting_date="2021-01-02", posting_time="00:01:00", ) @@ -148,8 +146,6 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): riv1.flags.dont_run_in_test = True riv1.submit() _assert_status(riv1, "Queued") - self.assertEqual(riv1.voucher_type, "Sales Invoice") # traceability - self.assertEqual(riv1.voucher_no, "SI-1") # newer than existing duplicate - riv1 riv2 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-03"})) From eb73017798eed964e8f019db4cef3513162855f6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 31 Oct 2023 17:30:42 +0530 Subject: [PATCH 237/280] refactor: pull remarks only if needed on AR/AP report --- .../report/accounts_receivable/accounts_receivable.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index b9c7a0bfb8..20444f9496 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -718,6 +718,7 @@ class ReceivablePayableReport(object): query = ( qb.from_(ple) .select( + ple.name, ple.account, ple.voucher_type, ple.voucher_no, @@ -731,13 +732,15 @@ class ReceivablePayableReport(object): ple.account_currency, ple.amount, ple.amount_in_account_currency, - ple.remarks, ) .where(ple.delinked == 0) .where(Criterion.all(self.qb_selection_filter)) .where(Criterion.any(self.or_filters)) ) + if self.filters.get("show_remarks"): + query = query.select(ple.remarks) + if self.filters.get("group_by_party"): query = query.orderby(self.ple.party, self.ple.posting_date) else: From fb0ec74d086085160fbcca9ccfffb6966673e0cb Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 31 Oct 2023 13:11:44 +0100 Subject: [PATCH 238/280] fix(packed_item): ensure proper names for ref integrity (#37597) --- erpnext/selling/doctype/sales_order/sales_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 2f6578ea16..a40cde12f8 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -759,6 +759,8 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): if target.company_address: target.update(get_fetch_values("Delivery Note", "company_address", target.company_address)) + # set target items names to ensure proper linking with packed_items + target.set_new_name() make_packing_list(target) def condition(doc): From 1fd888175f2f224bcebba3e9a958d672ab09bcd5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 31 Oct 2023 17:47:02 +0530 Subject: [PATCH 239/280] chore: update default limit values in reconciliation tool --- erpnext/patches.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d7f33adeea..78d2c2c340 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -343,5 +343,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item erpnext.patches.v15_0.update_sre_from_voucher_details erpnext.patches.v14_0.rename_over_order_allowance_field erpnext.patches.v14_0.migrate_delivery_stop_lock_field +execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50) +execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50) # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger From 77af247450fc6901ae49ef6109073bb4f637113c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 31 Oct 2023 18:02:24 +0530 Subject: [PATCH 240/280] chore: fixed test cases (#37792) --- .../test_asset_maintenance.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index eac875896b..a33acfd833 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -54,16 +54,24 @@ class TestAssetMaintenance(unittest.TestCase): next_due_date = calculate_next_due_date(nowdate(), "Monthly") self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) - asset_maintenance_log = frappe.get_doc( + asset_maintenance_log = frappe.db.get_value( + "Asset Maintenance Log", + {"asset_maintenance": asset_maintenance.name, "task_name": "Change Oil"}, + "name", + ) + + asset_maintenance_log_doc = frappe.get_doc("Asset Maintenance Log", asset_maintenance_log) + asset_maintenance_log_doc.update( { - "doctype": "Asset Maintenance Log", - "asset_maintenance": self.asset_name, - "task": "Change Oil", "completion_date": add_days(nowdate(), 2), "maintenance_status": "Completed", } - ).insert() - next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly") + ) + + asset_maintenance_log_doc.save() + next_due_date = calculate_next_due_date(asset_maintenance_log_doc.completion_date, "Monthly") + + asset_maintenance.reload() self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date) From daf2ec063c4b0313e32e247459d0cb8dc6958833 Mon Sep 17 00:00:00 2001 From: hyaray Date: Tue, 31 Oct 2023 21:21:27 +0800 Subject: [PATCH 241/280] fix: In-Transit Warehouse company filter (#37796) --- erpnext/setup/doctype/company/company.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 23b93dc161..1bd469b956 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -40,7 +40,7 @@ frappe.ui.form.on("Company", { filters:{ 'warehouse_type' : 'Transit', 'is_group': 0, - 'company': frm.doc.company + 'company': frm.doc.company_name } }; }); From e16cc38b70ef39f8f43a85eb555fbb05cdadee6e Mon Sep 17 00:00:00 2001 From: Aadhil <36843795+aadhilpm@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:47:15 +0300 Subject: [PATCH 242/280] fix: remove GoCardless Settings and Mpesa Settings from Workspace --- .../erpnext_integrations.json | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json index 510317f5c2..dfef223c43 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -192,26 +192,6 @@ "onboard": 0, "type": "Card Break" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "GoCardless Settings", - "link_count": 0, - "link_to": "GoCardless Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Mpesa Settings", - "link_count": 0, - "link_to": "Mpesa Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -223,7 +203,7 @@ "type": "Link" } ], - "modified": "2023-08-29 15:48:59.010704", + "modified": "2023-10-31 19:57:32.748726", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations", From e0a03789aef1891273ed6fe5a83b182d3f76e1b7 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 31 Oct 2023 16:53:08 +0100 Subject: [PATCH 243/280] fix: Use `process.extract` to get the corresponding party doc name of the result - rapidfuzz accepts an iterable or a dict. dict input gives the dict key and value in the result --- .../doctype/bank_transaction/auto_match_party.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py index 671cde58a9..04dab4c28a 100644 --- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py +++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py @@ -1,7 +1,6 @@ from typing import Tuple, Union import frappe -from frappe.core.utils import find from frappe.utils import flt from rapidfuzz import fuzz, process @@ -135,7 +134,7 @@ class AutoMatchbyPartyNameDescription: skip = False result = process.extract( query=self.get(field), - choices=[name.get("party_name") for name in names], + choices={row.get("name"): row.get("party_name") for row in names}, scorer=fuzz.token_set_ratio, ) party_name, skip = self.process_fuzzy_result(result) @@ -143,8 +142,6 @@ class AutoMatchbyPartyNameDescription: if not party_name: return None, skip - # Get Party Docname from the list of dicts - party_name = find(names, lambda x: x["party_name"] == party_name).get("name") return ( party, party_name, @@ -157,14 +154,14 @@ class AutoMatchbyPartyNameDescription: Returns: Result, Skip (whether or not to discontinue matching) """ - PARTY, SCORE, CUTOFF = 0, 1, 80 + SCORE, PARTY_ID, CUTOFF = 1, 2, 80 if not result or not len(result): return None, False first_result = result[0] if len(result) == 1: - return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True + return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True second_result = result[1] if first_result[SCORE] > CUTOFF: @@ -173,7 +170,7 @@ class AutoMatchbyPartyNameDescription: if first_result[SCORE] == second_result[SCORE]: return None, True - return first_result[PARTY], True + return first_result[PARTY_ID], True else: return None, False From 028b3e2fbf52cdb7561e8d63b54d5918b5bc8af4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 11:48:27 +0530 Subject: [PATCH 244/280] fix: `TypeError` in PR for non-stock item --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 2a4b6f34b5..cbc1693eaa 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -600,11 +600,10 @@ class PurchaseReceipt(BuyingController): make_rate_difference_entry(d) make_sub_contracting_gl_entries(d) make_divisional_loss_gl_entry(d, outgoing_amount) - elif ( - d.warehouse not in warehouse_with_no_account - or d.rejected_warehouse not in warehouse_with_no_account + elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or ( + d.rejected_warehouse and d.rejected_warehouse not in warehouse_with_no_account ): - warehouse_with_no_account.append(d.warehouse) + warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse) if d.is_fixed_asset: self.update_assets(d, d.valuation_rate) From 7e67d42d1d1837e47a5df3b1e6e22a72c996d761 Mon Sep 17 00:00:00 2001 From: mrchenxxx <44868578+mrchenxxx@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:55:36 +0800 Subject: [PATCH 245/280] chore: Update translations chore: Update translations --- erpnext/translations/zh.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index 1d8a261005..cf89dc6852 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -8743,3 +8743,4 @@ WhatsApp,WhatsApp的, Make a call,打个电话, Approve,同意, Reject,拒绝, +Stock,库存, From ec1a7869f82c9bb3d5e12e8a9dc695823e502a4a Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 15:35:18 +0530 Subject: [PATCH 246/280] refactor: rearrange fields and update label --- erpnext/stock/doctype/bin/bin.json | 53 ++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index a11572776a..02371cf90f 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -5,22 +5,27 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "warehouse", "item_code", - "reserved_qty", + "column_break_yreo", + "warehouse", + "section_break_stag", "actual_qty", - "ordered_qty", - "indented_qty", "planned_qty", - "projected_qty", + "indented_qty", + "ordered_qty", + "column_break_xn5j", + "reserved_qty", "reserved_qty_for_production", "reserved_qty_for_sub_contract", "reserved_qty_for_production_plan", - "ma_rate", + "projected_qty", + "section_break_pmrs", "stock_uom", - "fcfs_rate", + "column_break_0slj", "valuation_rate", - "stock_value" + "stock_value", + "fcfs_rate", + "ma_rate" ], "fields": [ { @@ -56,7 +61,7 @@ "fieldname": "reserved_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Reserved Quantity", + "label": "Reserved Qty", "oldfieldname": "reserved_qty", "oldfieldtype": "Currency", "read_only": 1 @@ -67,7 +72,7 @@ "fieldtype": "Float", "in_filter": 1, "in_list_view": 1, - "label": "Actual Quantity", + "label": "Actual Qty", "oldfieldname": "actual_qty", "oldfieldtype": "Currency", "read_only": 1 @@ -77,7 +82,7 @@ "fieldname": "ordered_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Ordered Quantity", + "label": "Ordered Qty", "oldfieldname": "ordered_qty", "oldfieldtype": "Currency", "read_only": 1 @@ -86,7 +91,7 @@ "default": "0.00", "fieldname": "indented_qty", "fieldtype": "Float", - "label": "Requested Quantity", + "label": "Requested Qty", "oldfieldname": "indented_qty", "oldfieldtype": "Currency", "read_only": 1 @@ -116,7 +121,7 @@ { "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", - "label": "Reserved Qty for sub contract", + "label": "Reserved Qty for Subcontract", "read_only": 1 }, { @@ -172,13 +177,33 @@ "fieldtype": "Float", "label": "Reserved Qty for Production Plan", "read_only": 1 + }, + { + "fieldname": "section_break_stag", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_yreo", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_xn5j", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_pmrs", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_0slj", + "fieldtype": "Column Break" } ], "hide_toolbar": 1, "idx": 1, "in_create": 1, "links": [], - "modified": "2023-05-02 23:26:21.806965", + "modified": "2023-11-01 15:28:55.240522", "modified_by": "Administrator", "module": "Stock", "name": "Bin", From f0a1f4ac7cb9362e8dfe05179533fe4ad00d061e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 15:36:41 +0530 Subject: [PATCH 247/280] refactor: remove unused fields `fcfs_rate` and `ma_rate` from Bin --- erpnext/stock/doctype/bin/bin.json | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index 02371cf90f..02684a7241 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -23,9 +23,7 @@ "stock_uom", "column_break_0slj", "valuation_rate", - "stock_value", - "fcfs_rate", - "ma_rate" + "stock_value" ], "fields": [ { @@ -124,17 +122,6 @@ "label": "Reserved Qty for Subcontract", "read_only": 1 }, - { - "fieldname": "ma_rate", - "fieldtype": "Float", - "hidden": 1, - "label": "Moving Average Rate", - "oldfieldname": "ma_rate", - "oldfieldtype": "Currency", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, { "fieldname": "stock_uom", "fieldtype": "Link", @@ -145,17 +132,6 @@ "options": "UOM", "read_only": 1 }, - { - "fieldname": "fcfs_rate", - "fieldtype": "Float", - "hidden": 1, - "label": "FCFS Rate", - "oldfieldname": "fcfs_rate", - "oldfieldtype": "Currency", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, { "fieldname": "valuation_rate", "fieldtype": "Float", @@ -203,7 +179,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2023-11-01 15:28:55.240522", + "modified": "2023-11-01 15:35:51.722534", "modified_by": "Administrator", "module": "Stock", "name": "Bin", From c5f5aa8208ce2449e626cc0e4cf120f30b0260b9 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:19:21 +0100 Subject: [PATCH 248/280] feat: report Timesheet Billing Summary (#37451) --- erpnext/projects/report/billing_summary.py | 155 ------------------ .../employee_billing_summary.js | 34 ---- .../employee_billing_summary.json | 36 ---- .../employee_billing_summary.py | 15 -- .../project_billing_summary/__init__.py | 0 .../project_billing_summary.js | 34 ---- .../project_billing_summary.py | 15 -- .../__init__.py | 0 .../timesheet_billing_summary.js | 67 ++++++++ .../timesheet_billing_summary.json} | 22 ++- .../timesheet_billing_summary.py | 146 +++++++++++++++++ .../projects/workspace/projects/projects.json | 10 +- 12 files changed, 232 insertions(+), 302 deletions(-) delete mode 100644 erpnext/projects/report/billing_summary.py delete mode 100644 erpnext/projects/report/employee_billing_summary/employee_billing_summary.js delete mode 100644 erpnext/projects/report/employee_billing_summary/employee_billing_summary.json delete mode 100644 erpnext/projects/report/employee_billing_summary/employee_billing_summary.py delete mode 100644 erpnext/projects/report/project_billing_summary/__init__.py delete mode 100644 erpnext/projects/report/project_billing_summary/project_billing_summary.js delete mode 100644 erpnext/projects/report/project_billing_summary/project_billing_summary.py rename erpnext/projects/report/{employee_billing_summary => timesheet_billing_summary}/__init__.py (100%) create mode 100644 erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js rename erpnext/projects/report/{project_billing_summary/project_billing_summary.json => timesheet_billing_summary/timesheet_billing_summary.json} (61%) create mode 100644 erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py deleted file mode 100644 index ac1524a49d..0000000000 --- a/erpnext/projects/report/billing_summary.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.utils import flt, time_diff_in_hours - - -def get_columns(): - return [ - { - "label": _("Employee ID"), - "fieldtype": "Link", - "fieldname": "employee", - "options": "Employee", - "width": 300, - }, - { - "label": _("Employee Name"), - "fieldtype": "data", - "fieldname": "employee_name", - "hidden": 1, - "width": 200, - }, - { - "label": _("Timesheet"), - "fieldtype": "Link", - "fieldname": "timesheet", - "options": "Timesheet", - "width": 150, - }, - {"label": _("Working Hours"), "fieldtype": "Float", "fieldname": "total_hours", "width": 150}, - { - "label": _("Billable Hours"), - "fieldtype": "Float", - "fieldname": "total_billable_hours", - "width": 150, - }, - {"label": _("Billing Amount"), "fieldtype": "Currency", "fieldname": "amount", "width": 150}, - ] - - -def get_data(filters): - data = [] - if filters.from_date > filters.to_date: - frappe.msgprint(_("From Date can not be greater than To Date")) - return data - - timesheets = get_timesheets(filters) - - filters.from_date = frappe.utils.get_datetime(filters.from_date) - filters.to_date = frappe.utils.add_to_date( - frappe.utils.get_datetime(filters.to_date), days=1, seconds=-1 - ) - - timesheet_details = get_timesheet_details(filters, timesheets.keys()) - - for ts, ts_details in timesheet_details.items(): - total_hours = 0 - total_billing_hours = 0 - total_amount = 0 - - for row in ts_details: - from_time, to_time = filters.from_date, filters.to_date - - if row.to_time < from_time or row.from_time > to_time: - continue - - if row.from_time > from_time: - from_time = row.from_time - - if row.to_time < to_time: - to_time = row.to_time - - activity_duration, billing_duration = get_billable_and_total_duration(row, from_time, to_time) - - total_hours += activity_duration - total_billing_hours += billing_duration - total_amount += billing_duration * flt(row.billing_rate) - - if total_hours: - data.append( - { - "employee": timesheets.get(ts).employee, - "employee_name": timesheets.get(ts).employee_name, - "timesheet": ts, - "total_billable_hours": total_billing_hours, - "total_hours": total_hours, - "amount": total_amount, - } - ) - - return data - - -def get_timesheets(filters): - record_filters = [ - ["start_date", "<=", filters.to_date], - ["end_date", ">=", filters.from_date], - ] - if not filters.get("include_draft_timesheets"): - record_filters.append(["docstatus", "=", 1]) - else: - record_filters.append(["docstatus", "!=", 2]) - if "employee" in filters: - record_filters.append(["employee", "=", filters.employee]) - - timesheets = frappe.get_all( - "Timesheet", filters=record_filters, fields=["employee", "employee_name", "name"] - ) - timesheet_map = frappe._dict() - for d in timesheets: - timesheet_map.setdefault(d.name, d) - - return timesheet_map - - -def get_timesheet_details(filters, timesheet_list): - timesheet_details_filter = {"parent": ["in", timesheet_list]} - - if "project" in filters: - timesheet_details_filter["project"] = filters.project - - timesheet_details = frappe.get_all( - "Timesheet Detail", - filters=timesheet_details_filter, - fields=[ - "from_time", - "to_time", - "hours", - "is_billable", - "billing_hours", - "billing_rate", - "parent", - ], - ) - - timesheet_details_map = frappe._dict() - for d in timesheet_details: - timesheet_details_map.setdefault(d.parent, []).append(d) - - return timesheet_details_map - - -def get_billable_and_total_duration(activity, start_time, end_time): - precision = frappe.get_precision("Timesheet Detail", "hours") - activity_duration = time_diff_in_hours(end_time, start_time) - billing_duration = 0.0 - if activity.is_billable: - billing_duration = activity.billing_hours - if activity_duration != activity.billing_hours: - billing_duration = activity_duration * activity.billing_hours / activity.hours - - return flt(activity_duration, precision), flt(billing_duration, precision) diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js deleted file mode 100644 index 2c25465a61..0000000000 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - - -frappe.query_reports["Employee Billing Summary"] = { - "filters": [ - { - fieldname: "employee", - label: __("Employee"), - fieldtype: "Link", - options: "Employee", - reqd: 1 - }, - { - fieldname:"from_date", - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.month_start(), -1), - reqd: 1 - }, - { - fieldname:"to_date", - label: __("To Date"), - fieldtype: "Date", - default: frappe.datetime.add_days(frappe.datetime.month_start(), -1), - reqd: 1 - }, - { - fieldname:"include_draft_timesheets", - label: __("Include Timesheets in Draft Status"), - fieldtype: "Check", - }, - ] -} diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json deleted file mode 100644 index e5626a0206..0000000000 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "add_total_row": 1, - "creation": "2019-03-08 15:08:19.929728", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2019-06-13 15:54:49.213973", - "modified_by": "Administrator", - "module": "Projects", - "name": "Employee Billing Summary", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Timesheet", - "report_name": "Employee Billing Summary", - "report_type": "Script Report", - "roles": [ - { - "role": "Projects User" - }, - { - "role": "HR User" - }, - { - "role": "Manufacturing User" - }, - { - "role": "Employee" - }, - { - "role": "Accounts User" - } - ] -} \ No newline at end of file diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py deleted file mode 100644 index a2f7378d1b..0000000000 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe - -from erpnext.projects.report.billing_summary import get_columns, get_data - - -def execute(filters=None): - filters = frappe._dict(filters or {}) - columns = get_columns() - - data = get_data(filters) - return columns, data diff --git a/erpnext/projects/report/project_billing_summary/__init__.py b/erpnext/projects/report/project_billing_summary/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js deleted file mode 100644 index fce0c68f11..0000000000 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - - -frappe.query_reports["Project Billing Summary"] = { - "filters": [ - { - fieldname: "project", - label: __("Project"), - fieldtype: "Link", - options: "Project", - reqd: 1 - }, - { - fieldname:"from_date", - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.month_start(), -1), - reqd: 1 - }, - { - fieldname:"to_date", - label: __("To Date"), - fieldtype: "Date", - default: frappe.datetime.add_days(frappe.datetime.month_start(),-1), - reqd: 1 - }, - { - fieldname:"include_draft_timesheets", - label: __("Include Timesheets in Draft Status"), - fieldtype: "Check", - }, - ] -} diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py deleted file mode 100644 index a2f7378d1b..0000000000 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe - -from erpnext.projects.report.billing_summary import get_columns, get_data - - -def execute(filters=None): - filters = frappe._dict(filters or {}) - columns = get_columns() - - data = get_data(filters) - return columns, data diff --git a/erpnext/projects/report/employee_billing_summary/__init__.py b/erpnext/projects/report/timesheet_billing_summary/__init__.py similarity index 100% rename from erpnext/projects/report/employee_billing_summary/__init__.py rename to erpnext/projects/report/timesheet_billing_summary/__init__.py diff --git a/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js new file mode 100644 index 0000000000..1efd0c6733 --- /dev/null +++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js @@ -0,0 +1,67 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Timesheet Billing Summary"] = { + tree: true, + initial_depth: 0, + filters: [ + { + fieldname: "employee", + label: __("Employee"), + fieldtype: "Link", + options: "Employee", + on_change: function (report) { + unset_group_by(report, "employee"); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "Link", + options: "Project", + on_change: function (report) { + unset_group_by(report, "project"); + }, + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_months( + frappe.datetime.month_start(), + -1 + ), + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.add_days( + frappe.datetime.month_start(), + -1 + ), + }, + { // NOTE: `update_group_by_options` expects this filter to be the fifth in the list + fieldname: "group_by", + label: __("Group By"), + fieldtype: "Select", + options: [ + "", + { value: "employee", label: __("Employee") }, + { value: "project", label: __("Project") }, + { value: "date", label: __("Start Date") }, + ], + }, + { + fieldname: "include_draft_timesheets", + label: __("Include Timesheets in Draft Status"), + fieldtype: "Check", + }, + ], +}; + +function unset_group_by(report, fieldname) { + if (report.get_filter_value(fieldname) && report.get_filter_value("group_by") == fieldname) { + report.set_filter_value("group_by", ""); + } +} diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.json b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json similarity index 61% rename from erpnext/projects/report/project_billing_summary/project_billing_summary.json rename to erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json index 817d0cdb66..0f070cb457 100644 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.json +++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json @@ -1,36 +1,42 @@ { "add_total_row": 1, - "creation": "2019-03-11 16:22:39.460524", - "disable_prepared_report": 0, + "columns": [], + "creation": "2023-10-10 23:53:43.692067", "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2019-06-13 15:54:55.255947", + "letter_head": "ALYF GmbH", + "letterhead": null, + "modified": "2023-10-11 00:58:30.639078", "modified_by": "Administrator", "module": "Projects", - "name": "Project Billing Summary", + "name": "Timesheet Billing Summary", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "Timesheet", - "report_name": "Project Billing Summary", + "report_name": "Timesheet Billing Summary", "report_type": "Script Report", "roles": [ { "role": "Projects User" }, { - "role": "HR User" + "role": "Employee" + }, + { + "role": "Accounts User" }, { "role": "Manufacturing User" }, { - "role": "Employee" + "role": "HR User" }, { - "role": "Accounts User" + "role": "Employee Self Service" } ] } \ No newline at end of file diff --git a/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py new file mode 100644 index 0000000000..a6e7150e41 --- /dev/null +++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py @@ -0,0 +1,146 @@ +import frappe +from frappe import _ +from frappe.model.docstatus import DocStatus + + +def execute(filters=None): + group_fieldname = filters.pop("group_by", None) + + filters = frappe._dict(filters or {}) + columns = get_columns(filters, group_fieldname) + + data = get_data(filters, group_fieldname) + return columns, data + + +def get_columns(filters, group_fieldname=None): + group_columns = { + "date": { + "label": _("Date"), + "fieldtype": "Date", + "fieldname": "date", + "width": 150, + }, + "project": { + "label": _("Project"), + "fieldtype": "Link", + "fieldname": "project", + "options": "Project", + "width": 200, + "hidden": int(bool(filters.get("project"))), + }, + "employee": { + "label": _("Employee ID"), + "fieldtype": "Link", + "fieldname": "employee", + "options": "Employee", + "width": 200, + "hidden": int(bool(filters.get("employee"))), + }, + } + columns = [] + if group_fieldname: + columns.append(group_columns.get(group_fieldname)) + columns.extend( + column for column in group_columns.values() if column.get("fieldname") != group_fieldname + ) + else: + columns.extend(group_columns.values()) + + columns.extend( + [ + { + "label": _("Employee Name"), + "fieldtype": "data", + "fieldname": "employee_name", + "hidden": 1, + }, + { + "label": _("Timesheet"), + "fieldtype": "Link", + "fieldname": "timesheet", + "options": "Timesheet", + "width": 150, + }, + {"label": _("Working Hours"), "fieldtype": "Float", "fieldname": "hours", "width": 150}, + { + "label": _("Billing Hours"), + "fieldtype": "Float", + "fieldname": "billing_hours", + "width": 150, + }, + { + "label": _("Billing Amount"), + "fieldtype": "Currency", + "fieldname": "billing_amount", + "width": 150, + }, + ] + ) + + return columns + + +def get_data(filters, group_fieldname=None): + _filters = [] + if filters.get("employee"): + _filters.append(("employee", "=", filters.get("employee"))) + if filters.get("project"): + _filters.append(("Timesheet Detail", "project", "=", filters.get("project"))) + if filters.get("from_date"): + _filters.append(("Timesheet Detail", "from_time", ">=", filters.get("from_date"))) + if filters.get("to_date"): + _filters.append(("Timesheet Detail", "to_time", "<=", filters.get("to_date"))) + if not filters.get("include_draft_timesheets"): + _filters.append(("docstatus", "=", DocStatus.submitted())) + else: + _filters.append(("docstatus", "in", (DocStatus.submitted(), DocStatus.draft()))) + + data = frappe.get_list( + "Timesheet", + fields=[ + "name as timesheet", + "`tabTimesheet`.employee", + "`tabTimesheet`.employee_name", + "`tabTimesheet Detail`.from_time as date", + "`tabTimesheet Detail`.project", + "`tabTimesheet Detail`.hours", + "`tabTimesheet Detail`.billing_hours", + "`tabTimesheet Detail`.billing_amount", + ], + filters=_filters, + order_by="`tabTimesheet Detail`.from_time", + ) + + return group_by(data, group_fieldname) if group_fieldname else data + + +def group_by(data, fieldname): + groups = {row.get(fieldname) for row in data} + grouped_data = [] + for group in sorted(groups): + group_row = { + fieldname: group, + "hours": sum(row.get("hours") for row in data if row.get(fieldname) == group), + "billing_hours": sum(row.get("billing_hours") for row in data if row.get(fieldname) == group), + "billing_amount": sum(row.get("billing_amount") for row in data if row.get(fieldname) == group), + "indent": 0, + "is_group": 1, + } + if fieldname == "employee": + group_row["employee_name"] = next( + row.get("employee_name") for row in data if row.get(fieldname) == group + ) + + grouped_data.append(group_row) + for row in data: + if row.get(fieldname) != group: + continue + + _row = row.copy() + _row[fieldname] = None + _row["indent"] = 1 + _row["is_group"] = 0 + grouped_data.append(_row) + + return grouped_data diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 94ae9c04a4..e6bead9ff4 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -155,9 +155,9 @@ "dependencies": "Project", "hidden": 0, "is_query_report": 1, - "label": "Project Billing Summary", + "label": "Timesheet Billing Summary", "link_count": 0, - "link_to": "Project Billing Summary", + "link_to": "Timesheet Billing Summary", "link_type": "Report", "onboard": 0, "type": "Link" @@ -192,7 +192,7 @@ "type": "Link" } ], - "modified": "2023-07-04 14:39:08.935853", + "modified": "2023-10-10 23:54:33.082108", "modified_by": "Administrator", "module": "Projects", "name": "Projects", @@ -234,8 +234,8 @@ "type": "DocType" }, { - "label": "Project Billing Summary", - "link_to": "Project Billing Summary", + "label": "Timesheet Billing Summary", + "link_to": "Timesheet Billing Summary", "type": "Report" }, { From 8fa677b8e8c6e4e65193ad0be7c0a6d5b4f1769e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 1 Nov 2023 20:30:16 +0530 Subject: [PATCH 249/280] refactor: checkbox to toggle remarks in General Ledger --- erpnext/accounts/report/general_ledger/general_ledger.js | 6 ++++++ erpnext/accounts/report/general_ledger/general_ledger.py | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 37d0659acf..c0b4f59579 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -193,7 +193,13 @@ frappe.query_reports["General Ledger"] = { "fieldname": "add_values_in_transaction_currency", "label": __("Add Columns in Transaction Currency"), "fieldtype": "Check" + }, + { + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check" } + ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 79bfd7833a..5e484cf558 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -163,6 +163,9 @@ def get_gl_entries(filters, accounting_dimensions): select_fields = """, debit, credit, debit_in_account_currency, credit_in_account_currency """ + if filters.get("show_remarks"): + select_fields += """,remarks""" + order_by_statement = "order by posting_date, account, creation" if filters.get("include_dimensions"): @@ -195,7 +198,7 @@ def get_gl_entries(filters, accounting_dimensions): voucher_type, voucher_no, {dimension_fields} cost_center, project, {transaction_currency_fields} against_voucher_type, against_voucher, account_currency, - remarks, against, is_opening, creation {select_fields} + against, is_opening, creation {select_fields} from `tabGL Entry` where company=%(company)s {conditions} {order_by_statement} @@ -631,8 +634,10 @@ def get_columns(filters): "width": 100, }, {"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100}, - {"label": _("Remarks"), "fieldname": "remarks", "width": 400}, ] ) + if filters.get("show_remarks"): + columns.extend([{"label": _("Remarks"), "fieldname": "remarks", "width": 400}]) + return columns From 38e5e4a8930714c08471308ba47b9f063802a4b3 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 1 Nov 2023 18:05:50 +0100 Subject: [PATCH 250/280] feat(Stock Balance): add filters from route --- erpnext/stock/page/stock_balance/stock_balance.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js index f00dd3e791..90b8d45342 100644 --- a/erpnext/stock/page/stock_balance/stock_balance.js +++ b/erpnext/stock/page/stock_balance/stock_balance.js @@ -11,6 +11,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { label: __('Warehouse'), fieldtype:'Link', options:'Warehouse', + default: frappe.route_options && frappe.route_options.warehouse, change: function() { page.item_dashboard.start = 0; page.item_dashboard.refresh(); @@ -22,6 +23,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { label: __('Item'), fieldtype:'Link', options:'Item', + default: frappe.route_options && frappe.route_options.item_code, change: function() { page.item_dashboard.start = 0; page.item_dashboard.refresh(); @@ -33,6 +35,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { label: __('Item Group'), fieldtype:'Link', options:'Item Group', + default: frappe.route_options && frappe.route_options.item_group, change: function() { page.item_dashboard.start = 0; page.item_dashboard.refresh(); From 54e8ce1ac5a0d14905050fbb630a1602a560088a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 2 Nov 2023 09:16:26 +0530 Subject: [PATCH 251/280] refactor: pass limits to JE and PE queries in reconciliation tool --- .../payment_reconciliation/payment_reconciliation.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 1626f25f3e..43167be15a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -109,6 +109,8 @@ class PaymentReconciliation(Document): "t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1" ) + limit = f"limit {self.payment_limit}" if self.payment_limit else " " + # nosemgrep journal_entries = frappe.db.sql( """ @@ -132,11 +134,13 @@ class PaymentReconciliation(Document): ELSE {bank_account_condition} END) order by t1.posting_date + {limit} """.format( **{ "dr_or_cr": dr_or_cr, "bank_account_condition": bank_account_condition, "condition": condition, + "limit": limit, } ), { @@ -162,7 +166,7 @@ class PaymentReconciliation(Document): if self.payment_name: conditions.append(doc.name.like(f"%{self.payment_name}%")) - self.return_invoices = ( + self.return_invoices_query = ( qb.from_(doc) .select( ConstantColumn(voucher_type).as_("voucher_type"), @@ -170,8 +174,11 @@ class PaymentReconciliation(Document): doc.return_against, ) .where(Criterion.all(conditions)) - .run(as_dict=True) ) + if self.payment_limit: + self.return_invoices_query = self.return_invoices_query.limit(self.payment_limit) + + self.return_invoices = self.return_invoices_query.run(as_dict=True) def get_dr_or_cr_notes(self): From a9fceeb00ff266b181791dc0ca4ca334810fff0a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 2 Nov 2023 11:32:24 +0530 Subject: [PATCH 252/280] chore: std permissions for Process Payment Reconciilation log --- .../process_payment_reconciliation_log.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json index 1131a0fca6..b4ac9812cb 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json @@ -110,7 +110,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-04-21 17:36:26.642617", + "modified": "2023-11-02 11:32:12.254018", "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log", @@ -125,7 +125,19 @@ "print": 1, "read": 1, "report": 1, - "role": "System Manager", + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", "share": 1, "write": 1 } From 0104897d693ca98796536eac7a9edadc58c64fff Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 2 Nov 2023 13:14:56 +0530 Subject: [PATCH 253/280] fix: remove voucher type and no for Item and Warehouse based reposting --- erpnext/controllers/stock_controller.py | 2 -- .../stock_reposting_settings/stock_reposting_settings.json | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a7330ec63c..fc45c7ad52 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1210,8 +1210,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entry = frappe.new_doc("Repost Item Valuation") repost_entry.based_on = "Item and Warehouse" - repost_entry.voucher_type = voucher_type - repost_entry.voucher_no = voucher_no repost_entry.item_code = sle.item_code repost_entry.warehouse = sle.warehouse diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json index 7c712ce225..68afd996b4 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -50,7 +50,7 @@ "label": "Limit timeslot for Stock Reposting" }, { - "default": "0", + "default": "1", "fieldname": "item_based_reposting", "fieldtype": "Check", "label": "Use Item based reposting" @@ -70,7 +70,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-05-04 16:14:29.080697", + "modified": "2023-11-01 16:14:29.080697", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reposting Settings", From 539f0251d95d80055112d72e54af9571bea987dc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 2 Nov 2023 13:17:21 +0530 Subject: [PATCH 254/280] refactor: better output on gl and pl comparison report --- .../general_and_payment_ledger_comparison.py | 82 ++++++++++++++++--- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py index 099884a48e..696a03b0a7 100644 --- a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py +++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py @@ -79,7 +79,9 @@ class General_Payment_Ledger_Comparison(object): .select( gle.company, gle.account, + gle.voucher_type, gle.voucher_no, + gle.party_type, gle.party, outstanding, ) @@ -89,7 +91,9 @@ class General_Payment_Ledger_Comparison(object): & (gle.account.isin(val.accounts)) ) .where(Criterion.all(filter_criterion)) - .groupby(gle.company, gle.account, gle.voucher_no, gle.party) + .groupby( + gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party + ) .run() ) @@ -112,7 +116,13 @@ class General_Payment_Ledger_Comparison(object): self.account_types[acc_type].ple = ( qb.from_(ple) .select( - ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding") + ple.company, + ple.account, + ple.voucher_type, + ple.voucher_no, + ple.party_type, + ple.party, + Sum(ple.amount).as_("outstanding"), ) .where( (ple.company == self.filters.company) @@ -120,7 +130,9 @@ class General_Payment_Ledger_Comparison(object): & (ple.account.isin(val.accounts)) ) .where(Criterion.all(filter_criterion)) - .groupby(ple.company, ple.account, ple.voucher_no, ple.party) + .groupby( + ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party + ) .run() ) @@ -138,12 +150,12 @@ class General_Payment_Ledger_Comparison(object): self.diff = frappe._dict({}) for x in self.variation_in_payment_ledger: - self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]}) + self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]}) for x in self.variation_in_general_ledger: - self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update( - frappe._dict({"pl_balance": x[4]}) - ) + self.diff.setdefault( + (x[0], x[1], x[2], x[3], x[4], x[5]), frappe._dict({"gl_balance": 0.0}) + ).update(frappe._dict({"pl_balance": x[6]})) def generate_data(self): self.data = [] @@ -151,8 +163,12 @@ class General_Payment_Ledger_Comparison(object): self.data.append( frappe._dict( { - "voucher_no": key[2], - "party": key[3], + "company": key[0], + "account": key[1], + "voucher_type": key[2], + "voucher_no": key[3], + "party_type": key[4], + "party": key[5], "gl_balance": val.gl_balance, "pl_balance": val.pl_balance, } @@ -162,12 +178,52 @@ class General_Payment_Ledger_Comparison(object): def get_columns(self): self.columns = [] options = None + self.columns.append( + dict( + label=_("Company"), + fieldname="company", + fieldtype="Link", + options="Company", + width="100", + ) + ) + + self.columns.append( + dict( + label=_("Account"), + fieldname="account", + fieldtype="Link", + options="Account", + width="100", + ) + ) + + self.columns.append( + dict( + label=_("Voucher Type"), + fieldname="voucher_type", + fieldtype="Link", + options="DocType", + width="100", + ) + ) + self.columns.append( dict( label=_("Voucher No"), fieldname="voucher_no", - fieldtype="Data", - options=options, + fieldtype="Dynamic Link", + options="voucher_type", + width="100", + ) + ) + + self.columns.append( + dict( + label=_("Party Type"), + fieldname="party_type", + fieldtype="Link", + options="DocType", width="100", ) ) @@ -176,8 +232,8 @@ class General_Payment_Ledger_Comparison(object): dict( label=_("Party"), fieldname="party", - fieldtype="Data", - options=options, + fieldtype="Dynamic Link", + options="party_type", width="100", ) ) From 639f427d6d31c7d018033c2110c478f8e6ea8ce4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 2 Nov 2023 13:40:40 +0530 Subject: [PATCH 255/280] refactor(test): for ledger comparision report --- .../test_general_and_payment_ledger_comparison.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py index 4b0e99d712..59e906ba33 100644 --- a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py +++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py @@ -50,7 +50,11 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin): self.assertEqual(len(data), 1) expected = { + "company": sinv.company, + "account": sinv.debit_to, + "voucher_type": sinv.doctype, "voucher_no": sinv.name, + "party_type": "Customer", "party": sinv.customer, "gl_balance": sinv.grand_total, "pl_balance": sinv.grand_total - 1, From 1b808e1d7c2c2d1ad51bd18acf6a45e9b5cfc605 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:01:26 +0530 Subject: [PATCH 256/280] fix: standard submit perm in repost ledger for editable invoices (#37826) * fix: ignore perm while reposting ledger * fix: use flag in save * fix: remove unnecessary save --- erpnext/controllers/accounts_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6efe631a29..38c1e820d2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2269,6 +2269,7 @@ class AccountsController(TransactionBase): repost_ledger = frappe.new_doc("Repost Accounting Ledger") repost_ledger.company = self.company repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name}) + repost_ledger.flags.ignore_permissions = True repost_ledger.insert() repost_ledger.submit() self.db_set("repost_required", 0) From ed1c198897c31795c720f0f738c2ec40f4a70bc8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 2 Nov 2023 14:21:16 +0530 Subject: [PATCH 257/280] chore: fix test cases --- .../repost_item_valuation/test_repost_item_valuation.py | 4 +++- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 1 + .../doctype/stock_reconciliation/test_stock_reconciliation.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) 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 623e8fafe9..5b76e442f4 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 @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, call import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, add_to_date, now, nowdate, today from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice @@ -196,6 +196,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): riv.set_status("Skipped") + @change_settings("Stock Reposting Settings", {"item_based_reposting": 0}) def test_prevention_of_cancelled_transaction_riv(self): frappe.flags.dont_execute_stock_reposts = True @@ -373,6 +374,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): accounts_settings.acc_frozen_upto = "" accounts_settings.save() + @change_settings("Stock Reposting Settings", {"item_based_reposting": 0}) def test_create_repost_entry_for_cancelled_document(self): pr = make_purchase_receipt( company="_Test Company with perpetual inventory", diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 3e0610ef6e..b640983a09 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1467,6 +1467,7 @@ class TestStockEntry(FrappeTestCase): self.assertEqual(se.items[0].item_name, item.item_name) self.assertEqual(se.items[0].stock_uom, item.stock_uom) + @change_settings("Stock Reposting Settings", {"item_based_reposting": 0}) def test_reposting_for_depedent_warehouse(self): from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 4817c8d8dc..34a7cbaa72 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -674,6 +674,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(flt(sl_entry.actual_qty), 1.0) self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0) + @change_settings("Stock Reposting Settings", {"item_based_reposting": 0}) def test_backdated_stock_reco_entry(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From c37e374fdd322c0f0423469ce95faecac15b18e4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 2 Nov 2023 21:00:45 +0530 Subject: [PATCH 258/280] fix: Add index to supplier invoice field (#37861) * fix: Add index to supplier invoice field * chore: remove unintetional changes --- .../doctype/purchase_invoice/purchase_invoice.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 2d1f4451b6..00176c01fc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -384,7 +384,8 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1 + "print_hide": 1, + "search_index": 1 }, { "fieldname": "column_break_15", @@ -1602,7 +1603,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-10-16 16:24:51.886231", + "modified": "2023-11-02 18:46:16.810187", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -1665,4 +1666,4 @@ "timeline_field": "supplier", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From e019d43d0b3fa6faa241a7b9885f65f6fccc9459 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Nov 2023 00:53:30 +0530 Subject: [PATCH 259/280] fix: permission error while creating Supplier Quotation from Portal --- erpnext/controllers/buying_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a76abe2154..3a802bd26f 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -4,7 +4,7 @@ import frappe from frappe import ValidationError, _, msgprint -from frappe.contacts.doctype.address.address import get_address_display +from frappe.contacts.doctype.address.address import render_address from frappe.utils import cint, flt, getdate from frappe.utils.data import nowtime @@ -246,7 +246,9 @@ class BuyingController(SubcontractingController): for address_field, address_display_field in address_dict.items(): if self.get(address_field): - self.set(address_display_field, get_address_display(self.get(address_field))) + self.set( + address_display_field, render_address(self.get(address_field), check_permissions=False) + ) def set_total_in_words(self): from frappe.utils import money_in_words From 23beb46d15a2b96601e1f20a68dee06d7e5f0c49 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Nov 2023 11:03:54 +0530 Subject: [PATCH 260/280] refactor: group only by voucher flag in AR/AP report --- .../accounts_payable/accounts_payable.js | 8 ++++++- .../accounts_receivable.js | 8 ++++++- .../accounts_receivable.py | 22 +++++++++++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 9c73cbb344..eff705dafa 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -143,7 +143,13 @@ frappe.query_reports["Accounts Payable"] = { "fieldname": "show_future_payments", "label": __("Show Future Payments"), "fieldtype": "Check", + }, + { + "fieldname": "ignore_accounts", + "label": __("Group by Voucher"), + "fieldtype": "Check", } + ], "formatter": function(value, row, column, data, default_formatter) { @@ -175,4 +181,4 @@ function get_party_type_options() { }); }); return options; -} \ No newline at end of file +} diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 1073be0bdc..786aad601b 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -172,7 +172,13 @@ frappe.query_reports["Accounts Receivable"] = { "fieldname": "show_remarks", "label": __("Show Remarks"), "fieldtype": "Check", + }, + { + "fieldname": "ignore_accounts", + "label": __("Group by Voucher"), + "fieldtype": "Check", } + ], "formatter": function(value, row, column, data, default_formatter) { @@ -205,4 +211,4 @@ function get_party_type_options() { }); }); return options; -} \ No newline at end of file +} diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index b9c7a0bfb8..69f703d907 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -116,7 +116,12 @@ class ReceivablePayableReport(object): # build all keys, since we want to exclude vouchers beyond the report date for ple in self.ple_entries: # get the balance object for voucher_type - key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) + + if self.filters.get("ingore_accounts"): + key = (ple.voucher_type, ple.voucher_no, ple.party) + else: + key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) + if not key in self.voucher_balance: self.voucher_balance[key] = frappe._dict( voucher_type=ple.voucher_type, @@ -183,7 +188,10 @@ class ReceivablePayableReport(object): ): return - key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party) + if self.filters.get("ingore_accounts"): + key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) + else: + key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party) # If payment is made against credit note # and credit note is made against a Sales Invoice @@ -192,13 +200,19 @@ class ReceivablePayableReport(object): if ple.against_voucher_no in self.return_entries: return_against = self.return_entries.get(ple.against_voucher_no) if return_against: - key = (ple.account, ple.against_voucher_type, return_against, ple.party) + if self.filters.get("ingore_accounts"): + key = (ple.against_voucher_type, return_against, ple.party) + else: + key = (ple.account, ple.against_voucher_type, return_against, ple.party) row = self.voucher_balance.get(key) if not row: # no invoice, this is an invoice / stand-alone payment / credit note - row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party)) + if self.filters.get("ingore_accounts"): + row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party)) + else: + row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party)) row.party_type = ple.party_type return row From 469ae2c7f1edb6dbd2d770b65c48bad5a0293ffe Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 3 Nov 2023 15:57:52 +0530 Subject: [PATCH 261/280] perf: index return against for purchase invoice (#37881) --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 00176c01fc..09bffff6da 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -408,7 +408,8 @@ "no_copy": 1, "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "section_addresses", @@ -1603,7 +1604,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-11-02 18:46:16.810187", + "modified": "2023-11-03 15:47:30.319200", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From d4c0dbfacc7e99da6cba2c5d389f0a662490b0eb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 3 Nov 2023 17:19:06 +0530 Subject: [PATCH 262/280] fix: incorrect available qty for backdated stock reco with batch (#37858) * fix: incorrect available qty for backdated stock reco with batch * test: added test case --- .../serial_and_batch_bundle.py | 13 +- .../stock_reconciliation.py | 135 +++++++++++++----- .../test_stock_reconciliation.py | 69 ++++++++- .../stock_reconciliation_item.json | 3 +- .../stock/report/stock_ledger/stock_ledger.py | 8 ++ .../stock_ledger_invariant_check.py | 13 +- erpnext/stock/stock_ledger.py | 68 +++++---- 7 files changed, 229 insertions(+), 80 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index f96c184c88..f2bbf2b211 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -121,7 +121,7 @@ class SerialandBatchBundle(Document): def throw_error_message(self, message, exception=frappe.ValidationError): frappe.throw(_(message), exception, title=_("Error")) - def set_incoming_rate(self, row=None, save=False): + def set_incoming_rate(self, row=None, save=False, allow_negative_stock=False): if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [ "Installation Note", "Job Card", @@ -131,7 +131,9 @@ class SerialandBatchBundle(Document): return if self.type_of_transaction == "Outward": - self.set_incoming_rate_for_outward_transaction(row, save) + self.set_incoming_rate_for_outward_transaction( + row, save, allow_negative_stock=allow_negative_stock + ) else: self.set_incoming_rate_for_inward_transaction(row, save) @@ -152,7 +154,9 @@ class SerialandBatchBundle(Document): def get_serial_nos(self): return [d.serial_no for d in self.entries if d.serial_no] - def set_incoming_rate_for_outward_transaction(self, row=None, save=False): + def set_incoming_rate_for_outward_transaction( + self, row=None, save=False, allow_negative_stock=False + ): sle = self.get_sle_for_outward_transaction() if self.has_serial_no: @@ -181,7 +185,8 @@ class SerialandBatchBundle(Document): if self.docstatus == 1: available_qty += flt(d.qty) - self.validate_negative_batch(d.batch_no, available_qty) + if not allow_negative_stock: + self.validate_negative_batch(d.batch_no, available_qty) d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 98b4ffdfcf..323ad4f57d 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -6,7 +6,7 @@ from typing import Optional import frappe from frappe import _, bold, msgprint from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import cint, cstr, flt +from frappe.utils import add_to_date, cint, cstr, flt import erpnext from erpnext.accounts.utils import get_company_default @@ -88,9 +88,12 @@ class StockReconciliation(StockController): self.repost_future_sle_and_gle() self.delete_auto_created_batches() - def set_current_serial_and_batch_bundle(self): + def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None: """Set Serial and Batch Bundle for each item""" for item in self.items: + if voucher_detail_no and voucher_detail_no != item.name: + continue + item_details = frappe.get_cached_value( "Item", item.item_code, ["has_serial_no", "has_batch_no"], as_dict=1 ) @@ -148,6 +151,7 @@ class StockReconciliation(StockController): "warehouse": item.warehouse, "posting_date": self.posting_date, "posting_time": self.posting_time, + "ignore_voucher_nos": [self.name], } ) ) @@ -163,11 +167,36 @@ class StockReconciliation(StockController): ) if not serial_and_batch_bundle.entries: + if voucher_detail_no: + return + continue - item.current_serial_and_batch_bundle = serial_and_batch_bundle.save().name + serial_and_batch_bundle.save() + item.current_serial_and_batch_bundle = serial_and_batch_bundle.name item.current_qty = abs(serial_and_batch_bundle.total_qty) item.current_valuation_rate = abs(serial_and_batch_bundle.avg_rate) + if save: + sle_creation = frappe.db.get_value( + "Serial and Batch Bundle", item.serial_and_batch_bundle, "creation" + ) + creation = add_to_date(sle_creation, seconds=-1) + item.db_set( + { + "current_serial_and_batch_bundle": item.current_serial_and_batch_bundle, + "current_qty": item.current_qty, + "current_valuation_rate": item.current_valuation_rate, + "creation": creation, + } + ) + + serial_and_batch_bundle.db_set( + { + "creation": creation, + "voucher_no": self.name, + "voucher_detail_no": voucher_detail_no, + } + ) def set_new_serial_and_batch_bundle(self): for item in self.items: @@ -689,56 +718,84 @@ class StockReconciliation(StockController): else: self._cancel() - def recalculate_current_qty(self, item_code, batch_no): + def recalculate_current_qty(self, voucher_detail_no, sle_creation, add_new_sle=False): from erpnext.stock.stock_ledger import get_valuation_rate sl_entries = [] + for row in self.items: - if ( - not (row.item_code == item_code and row.batch_no == batch_no) - and not row.serial_and_batch_bundle - ): + if voucher_detail_no != row.name: continue + current_qty = 0.0 if row.current_serial_and_batch_bundle: - self.recalculate_qty_for_serial_and_batch_bundle(row) - continue - - current_qty = get_batch_qty_for_stock_reco( - item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name - ) + current_qty = self.get_qty_for_serial_and_batch_bundle(row) + elif row.batch_no: + current_qty = get_batch_qty_for_stock_reco( + row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name + ) precesion = row.precision("current_qty") - if flt(current_qty, precesion) == flt(row.current_qty, precesion): - continue + if flt(current_qty, precesion) != flt(row.current_qty, precesion): + val_rate = get_valuation_rate( + row.item_code, + row.warehouse, + self.doctype, + self.name, + company=self.company, + batch_no=row.batch_no, + serial_and_batch_bundle=row.current_serial_and_batch_bundle, + ) - val_rate = get_valuation_rate( - item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no - ) + row.current_valuation_rate = val_rate + row.current_qty = current_qty + row.db_set( + { + "current_qty": row.current_qty, + "current_valuation_rate": row.current_valuation_rate, + "current_amount": flt(row.current_qty * row.current_valuation_rate), + } + ) - row.current_valuation_rate = val_rate - if not row.current_qty and current_qty: - sle = self.get_sle_for_items(row) - sle.actual_qty = current_qty * -1 - sle.valuation_rate = val_rate - sl_entries.append(sle) + if ( + add_new_sle + and not frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0}, + "name", + ) + and (not row.current_serial_and_batch_bundle and not row.batch_no) + ): + self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True) + row.reload() - row.current_qty = current_qty - row.db_set( - { - "current_qty": row.current_qty, - "current_valuation_rate": row.current_valuation_rate, - "current_amount": flt(row.current_qty * row.current_valuation_rate), - } - ) + if row.current_qty > 0 and row.current_serial_and_batch_bundle: + new_sle = self.get_sle_for_items(row) + new_sle.actual_qty = row.current_qty * -1 + new_sle.valuation_rate = row.current_valuation_rate + new_sle.creation_time = add_to_date(sle_creation, seconds=-1) + new_sle.serial_and_batch_bundle = row.current_serial_and_batch_bundle + new_sle.qty_after_transaction = 0.0 + sl_entries.append(new_sle) if sl_entries: - self.make_sl_entries(sl_entries, allow_negative_stock=True) + self.make_sl_entries(sl_entries, allow_negative_stock=self.has_negative_stock_allowed()) + if not frappe.db.exists("Repost Item Valuation", {"voucher_no": self.name, "status": "Queued"}): + self.repost_future_sle_and_gle(force=True) - def recalculate_qty_for_serial_and_batch_bundle(self, row): + def has_negative_stock_allowed(self): + allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + + if all(d.serial_and_batch_bundle and flt(d.qty) == flt(d.current_qty) for d in self.items): + allow_negative_stock = True + + return allow_negative_stock + + def get_qty_for_serial_and_batch_bundle(self, row): doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle) precision = doc.entries[0].precision("qty") + current_qty = 0 for d in doc.entries: qty = ( get_batch_qty( @@ -751,10 +808,12 @@ class StockReconciliation(StockController): or 0 ) * -1 - if flt(d.qty, precision) == flt(qty, precision): - continue + if flt(d.qty, precision) != flt(qty, precision): + d.db_set("qty", qty) - d.db_set("qty", qty) + current_qty += qty + + return abs(current_qty) def get_batch_qty_for_stock_reco( diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 34a7cbaa72..1ec99bf9a5 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -742,13 +742,6 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): se2.cancel() - self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name})) - - self.assertEqual( - frappe.db.get_value("Repost Item Valuation", {"voucher_no": stock_reco.name}, "status"), - "Completed", - ) - sle = frappe.get_all( "Stock Ledger Entry", filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0}, @@ -766,6 +759,68 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(flt(sle[0].actual_qty), flt(-100.0)) + def test_backdated_stock_reco_entry_with_batch(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item_code = self.make_item( + "Test New Batch Item ABCVSD", + { + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "BNS9.####", + "create_new_batch": 1, + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + # Stock Reco for 100, Balace Qty 100 + stock_reco = create_stock_reconciliation( + item_code=item_code, + posting_date=nowdate(), + posting_time="11:00:00", + warehouse=warehouse, + qty=100, + rate=100, + ) + + sles = frappe.get_all( + "Stock Ledger Entry", + fields=["actual_qty"], + filters={"voucher_no": stock_reco.name, "is_cancelled": 0}, + ) + + self.assertEqual(len(sles), 1) + + stock_reco.reload() + batch_no = get_batch_from_bundle(stock_reco.items[0].serial_and_batch_bundle) + + # Stock Reco for 100, Balace Qty 100 + stock_reco1 = create_stock_reconciliation( + item_code=item_code, + posting_date=add_days(nowdate(), -1), + posting_time="11:00:00", + batch_no=batch_no, + warehouse=warehouse, + qty=60, + rate=100, + ) + + sles = frappe.get_all( + "Stock Ledger Entry", + fields=["actual_qty"], + filters={"voucher_no": stock_reco.name, "is_cancelled": 0}, + ) + + stock_reco1.reload() + new_batch_no = get_batch_from_bundle(stock_reco1.items[0].serial_and_batch_bundle) + + self.assertEqual(len(sles), 2) + + for row in sles: + if row.actual_qty < 0: + self.assertEqual(row.actual_qty, -60) + def test_update_stock_reconciliation_while_reposting(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index ca19bbb96a..d9cbf95710 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -205,6 +205,7 @@ "fieldname": "current_serial_and_batch_bundle", "fieldtype": "Link", "label": "Current Serial / Batch Bundle", + "no_copy": 1, "options": "Serial and Batch Bundle", "read_only": 1 }, @@ -216,7 +217,7 @@ ], "istable": 1, "links": [], - "modified": "2023-07-26 12:54:34.011915", + "modified": "2023-11-02 15:47:07.929550", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index eeef39641b..e59f2fe644 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -249,6 +249,13 @@ def get_columns(filters): "options": "Serial No", "width": 100, }, + { + "label": _("Serial and Batch Bundle"), + "fieldname": "serial_and_batch_bundle", + "fieldtype": "Link", + "options": "Serial and Batch Bundle", + "width": 100, + }, {"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100}, { "label": _("Project"), @@ -287,6 +294,7 @@ def get_stock_ledger_entries(filters, items): sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, + sle.serial_and_batch_bundle, sle.voucher_no, sle.stock_value, sle.batch_no, diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py index ca15afe444..fb392f7e36 100644 --- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py +++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py @@ -24,6 +24,7 @@ SLE_FIELDS = ( "stock_value_difference", "valuation_rate", "voucher_detail_no", + "serial_and_batch_bundle", ) @@ -64,7 +65,11 @@ def add_invariant_check_fields(sles): balance_qty += sle.actual_qty balance_stock_value += sle.stock_value_difference - if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no: + if ( + sle.voucher_type == "Stock Reconciliation" + and not sle.batch_no + and not sle.serial_and_batch_bundle + ): balance_qty = frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "qty") if balance_qty is None: balance_qty = sle.qty_after_transaction @@ -143,6 +148,12 @@ def get_columns(): "label": _("Batch"), "options": "Batch", }, + { + "fieldname": "serial_and_batch_bundle", + "fieldtype": "Link", + "label": _("Serial and Batch Bundle"), + "options": "Serial and Batch Bundle", + }, { "fieldname": "use_batchwise_valuation", "fieldtype": "Check", diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b950f18810..551701b47a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -203,6 +203,11 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.allow_negative_stock = allow_negative_stock sle.via_landed_cost_voucher = via_landed_cost_voucher sle.submit() + + # Added to handle the case when the stock ledger entry is created from the repostig + if args.get("creation_time") and args.get("voucher_type") == "Stock Reconciliation": + sle.db_set("creation", args.get("creation_time")) + return sle @@ -689,9 +694,11 @@ class update_entries_after(object): if ( sle.voucher_type == "Stock Reconciliation" - and (sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle)) + and ( + sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle and not sle.has_serial_no) + ) and sle.voucher_detail_no - and sle.actual_qty < 0 + and not self.args.get("sle_id") ): self.reset_actual_qty_for_stock_reco(sle) @@ -754,27 +761,22 @@ class update_entries_after(object): self.update_outgoing_rate_on_transaction(sle) def reset_actual_qty_for_stock_reco(self, sle): - if sle.serial_and_batch_bundle: - current_qty = frappe.get_cached_value( - "Serial and Batch Bundle", sle.serial_and_batch_bundle, "total_qty" + doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no) + doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0) + + if sle.actual_qty < 0: + sle.actual_qty = ( + flt(frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "current_qty")) + * -1 ) - if current_qty is not None: - current_qty = abs(current_qty) - else: - current_qty = frappe.get_cached_value( - "Stock Reconciliation Item", sle.voucher_detail_no, "current_qty" - ) - - if current_qty: - sle.actual_qty = current_qty * -1 - elif current_qty == 0: - sle.is_cancelled = 1 + if abs(sle.actual_qty) == 0.0: + sle.is_cancelled = 1 def calculate_valuation_for_serial_batch_bundle(self, sle): doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle) - doc.set_incoming_rate(save=True) + doc.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock) doc.calculate_qty_and_amount(save=True) self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount) @@ -1461,6 +1463,7 @@ def get_valuation_rate( currency=None, company=None, raise_error_if_no_rate=True, + batch_no=None, serial_and_batch_bundle=None, ): @@ -1469,6 +1472,25 @@ def get_valuation_rate( if not company: company = frappe.get_cached_value("Warehouse", warehouse, "company") + if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"): + table = frappe.qb.DocType("Stock Ledger Entry") + query = ( + frappe.qb.from_(table) + .select(Sum(table.stock_value_difference) / Sum(table.actual_qty)) + .where( + (table.item_code == item_code) + & (table.warehouse == warehouse) + & (table.batch_no == batch_no) + & (table.is_cancelled == 0) + & (table.voucher_no != voucher_no) + & (table.voucher_type != voucher_type) + ) + ) + + last_valuation_rate = query.run() + if last_valuation_rate: + return flt(last_valuation_rate[0][0]) + # Get moving average rate of a specific batch number if warehouse and serial_and_batch_bundle: batch_obj = BatchNoValuation( @@ -1563,8 +1585,6 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): next_stock_reco_detail = get_next_stock_reco(args) if next_stock_reco_detail: detail = next_stock_reco_detail[0] - if detail.batch_no or (detail.serial_and_batch_bundle and detail.has_batch_no): - regenerate_sle_for_batch_stock_reco(detail) # add condition to update SLEs before this date & time datetime_limit_condition = get_datetime_limit_condition(detail) @@ -1593,16 +1613,6 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): validate_negative_qty_in_future_sle(args, allow_negative_stock) -def regenerate_sle_for_batch_stock_reco(detail): - doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) - doc.recalculate_current_qty(detail.item_code, detail.batch_no) - - if not frappe.db.exists( - "Repost Item Valuation", {"voucher_no": doc.name, "status": "Queued", "docstatus": "1"} - ): - doc.repost_future_sle_and_gle(force=True) - - def get_stock_reco_qty_shift(args): stock_reco_qty_shift = 0 if args.get("is_cancelled"): From f14d1eb8716e1a816af4ed69f9dd1f6a4d30f035 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Nov 2023 17:56:40 +0530 Subject: [PATCH 263/280] chore: performance optimization on payment ledger entry doctype --- .../payment_ledger_entry.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 4ae813571a..28c9529995 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -65,7 +65,8 @@ "fieldtype": "Link", "in_standard_filter": 1, "label": "Voucher Type", - "options": "DocType" + "options": "DocType", + "search_index": 1 }, { "fieldname": "voucher_no", @@ -73,14 +74,16 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Voucher No", - "options": "voucher_type" + "options": "voucher_type", + "search_index": 1 }, { "fieldname": "against_voucher_type", "fieldtype": "Link", "in_standard_filter": 1, "label": "Against Voucher Type", - "options": "DocType" + "options": "DocType", + "search_index": 1 }, { "fieldname": "against_voucher_no", @@ -88,7 +91,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Against Voucher No", - "options": "against_voucher_type" + "options": "against_voucher_type", + "search_index": 1 }, { "fieldname": "amount", @@ -148,13 +152,14 @@ { "fieldname": "voucher_detail_no", "fieldtype": "Data", - "label": "Voucher Detail No" + "label": "Voucher Detail No", + "search_index": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-10-30 16:15:00.470283", + "modified": "2023-11-03 16:39:58.904113", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Ledger Entry", From 60435daba37c97b88a635ed2449d842abc32582e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Nov 2023 17:58:19 +0530 Subject: [PATCH 264/280] refactor: avoid precision based validation error while reconciling --- erpnext/accounts/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1c7052f8ff..e0adac412b 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -10,7 +10,7 @@ import frappe.defaults from frappe import _, qb, throw from frappe.model.meta import get_field_precision from frappe.query_builder import AliasedQuery, Criterion, Table -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Round, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( cint, @@ -536,6 +536,8 @@ def check_if_advance_entry_modified(args): ) else: + precision = frappe.get_precision("Payment Entry", "unallocated_amount") + payment_entry = frappe.qb.DocType("Payment Entry") payment_ref = frappe.qb.DocType("Payment Entry Reference") @@ -557,7 +559,10 @@ def check_if_advance_entry_modified(args): .where(payment_ref.allocated_amount == args.get("unreconciled_amount")) ) else: - q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount")) + q = q.where( + Round(payment_entry.unallocated_amount, precision) + == Round(args.get("unreconciled_amount"), precision) + ) ret = q.run(as_dict=True) From 568d5bfbe82088bb1aea52b4541d16f23ccec931 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Fri, 3 Nov 2023 22:35:08 +0530 Subject: [PATCH 265/280] chore: rename daily_depreciation in asset to depreciation_amount_based_on_num_days_in_month [dev] (#37893) * chore: rename daily_depreciation to depreciation_based_on_num_days_in_month * chore: add patch * chore: remove unnecessary files * chore: add amount in field name * chore: add amount in label --- erpnext/assets/doctype/asset/asset.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 9 +++++--- .../asset_depreciation_schedule.json | 8 +++---- .../asset_depreciation_schedule.py | 8 ++++--- .../asset_finance_book.json | 18 ++++++++-------- erpnext/patches.txt | 1 + ...ation_amount_based_on_num_days_in_month.py | 21 +++++++++++++++++++ 7 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9d35634933..c003afe118 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -818,7 +818,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount): "depreciation_method": d.depreciation_method, "total_number_of_depreciations": d.total_number_of_depreciations, "frequency_of_depreciation": d.frequency_of_depreciation, - "daily_depreciation": d.daily_depreciation, + "depreciation_amount_based_on_num_days_in_month": d.depreciation_amount_based_on_num_days_in_month, "salvage_value_percentage": d.salvage_value_percentage, "expected_value_after_useful_life": flt(gross_purchase_amount) * flt(d.salvage_value_percentage / 100), diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index d69f5ef0b7..cdaccbbfbe 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -755,7 +755,9 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) - def test_schedule_for_straight_line_method_with_daily_depreciation(self): + def test_schedule_for_straight_line_method_with_depreciation_amount_based_on_num_days_in_month( + self, + ): asset = create_asset( calculate_depreciation=1, available_for_use_date="2023-01-01", @@ -764,7 +766,7 @@ class TestDepreciationMethods(AssetSetup): depreciation_start_date="2023-01-31", total_number_of_depreciations=12, frequency_of_depreciation=1, - daily_depreciation=1, + depreciation_amount_based_on_num_days_in_month=1, ) expected_schedules = [ @@ -1760,7 +1762,8 @@ def create_asset(**args): "total_number_of_depreciations": args.total_number_of_depreciations or 5, "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, "depreciation_start_date": args.depreciation_start_date, - "daily_depreciation": args.daily_depreciation or 0, + "depreciation_amount_based_on_num_days_in_month": args.depreciation_amount_based_on_num_days_in_month + or 0, }, ) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index 3772ef4d68..6f07d84bcb 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -19,7 +19,7 @@ "depreciation_method", "total_number_of_depreciations", "rate_of_depreciation", - "daily_depreciation", + "depreciation_amount_based_on_num_days_in_month", "column_break_8", "frequency_of_depreciation", "expected_value_after_useful_life", @@ -179,9 +179,9 @@ { "default": "0", "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", - "fieldname": "daily_depreciation", + "fieldname": "depreciation_amount_based_on_num_days_in_month", "fieldtype": "Check", - "label": "Daily Depreciation", + "label": "Depreciation amount based on number of days in the month", "print_hide": 1, "read_only": 1 } @@ -189,7 +189,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-10 22:22:09.722968", + "modified": "2023-11-03 21:32:15.021796", "modified_by": "Administrator", "module": "Assets", "name": "Asset Depreciation Schedule", diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 7a88ffc5b7..109f96f9f4 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -153,7 +153,9 @@ class AssetDepreciationSchedule(Document): self.frequency_of_depreciation = row.frequency_of_depreciation self.rate_of_depreciation = row.rate_of_depreciation self.expected_value_after_useful_life = row.expected_value_after_useful_life - self.daily_depreciation = row.daily_depreciation + self.depreciation_amount_based_on_num_days_in_month = ( + row.depreciation_amount_based_on_num_days_in_month + ) self.status = "Draft" def make_depr_schedule( @@ -573,7 +575,7 @@ def get_straight_line_or_manual_depr_amount( ) # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: - if row.daily_depreciation: + if row.depreciation_amount_based_on_num_days_in_month: daily_depr_amount = ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) ) / date_diff( @@ -618,7 +620,7 @@ def get_straight_line_or_manual_depr_amount( ) / number_of_pending_depreciations # if the Depreciation Schedule is being prepared for the first time else: - if row.daily_depreciation: + if row.depreciation_amount_based_on_num_days_in_month: daily_depr_amount = ( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 2c27dc9aca..df560692c5 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -8,7 +8,7 @@ "finance_book", "depreciation_method", "total_number_of_depreciations", - "daily_depreciation", + "depreciation_amount_based_on_num_days_in_month", "column_break_5", "frequency_of_depreciation", "depreciation_start_date", @@ -86,23 +86,23 @@ "fieldtype": "Percent", "label": "Rate of Depreciation" }, - { - "default": "0", - "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", - "fieldname": "daily_depreciation", - "fieldtype": "Check", - "label": "Daily Depreciation" - }, { "fieldname": "salvage_value_percentage", "fieldtype": "Percent", "label": "Salvage Value Percentage" + }, + { + "default": "0", + "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", + "fieldname": "depreciation_amount_based_on_num_days_in_month", + "fieldtype": "Check", + "label": "Depreciation amount based on number of days in the month" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-09-29 15:39:52.740594", + "modified": "2023-11-03 21:30:24.266601", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 78d2c2c340..ae2caa71ee 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -345,5 +345,6 @@ erpnext.patches.v14_0.rename_over_order_allowance_field erpnext.patches.v14_0.migrate_delivery_stop_lock_field execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50) execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50) +erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py b/erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py new file mode 100644 index 0000000000..63dc0e09bc --- /dev/null +++ b/erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py @@ -0,0 +1,21 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + + +from frappe.model.utils.rename_field import rename_field + + +def execute(): + try: + rename_field( + "Asset Finance Book", "daily_depreciation", "depreciation_amount_based_on_num_days_in_month" + ) + rename_field( + "Asset Depreciation Schedule", + "daily_depreciation", + "depreciation_amount_based_on_num_days_in_month", + ) + + except Exception as e: + if e.args[0] != 1054: + raise From 2ce6bbf291d656542492106555ef10ac920731c1 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Sat, 4 Nov 2023 01:41:46 +0530 Subject: [PATCH 266/280] chore: rename depreciation_amount_based_on_num_days_in_month to daily_prorata_based [dev] (#37897) chore: rename depreciation_amount_based_on_num_days_in_month to daily_prorata_based --- erpnext/assets/doctype/asset/asset.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 7 +++---- .../asset_depreciation_schedule.json | 6 +++--- .../asset_depreciation_schedule.py | 8 +++---- .../asset_finance_book.json | 6 +++--- erpnext/patches.txt | 1 + ...um_days_in_month_to_daily_prorata_based.py | 21 +++++++++++++++++++ 7 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index c003afe118..3c570d1af0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -818,7 +818,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount): "depreciation_method": d.depreciation_method, "total_number_of_depreciations": d.total_number_of_depreciations, "frequency_of_depreciation": d.frequency_of_depreciation, - "depreciation_amount_based_on_num_days_in_month": d.depreciation_amount_based_on_num_days_in_month, + "daily_prorata_based": d.daily_prorata_based, "salvage_value_percentage": d.salvage_value_percentage, "expected_value_after_useful_life": flt(gross_purchase_amount) * flt(d.salvage_value_percentage / 100), diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index cdaccbbfbe..9e3ec6faa8 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -755,7 +755,7 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) - def test_schedule_for_straight_line_method_with_depreciation_amount_based_on_num_days_in_month( + def test_schedule_for_straight_line_method_with_daily_prorata_based( self, ): asset = create_asset( @@ -766,7 +766,7 @@ class TestDepreciationMethods(AssetSetup): depreciation_start_date="2023-01-31", total_number_of_depreciations=12, frequency_of_depreciation=1, - depreciation_amount_based_on_num_days_in_month=1, + daily_prorata_based=1, ) expected_schedules = [ @@ -1762,8 +1762,7 @@ def create_asset(**args): "total_number_of_depreciations": args.total_number_of_depreciations or 5, "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, "depreciation_start_date": args.depreciation_start_date, - "depreciation_amount_based_on_num_days_in_month": args.depreciation_amount_based_on_num_days_in_month - or 0, + "daily_prorata_based": args.daily_prorata_based or 0, }, ) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index 6f07d84bcb..8d8b46321f 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -19,7 +19,7 @@ "depreciation_method", "total_number_of_depreciations", "rate_of_depreciation", - "depreciation_amount_based_on_num_days_in_month", + "daily_prorata_based", "column_break_8", "frequency_of_depreciation", "expected_value_after_useful_life", @@ -179,9 +179,9 @@ { "default": "0", "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", - "fieldname": "depreciation_amount_based_on_num_days_in_month", + "fieldname": "daily_prorata_based", "fieldtype": "Check", - "label": "Depreciation amount based on number of days in the month", + "label": "Depreciate based on daily pro-rata", "print_hide": 1, "read_only": 1 } diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 109f96f9f4..7305691f97 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -153,9 +153,7 @@ class AssetDepreciationSchedule(Document): self.frequency_of_depreciation = row.frequency_of_depreciation self.rate_of_depreciation = row.rate_of_depreciation self.expected_value_after_useful_life = row.expected_value_after_useful_life - self.depreciation_amount_based_on_num_days_in_month = ( - row.depreciation_amount_based_on_num_days_in_month - ) + self.daily_prorata_based = row.daily_prorata_based self.status = "Draft" def make_depr_schedule( @@ -575,7 +573,7 @@ def get_straight_line_or_manual_depr_amount( ) # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: - if row.depreciation_amount_based_on_num_days_in_month: + if row.daily_prorata_based: daily_depr_amount = ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) ) / date_diff( @@ -620,7 +618,7 @@ def get_straight_line_or_manual_depr_amount( ) / number_of_pending_depreciations # if the Depreciation Schedule is being prepared for the first time else: - if row.depreciation_amount_based_on_num_days_in_month: + if row.daily_prorata_based: daily_depr_amount = ( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index df560692c5..e597d5fe31 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -8,7 +8,7 @@ "finance_book", "depreciation_method", "total_number_of_depreciations", - "depreciation_amount_based_on_num_days_in_month", + "daily_prorata_based", "column_break_5", "frequency_of_depreciation", "depreciation_start_date", @@ -94,9 +94,9 @@ { "default": "0", "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"", - "fieldname": "depreciation_amount_based_on_num_days_in_month", + "fieldname": "daily_prorata_based", "fieldtype": "Check", - "label": "Depreciation amount based on number of days in the month" + "label": "Depreciate based on daily pro-rata" } ], "index_web_pages_for_search": 1, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ae2caa71ee..1e5b08bf36 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -346,5 +346,6 @@ erpnext.patches.v14_0.migrate_delivery_stop_lock_field execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50) execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50) erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month +erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py b/erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py new file mode 100644 index 0000000000..2c03c23d87 --- /dev/null +++ b/erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py @@ -0,0 +1,21 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + + +from frappe.model.utils.rename_field import rename_field + + +def execute(): + try: + rename_field( + "Asset Finance Book", "depreciation_amount_based_on_num_days_in_month", "daily_prorata_based" + ) + rename_field( + "Asset Depreciation Schedule", + "depreciation_amount_based_on_num_days_in_month", + "daily_prorata_based", + ) + + except Exception as e: + if e.args[0] != 1054: + raise From d9e284366d6c67ebd41b914b248c6ac94e973b7e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 27 Oct 2023 16:35:35 +0530 Subject: [PATCH 267/280] fix: consider reserved serial nos while cancelling a stock transaction --- .../stock_reservation_entry.py | 29 ++++++++ erpnext/stock/stock_ledger.py | 74 +++++++++++++++---- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 6b39965f9b..9e79702d82 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -681,6 +681,35 @@ def get_sre_reserved_qty_for_voucher_detail_no( return flt(reserved_qty[0][0]) +def get_sre_reserved_serial_nos_details( + item_code: str, warehouse: str, serial_nos: list = None +) -> dict: + """Returns a dict of `Serial No` reserved in Stock Reservation Entry. The dict is like {serial_no: sre_name, ...}""" + + sre = frappe.qb.DocType("Stock Reservation Entry") + sb_entry = frappe.qb.DocType("Serial and Batch Entry") + query = ( + frappe.qb.from_(sre) + .inner_join(sb_entry) + .on(sre.name == sb_entry.parent) + .select(sb_entry.serial_no, sre.name) + .where( + (sre.docstatus == 1) + & (sre.item_code == item_code) + & (sre.warehouse == warehouse) + & (sre.reserved_qty > sre.delivered_qty) + & (sre.status.notin(["Delivered", "Cancelled"])) + & (sre.reservation_based_on == "Serial and Batch") + ) + .orderby(sb_entry.creation) + ) + + if serial_nos: + query = query.where(sb_entry.serial_no.isin(serial_nos)) + + return frappe._dict(query.run()) + + def get_sre_details_for_voucher(voucher_type: str, voucher_no: str) -> list[dict]: """Returns a list of SREs for the provided voucher.""" diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 551701b47a..ab88381d65 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -19,6 +19,9 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_in from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock, ) +from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( + get_sre_reserved_serial_nos_details, +) from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_or_make_bin, @@ -1719,22 +1722,22 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): frappe.throw(message, NegativeStockError, title=_("Insufficient Stock")) - if not args.batch_no: - return + if args.batch_no: + neg_batch_sle = get_future_sle_with_negative_batch_qty(args) + if is_negative_with_precision(neg_batch_sle, is_batch=True): + message = _( + "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." + ).format( + abs(neg_batch_sle[0]["cumulative_total"]), + frappe.get_desk_link("Batch", args.batch_no), + frappe.get_desk_link("Warehouse", args.warehouse), + neg_batch_sle[0]["posting_date"], + neg_batch_sle[0]["posting_time"], + frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]), + ) + frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch")) - neg_batch_sle = get_future_sle_with_negative_batch_qty(args) - if is_negative_with_precision(neg_batch_sle, is_batch=True): - message = _( - "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." - ).format( - abs(neg_batch_sle[0]["cumulative_total"]), - frappe.get_desk_link("Batch", args.batch_no), - frappe.get_desk_link("Warehouse", args.warehouse), - neg_batch_sle[0]["posting_date"], - neg_batch_sle[0]["posting_time"], - frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]), - ) - frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch")) + validate_reserved_stock(args) def is_negative_with_precision(neg_sle, is_batch=False): @@ -1801,6 +1804,47 @@ def get_future_sle_with_negative_batch_qty(args): ) +def validate_reserved_stock(kwargs): + if kwargs.serial_no: + serial_nos = kwargs.serial_no.split("\n") + validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos) + + elif kwargs.serial_and_batch_bundle: + sbb_entries = frappe.db.get_all( + "Serial and Batch Entry", + { + "parenttype": "Serial and Batch Bundle", + "parent": kwargs.serial_and_batch_bundle, + "docstatus": 1, + }, + ["batch_no", "serial_no", "qty"], + ) + serial_nos = [entry.serial_no for entry in sbb_entries if entry.serial_no] + + if serial_nos: + validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos) + + +def validate_reserved_serial_nos(item_code, warehouse, serial_nos): + if reserved_serial_nos_details := get_sre_reserved_serial_nos_details( + item_code, warehouse, serial_nos + ): + if common_serial_nos := list( + set(serial_nos).intersection(set(reserved_serial_nos_details.keys())) + ): + msg = _( + "Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding." + ) + msg += "
" + msg += _("Example: Serial No {0} reserved in {1}.").format( + frappe.bold(common_serial_nos[0]), + frappe.get_desk_link( + "Stock Reservation Entry", reserved_serial_nos_details[common_serial_nos[0]] + ), + ) + frappe.throw(msg, title=_("Reserved Serial No.")) + + def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool: if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)): return True From e1a87a802d18f1fb33c3d9f1066da0cb7b3d4210 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 31 Oct 2023 18:41:58 +0530 Subject: [PATCH 268/280] fix: consider reserved batches while cancelling a stock transaction --- .../stock_reservation_entry.py | 33 +++++++++++++ erpnext/stock/stock_ledger.py | 48 +++++++++++++++++-- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 9e79702d82..8063ad508f 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -710,6 +710,39 @@ def get_sre_reserved_serial_nos_details( return frappe._dict(query.run()) +def get_sre_reserved_batch_nos_details( + item_code: str, warehouse: str, batch_nos: list = None +) -> dict: + """Returns a dict of `Batch Qty` reserved in Stock Reservation Entry. The dict is like {batch_no: qty, ...}""" + + sre = frappe.qb.DocType("Stock Reservation Entry") + sb_entry = frappe.qb.DocType("Serial and Batch Entry") + query = ( + frappe.qb.from_(sre) + .inner_join(sb_entry) + .on(sre.name == sb_entry.parent) + .select( + sb_entry.batch_no, + Sum(sb_entry.qty - sb_entry.delivered_qty), + ) + .where( + (sre.docstatus == 1) + & (sre.item_code == item_code) + & (sre.warehouse == warehouse) + & ((sre.reserved_qty - sre.delivered_qty) > 0) + & (sre.status.notin(["Delivered", "Cancelled"])) + & (sre.reservation_based_on == "Serial and Batch") + ) + .groupby(sb_entry.batch_no) + .orderby(sb_entry.creation) + ) + + if batch_nos: + query = query.where(sb_entry.batch_no.isin(batch_nos)) + + return frappe._dict(query.run()) + + def get_sre_details_for_voucher(voucher_type: str, voucher_no: str) -> list[dict]: """Returns a list of SREs for the provided voucher.""" diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ab88381d65..d8cfdaaaca 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -11,11 +11,17 @@ from frappe import _, scrub from frappe.model.meta import get_field_precision from frappe.query_builder import Case from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, parse_json +from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json import erpnext from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions +from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + get_available_batches, +) +from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( + get_sre_reserved_batch_nos_details, +) from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock, ) @@ -1809,6 +1815,9 @@ def validate_reserved_stock(kwargs): serial_nos = kwargs.serial_no.split("\n") validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos) + elif kwargs.batch_no: + validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, [kwargs.batch_no]) + elif kwargs.serial_and_batch_bundle: sbb_entries = frappe.db.get_all( "Serial and Batch Entry", @@ -1817,12 +1826,13 @@ def validate_reserved_stock(kwargs): "parent": kwargs.serial_and_batch_bundle, "docstatus": 1, }, - ["batch_no", "serial_no", "qty"], + ["batch_no", "serial_no"], ) - serial_nos = [entry.serial_no for entry in sbb_entries if entry.serial_no] - if serial_nos: + if serial_nos := [entry.serial_no for entry in sbb_entries if entry.serial_no]: validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos) + elif batch_nos := [entry.batch_no for entry in sbb_entries if entry.batch_no]: + validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, batch_nos) def validate_reserved_serial_nos(item_code, warehouse, serial_nos): @@ -1845,6 +1855,36 @@ def validate_reserved_serial_nos(item_code, warehouse, serial_nos): frappe.throw(msg, title=_("Reserved Serial No.")) +def validate_reserved_batch_nos(item_code, warehouse, batch_nos): + if reserved_batches_map := get_sre_reserved_batch_nos_details(item_code, warehouse, batch_nos): + available_batches = get_available_batches( + frappe._dict( + { + "item_code": item_code, + "warehouse": warehouse, + "posting_date": nowdate(), + "posting_time": nowtime(), + } + ) + ) + available_batches_map = {row.batch_no: row.qty for row in available_batches} + precision = cint(frappe.db.get_default("float_precision")) or 2 + + for batch_no in batch_nos: + diff = flt( + available_batches_map.get(batch_no, 0) - reserved_batches_map.get(batch_no, 0), precision + ) + if diff < 0 and abs(diff) > 0.0001: + msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format( + abs(diff), + frappe.get_desk_link("Batch", batch_no), + frappe.get_desk_link("Warehouse", warehouse), + nowdate(), + nowtime(), + ) + frappe.throw(msg, title=_("Reserved Stock for Batch")) + + def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool: if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)): return True From 98d6cdd53c98b2244031f6b55a7658f1b21b7337 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 16:52:42 +0530 Subject: [PATCH 269/280] feat: add field `reserved_stock` in Bin --- erpnext/stock/doctype/bin/bin.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index 02684a7241..312470d50e 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -13,12 +13,13 @@ "planned_qty", "indented_qty", "ordered_qty", + "projected_qty", "column_break_xn5j", "reserved_qty", "reserved_qty_for_production", "reserved_qty_for_sub_contract", "reserved_qty_for_production_plan", - "projected_qty", + "reserved_stock", "section_break_pmrs", "stock_uom", "column_break_0slj", @@ -173,13 +174,20 @@ { "fieldname": "column_break_0slj", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "reserved_stock", + "fieldtype": "Float", + "label": "Reserved Stock", + "read_only": 1 } ], "hide_toolbar": 1, "idx": 1, "in_create": 1, "links": [], - "modified": "2023-11-01 15:35:51.722534", + "modified": "2023-11-01 16:51:17.079107", "modified_by": "Administrator", "module": "Stock", "name": "Bin", From f52916a2c360dd6befdd1f3dcc6c184cc181662b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 17:37:17 +0530 Subject: [PATCH 270/280] feat: maintain `Reserved Stock` in Bin --- erpnext/stock/doctype/bin/bin.py | 11 +++++++++++ erpnext/stock/doctype/delivery_note/delivery_note.py | 6 ++++++ .../stock_reservation_entry.py | 12 ++++++++++++ 3 files changed, 29 insertions(+) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 5abea9e69f..df466ede68 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -148,6 +148,17 @@ class Bin(Document): self.set_projected_qty() self.db_set("projected_qty", self.projected_qty, update_modified=True) + def update_reserved_stock(self): + """Update `Reserved Stock` on change in Reserved Qty of Stock Reservation Entry""" + + from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( + get_sre_reserved_qty_for_item_and_warehouse, + ) + + reserved_stock = get_sre_reserved_qty_for_item_and_warehouse(self.item_code, self.warehouse) + + self.db_set("reserved_stock", flt(reserved_stock), update_modified=True) + def on_doctype_update(): frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 190575eb94..66dd33a400 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -365,6 +365,9 @@ class DeliveryNote(SellingController): # Update Stock Reservation Entry `Status` based on `Delivered Qty`. sre_doc.update_status() + # Update Reserved Stock in Bin. + sre_doc.update_reserved_stock_in_bin() + qty_to_deliver -= qty_can_be_deliver if self._action == "cancel": @@ -427,6 +430,9 @@ class DeliveryNote(SellingController): # Update Stock Reservation Entry `Status` based on `Delivered Qty`. sre_doc.update_status() + # Update Reserved Stock in Bin. + sre_doc.update_reserved_stock_in_bin() + qty_to_undelivered -= qty_can_be_undelivered def validate_against_stock_reservation_entries(self): diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 8063ad508f..09542826f3 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -9,6 +9,8 @@ from frappe.model.document import Document from frappe.query_builder.functions import Sum from frappe.utils import cint, flt +from erpnext.stock.utils import get_or_make_bin + class StockReservationEntry(Document): def validate(self) -> None: @@ -31,6 +33,7 @@ class StockReservationEntry(Document): self.update_reserved_qty_in_voucher() self.update_reserved_qty_in_pick_list() self.update_status() + self.update_reserved_stock_in_bin() def on_update_after_submit(self) -> None: self.can_be_updated() @@ -40,12 +43,14 @@ class StockReservationEntry(Document): self.validate_reservation_based_on_serial_and_batch() self.update_reserved_qty_in_voucher() self.update_status() + self.update_reserved_stock_in_bin() self.reload() def on_cancel(self) -> None: self.update_reserved_qty_in_voucher() self.update_reserved_qty_in_pick_list() self.update_status() + self.update_reserved_stock_in_bin() def validate_amended_doc(self) -> None: """Raises an exception if document is amended.""" @@ -341,6 +346,13 @@ class StockReservationEntry(Document): update_modified=update_modified, ) + def update_reserved_stock_in_bin(self) -> None: + """Updates `Reserved Stock` in Bin.""" + + bin_name = get_or_make_bin(self.item_code, self.warehouse) + bin_doc = frappe.get_cached_doc("Bin", bin_name) + bin_doc.update_reserved_stock() + def update_status(self, status: str = None, update_modified: bool = True) -> None: """Updates status based on Voucher Qty, Reserved Qty and Delivered Qty.""" From 73b65ac82ec11698605720bf37437fab853f3120 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 18:35:07 +0530 Subject: [PATCH 271/280] fix: consider reserved stock while cancelling a stock transaction --- erpnext/stock/stock_ledger.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d8cfdaaaca..1ed1c4759d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -21,16 +21,12 @@ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle impor ) from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_batch_nos_details, -) -from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( - get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock, -) -from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( get_sre_reserved_serial_nos_details, ) from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_or_make_bin, + get_stock_balance, get_valuation_method, ) from erpnext.stock.valuation import FIFOValuation, LIFOValuation, round_off_if_near_zero @@ -97,6 +93,7 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc is_stock_item = frappe.get_cached_value("Item", args.get("item_code"), "is_stock_item") if is_stock_item: bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse")) + args.reserved_stock = flt(frappe.db.get_value("Bin", bin_name, "reserved_stock")) repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher) update_bin_qty(bin_name, args) else: @@ -123,6 +120,7 @@ def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_vou "voucher_no": args.get("voucher_no"), "sle_id": args.get("name"), "creation": args.get("creation"), + "reserved_stock": args.get("reserved_stock"), }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher, @@ -520,7 +518,7 @@ class update_entries_after(object): self.new_items_found = False self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict()) self.affected_transactions: Set[Tuple[str, str]] = set() - self.reserved_stock = get_reserved_stock(self.args.item_code, self.args.warehouse) + self.reserved_stock = flt(self.args.reserved_stock) self.data = frappe._dict() self.initialize_previous_data(self.args) @@ -1743,7 +1741,8 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): ) frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch")) - validate_reserved_stock(args) + if args.reserved_stock: + validate_reserved_stock(args) def is_negative_with_precision(neg_sle, is_batch=False): @@ -1834,6 +1833,21 @@ def validate_reserved_stock(kwargs): elif batch_nos := [entry.batch_no for entry in sbb_entries if entry.batch_no]: validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, batch_nos) + else: + precision = cint(frappe.db.get_default("float_precision")) or 2 + balance_qty = get_stock_balance(kwargs.item_code, kwargs.warehouse) + + diff = flt(balance_qty - kwargs.get("reserved_stock", 0), precision) + if diff < 0 and abs(diff) > 0.0001: + msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format( + abs(diff), + frappe.get_desk_link("Item", kwargs.item_code), + frappe.get_desk_link("Warehouse", kwargs.warehouse), + nowdate(), + nowtime(), + ) + frappe.throw(msg, title=_("Reserved Stock")) + def validate_reserved_serial_nos(item_code, warehouse, serial_nos): if reserved_serial_nos_details := get_sre_reserved_serial_nos_details( From 10242235bc6bb9081abd4b8c48c23541e33ae646 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 21:02:32 +0530 Subject: [PATCH 272/280] fix(test): `test_stock_reservation_against_sales_order` --- .../stock_reservation_entry/test_stock_reservation_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index f4c74a8aac..21dbf3030e 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -286,6 +286,7 @@ class TestStockReservationEntry(FrappeTestCase): self.assertEqual(item.stock_reserved_qty, sre_details.reserved_qty) self.assertEqual(sre_details.status, "Partially Reserved") + cancel_stock_reservation_entries("Sales Order", so.name) se.cancel() # Test - 3: Stock should be fully Reserved if the Available Qty to Reserve is greater than the Un-reserved Qty. From 1f88b1ef84c826fc35ce3d60a562a3fe8a75b9f2 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 1 Nov 2023 20:48:09 +0530 Subject: [PATCH 273/280] chore: patch to set reserved stock in Bin --- erpnext/patches.txt | 1 + .../v15_0/set_reserved_stock_in_bin.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 erpnext/patches/v15_0/set_reserved_stock_in_bin.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1e5b08bf36..e0f32c55da 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -347,5 +347,6 @@ execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50 execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50) erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based +erpnext.patches.v15_0.set_reserved_stock_in_bin # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v15_0/set_reserved_stock_in_bin.py b/erpnext/patches/v15_0/set_reserved_stock_in_bin.py new file mode 100644 index 0000000000..fd0a23333e --- /dev/null +++ b/erpnext/patches/v15_0/set_reserved_stock_in_bin.py @@ -0,0 +1,24 @@ +import frappe +from frappe.query_builder.functions import Sum + + +def execute(): + sre = frappe.qb.DocType("Stock Reservation Entry") + query = ( + frappe.qb.from_(sre) + .select( + sre.item_code, + sre.warehouse, + Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_stock"), + ) + .where((sre.docstatus == 1) & (sre.status.notin(["Delivered", "Cancelled"]))) + .groupby(sre.item_code, sre.warehouse) + ) + + for d in query.run(as_dict=True): + frappe.db.set_value( + "Bin", + {"item_code": d.item_code, "warehouse": d.warehouse}, + "reserved_stock", + d.reserved_stock, + ) From 9231706227977951cb69d765f77793f47a6f5c77 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 2 Nov 2023 10:36:00 +0530 Subject: [PATCH 274/280] fix: qty based check for stock reservation of serial-batch items based on qty --- erpnext/stock/stock_ledger.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 1ed1c4759d..e9381d42b9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1833,20 +1833,20 @@ def validate_reserved_stock(kwargs): elif batch_nos := [entry.batch_no for entry in sbb_entries if entry.batch_no]: validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, batch_nos) - else: - precision = cint(frappe.db.get_default("float_precision")) or 2 - balance_qty = get_stock_balance(kwargs.item_code, kwargs.warehouse) + # Qty based validation for non-serial-batch items OR SRE with Reservation Based On Qty. + precision = cint(frappe.db.get_default("float_precision")) or 2 + balance_qty = get_stock_balance(kwargs.item_code, kwargs.warehouse) - diff = flt(balance_qty - kwargs.get("reserved_stock", 0), precision) - if diff < 0 and abs(diff) > 0.0001: - msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format( - abs(diff), - frappe.get_desk_link("Item", kwargs.item_code), - frappe.get_desk_link("Warehouse", kwargs.warehouse), - nowdate(), - nowtime(), - ) - frappe.throw(msg, title=_("Reserved Stock")) + diff = flt(balance_qty - kwargs.get("reserved_stock", 0), precision) + if diff < 0 and abs(diff) > 0.0001: + msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format( + abs(diff), + frappe.get_desk_link("Item", kwargs.item_code), + frappe.get_desk_link("Warehouse", kwargs.warehouse), + nowdate(), + nowtime(), + ) + frappe.throw(msg, title=_("Reserved Stock")) def validate_reserved_serial_nos(item_code, warehouse, serial_nos): From 54b323e557f605294e8bcdd9eddf2fd4dd66ab38 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Nov 2023 18:00:49 +0530 Subject: [PATCH 275/280] test: add test case for stock stock reservation --- .../test_stock_reservation_entry.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 21dbf3030e..dd023e2080 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -494,7 +494,7 @@ class TestStockReservationEntry(FrappeTestCase): "pick_serial_and_batch_based_on": "FIFO", }, ) - def test_stock_reservation_from_pick_list(self): + def test_stock_reservation_from_pick_list(self) -> None: items_details = create_items() create_material_receipt(items_details, self.warehouse, qty=100) @@ -576,7 +576,7 @@ class TestStockReservationEntry(FrappeTestCase): "auto_reserve_stock_for_sales_order_on_purchase": 1, }, ) - def test_stock_reservation_from_purchase_receipt(self): + def test_stock_reservation_from_purchase_receipt(self) -> None: from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt from erpnext.selling.doctype.sales_order.sales_order import make_material_request from erpnext.stock.doctype.material_request.material_request import make_purchase_order @@ -646,6 +646,40 @@ class TestStockReservationEntry(FrappeTestCase): # Test - 3: Reserved Serial/Batch Nos should be equal to PR Item Serial/Batch Nos. self.assertEqual(set(sb_details), set(reserved_sb_details)) + @change_settings( + "Stock Settings", + { + "allow_negative_stock": 0, + "enable_stock_reservation": 1, + "auto_reserve_serial_and_batch": 1, + "pick_serial_and_batch_based_on": "FIFO", + }, + ) + def test_consider_reserved_stock_while_cancelling_an_inward_transaction(self) -> None: + items_details = create_items() + se = create_material_receipt(items_details, self.warehouse, qty=100) + + item_list = [] + for item_code, properties in items_details.items(): + item_list.append( + { + "item_code": item_code, + "warehouse": self.warehouse, + "qty": randint(11, 100), + "uom": properties.stock_uom, + "rate": randint(10, 400), + } + ) + + so = make_sales_order( + item_list=item_list, + warehouse=self.warehouse, + ) + so.create_stock_reservation_entries() + + # Test - 1: ValidationError should be thrown as the inwarded stock is reserved. + self.assertRaises(frappe.ValidationError, se.cancel) + def tearDown(self) -> None: cancel_all_stock_reservation_entries() return super().tearDown() From a3191f1c8c08c5723d35f4cf05d44f123ec4ca2e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Nov 2023 14:37:23 +0530 Subject: [PATCH 276/280] refactor: flag to toggle billed amy update in DN for Credit Note --- .../accounts/doctype/sales_invoice/sales_invoice.json | 10 +++++++++- .../accounts/doctype/sales_invoice/sales_invoice.py | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e5adeae501..cd725b9862 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -26,6 +26,7 @@ "is_return", "return_against", "update_billed_amount_in_sales_order", + "update_billed_amount_in_delivery_note", "is_debit_note", "amended_from", "accounting_dimensions_section", @@ -2153,6 +2154,13 @@ "fieldname": "use_company_roundoff_cost_center", "fieldtype": "Check", "label": "Use Company default Cost Center for Round off" + }, + { + "default": "0", + "depends_on": "eval: doc.is_return", + "fieldname": "update_billed_amount_in_delivery_note", + "fieldtype": "Check", + "label": "Update Billed Amount in Delivery Note" } ], "icon": "fa fa-file-text", @@ -2165,7 +2173,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-07-25 16:02:18.988799", + "modified": "2023-11-03 14:39:38.012346", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f6d9c93261..45a65278d6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -253,6 +253,7 @@ class SalesInvoice(SellingController): self.update_status_updater_args() self.update_prevdoc_status() + self.update_billing_status_in_dn() self.clear_unallocated_mode_of_payments() @@ -1429,6 +1430,8 @@ class SalesInvoice(SellingController): ) def update_billing_status_in_dn(self, update_modified=True): + if self.is_return and not self.update_billed_amount_in_delivery_note: + return updated_delivery_notes = [] for d in self.get("items"): if d.dn_detail: From 0c5bdbdcf3fd408b793c755900ba7e3bf958d482 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 5 Nov 2023 08:32:27 +0530 Subject: [PATCH 277/280] refactor(test): enable billed amt update on Sales Return(Cr Note) --- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 1eecf6dc2a..137c352e99 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1029,6 +1029,7 @@ class TestDeliveryNote(FrappeTestCase): dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-3) si1 = make_sales_invoice(dn1.name) + si1.update_billed_amount_in_delivery_note = True si1.insert() si1.submit() dn1.reload() @@ -1037,6 +1038,7 @@ class TestDeliveryNote(FrappeTestCase): dn2 = create_delivery_note(is_return=1, return_against=dn.name, qty=-4) si2 = make_sales_invoice(dn2.name) + si2.update_billed_amount_in_delivery_note = True si2.insert() si2.submit() dn2.reload() From e5bc8fccb17d96df81e10d3b4b565b430c1b5374 Mon Sep 17 00:00:00 2001 From: viralkansodiya15 <98073516+viralpatel15@users.noreply.github.com> Date: Sun, 5 Nov 2023 11:54:35 +0530 Subject: [PATCH 278/280] fix: list index out of range (#37890) * fix: list index out of range * fix: solve linter test failing --- erpnext/assets/doctype/asset/depreciation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index e2a4b2909a..84a428ca54 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -780,6 +780,15 @@ def get_disposal_account_and_cost_center(company): def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None): asset_doc = frappe.get_doc("Asset", asset) + if asset_doc.available_for_use_date > getdate(disposal_date): + frappe.throw( + "Disposal date {0} cannot be before available for use date {1} of the asset.".format( + disposal_date, asset_doc.available_for_use_date + ) + ) + elif asset_doc.available_for_use_date == getdate(disposal_date): + return flt(asset_doc.gross_purchase_amount - asset_doc.opening_accumulated_depreciation) + if not asset_doc.calculate_depreciation: return flt(asset_doc.value_after_depreciation) From 2b02ef00664b12812a99059bd9a29827231fdd94 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 5 Nov 2023 17:25:05 +0530 Subject: [PATCH 279/280] fix: POS change amount gl entry with no amount (#37799) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f6d9c93261..6f49339668 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1019,7 +1019,7 @@ class SalesInvoice(SellingController): def make_customer_gl_entry(self, gl_entries): # Checked both rounding_adjustment and rounded_total - # because rounded_total had value even before introcution of posting GLE based on rounded total + # because rounded_total had value even before introduction of posting GLE based on rounded total grand_total = ( self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total ) @@ -1267,7 +1267,7 @@ class SalesInvoice(SellingController): if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount: payment_mode.base_amount -= flt(self.change_amount) - if payment_mode.amount: + if payment_mode.base_amount: # POS, make payment entries gl_entries.append( self.get_gl_dict( From 34d3eb88b39ffb437fc111ed03527f2c3c4baf9b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 6 Nov 2023 11:07:09 +0530 Subject: [PATCH 280/280] feat: reserved production plan sub assembly items (#37884) --- .../production_plan/production_plan.json | 9 +- .../production_plan/production_plan.py | 44 ++++++++- .../production_plan/test_production_plan.py | 91 ++++++++++++++++++- .../production_plan_sub_assembly_item.json | 27 ++---- .../doctype/work_order/work_order.py | 36 +++++++- erpnext/stock/doctype/bin/bin.py | 30 +++++- 6 files changed, 212 insertions(+), 25 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 4a0041662b..49386c4ebc 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -36,6 +36,7 @@ "prod_plan_references", "section_break_24", "combine_sub_items", + "sub_assembly_warehouse", "section_break_ucc4", "skip_available_sub_assembly_item", "column_break_igxl", @@ -416,13 +417,19 @@ { "fieldname": "column_break_igxl", "fieldtype": "Column Break" + }, + { + "fieldname": "sub_assembly_warehouse", + "fieldtype": "Link", + "label": "Sub Assembly Warehouse", + "options": "Warehouse" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-09-29 11:41:03.246059", + "modified": "2023-11-03 14:08:11.928027", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1850d1e09e..6b12a29b50 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -490,6 +490,12 @@ class ProductionPlan(Document): bin = frappe.get_doc("Bin", bin_name, for_update=True) bin.update_reserved_qty_for_production_plan() + for d in self.sub_assembly_items: + if d.fg_warehouse and d.type_of_manufacturing == "In House": + bin_name = get_or_make_bin(d.production_item, d.fg_warehouse) + bin = frappe.get_doc("Bin", bin_name, for_update=True) + bin.update_reserved_qty_for_for_sub_assembly() + def delete_draft_work_order(self): for d in frappe.get_all( "Work Order", fields=["name"], filters={"docstatus": 0, "production_plan": ("=", self.name)} @@ -809,7 +815,11 @@ class ProductionPlan(Document): bom_data = [] - warehouse = row.warehouse if self.skip_available_sub_assembly_item else None + warehouse = ( + (self.sub_assembly_warehouse or row.warehouse) + if self.skip_available_sub_assembly_item + else None + ) get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) sub_assembly_items_store.extend(bom_data) @@ -831,7 +841,7 @@ class ProductionPlan(Document): for data in bom_data: data.qty = data.stock_qty data.production_plan_item = row.name - data.fg_warehouse = row.warehouse + data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse data.schedule_date = row.planned_start_date data.type_of_manufacturing = manufacturing_type or ( "Subcontract" if data.is_sub_contracted_item else "In House" @@ -1637,8 +1647,8 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): query = query.run() - if not query: - return 0.0 + if not query or query[0][0] is None: + return None reserved_qty_for_production_plan = flt(query[0][0]) @@ -1780,3 +1790,29 @@ def sales_order_query( query = query.offset(start) return query.run() + + +def get_reserved_qty_for_sub_assembly(item_code, warehouse): + table = frappe.qb.DocType("Production Plan") + child = frappe.qb.DocType("Production Plan Sub Assembly Item") + + query = ( + frappe.qb.from_(table) + .inner_join(child) + .on(table.name == child.parent) + .select(Sum(child.qty - IfNull(child.wo_produced_qty, 0))) + .where( + (table.docstatus == 1) + & (child.production_item == item_code) + & (child.fg_warehouse == warehouse) + & (table.status.notin(["Completed", "Closed"])) + ) + ) + + query = query.run() + + if not query or query[0][0] is None: + return None + + qty = flt(query[0][0]) + return qty if qty > 0 else 0.0 diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index d414988f41..e9c6ee3af2 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1042,13 +1042,14 @@ class TestProductionPlan(FrappeTestCase): after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) self.assertEqual(after_qty - before_qty, 1) - pln = frappe.get_doc("Production Plan", pln.name) pln.cancel() bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + pln.reload() + self.assertEqual(pln.docstatus, 2) self.assertEqual(after_qty, before_qty) def test_resered_qty_for_production_plan_for_work_order(self): @@ -1359,6 +1360,93 @@ class TestProductionPlan(FrappeTestCase): if row.item_code == "ChildPart2 For SUB Test": self.assertEqual(row.quantity, 2) + def test_reserve_sub_assembly_items(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + bom_tree = { + "Fininshed Goods Bicycle": { + "Frame Assembly": {"Frame": {}}, + "Chain Assembly": {"Chain": {}}, + } + } + parent_bom = create_nested_bom(bom_tree, prefix="") + + warehouse = "_Test Warehouse - _TC" + company = "_Test Company" + + sub_assembly_warehouse = create_warehouse("SUB ASSEMBLY WH", company=company) + + for item_code in ["Frame", "Chain"]: + make_stock_entry(item_code=item_code, target=warehouse, qty=2, basic_rate=100) + + before_qty = flt( + frappe.db.get_value( + "Bin", + {"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse}, + "reserved_qty_for_production_plan", + ) + ) + + plan = create_production_plan( + item_code=parent_bom.item, + planned_qty=2, + ignore_existing_ordered_qty=1, + do_not_submit=1, + skip_available_sub_assembly_item=1, + warehouse=warehouse, + sub_assembly_warehouse=sub_assembly_warehouse, + ) + + plan.get_sub_assembly_items() + plan.submit() + + after_qty = flt( + frappe.db.get_value( + "Bin", + {"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse}, + "reserved_qty_for_production_plan", + ) + ) + + self.assertEqual(after_qty, before_qty + 2) + + plan.make_work_order() + work_orders = frappe.get_all( + "Work Order", + fields=["name", "production_item"], + filters={"production_plan": plan.name}, + order_by="creation desc", + ) + + for d in work_orders: + wo_doc = frappe.get_doc("Work Order", d.name) + wo_doc.skip_transfer = 1 + wo_doc.from_wip_warehouse = 1 + + wo_doc.wip_warehouse = ( + warehouse + if d.production_item in ["Frame Assembly", "Chain Assembly"] + else sub_assembly_warehouse + ) + + wo_doc.submit() + + if d.production_item == "Frame Assembly": + self.assertEqual(wo_doc.fg_warehouse, sub_assembly_warehouse) + se_doc = frappe.get_doc(make_se_from_wo(wo_doc.name, "Manufacture", 2)) + se_doc.submit() + + after_qty = flt( + frappe.db.get_value( + "Bin", + {"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse}, + "reserved_qty_for_production_plan", + ) + ) + + self.assertEqual(after_qty, before_qty) + def create_production_plan(**args): """ @@ -1379,6 +1467,7 @@ def create_production_plan(**args): "ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0, "get_items_from": "Sales Order", "skip_available_sub_assembly_item": args.skip_available_sub_assembly_item or 0, + "sub_assembly_warehouse": args.sub_assembly_warehouse, } ) diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json index fde0404c01..aff740b732 100644 --- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -17,11 +17,10 @@ "type_of_manufacturing", "supplier", "work_order_details_section", - "work_order", + "wo_produced_qty", "purchase_order", "production_plan_item", "column_break_7", - "produced_qty", "received_qty", "indent", "section_break_19", @@ -52,13 +51,6 @@ "fieldtype": "Section Break", "label": "Reference" }, - { - "fieldname": "work_order", - "fieldtype": "Link", - "label": "Work Order", - "options": "Work Order", - "read_only": 1 - }, { "fieldname": "column_break_7", "fieldtype": "Column Break" @@ -81,7 +73,8 @@ { "fieldname": "received_qty", "fieldtype": "Float", - "label": "Received Qty" + "label": "Received Qty", + "read_only": 1 }, { "fieldname": "bom_no", @@ -161,12 +154,6 @@ "label": "Target Warehouse", "options": "Warehouse" }, - { - "fieldname": "produced_qty", - "fieldtype": "Data", - "label": "Produced Quantity", - "read_only": 1 - }, { "default": "In House", "fieldname": "type_of_manufacturing", @@ -209,12 +196,18 @@ "label": "Projected Qty", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "wo_produced_qty", + "fieldtype": "Float", + "label": "Produced Qty", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-22 17:52:34.708879", + "modified": "2023-11-03 13:33:42.959387", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Sub Assembly Item", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f9fddcbb5e..36a0cae5cc 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -293,6 +293,7 @@ class WorkOrder(Document): update_produced_qty_in_so_item(self.sales_order, self.sales_order_item) if self.production_plan: + self.set_produced_qty_for_sub_assembly_item() self.update_production_plan_status() def get_transferred_or_manufactured_qty(self, purpose): @@ -569,16 +570,49 @@ class WorkOrder(Document): ) def update_planned_qty(self): + from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_reserved_qty_for_sub_assembly, + ) + + qty_dict = {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)} + + if self.production_plan_sub_assembly_item and self.production_plan: + qty_dict["reserved_qty_for_production_plan"] = get_reserved_qty_for_sub_assembly( + self.production_item, self.fg_warehouse + ) + update_bin_qty( self.production_item, self.fg_warehouse, - {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)}, + qty_dict, ) if self.material_request: mr_obj = frappe.get_doc("Material Request", self.material_request) mr_obj.update_requested_qty([self.material_request_item]) + def set_produced_qty_for_sub_assembly_item(self): + table = frappe.qb.DocType("Work Order") + + query = ( + frappe.qb.from_(table) + .select(Sum(table.produced_qty)) + .where( + (table.production_plan == self.production_plan) + & (table.production_plan_sub_assembly_item == self.production_plan_sub_assembly_item) + & (table.docstatus == 1) + ) + ).run() + + produced_qty = flt(query[0][0]) if query else 0 + + frappe.db.set_value( + "Production Plan Sub Assembly Item", + self.production_plan_sub_assembly_item, + "wo_produced_qty", + produced_qty, + ) + def update_ordered_qty(self): if ( self.production_plan diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index df466ede68..8b2e5cf9ec 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -34,10 +34,15 @@ class Bin(Document): get_reserved_qty_for_production_plan, ) - self.reserved_qty_for_production_plan = get_reserved_qty_for_production_plan( + reserved_qty_for_production_plan = get_reserved_qty_for_production_plan( self.item_code, self.warehouse ) + if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan: + return + + self.reserved_qty_for_production_plan = flt(reserved_qty_for_production_plan) + self.db_set( "reserved_qty_for_production_plan", flt(self.reserved_qty_for_production_plan), @@ -48,6 +53,29 @@ class Bin(Document): self.set_projected_qty() self.db_set("projected_qty", self.projected_qty, update_modified=True) + def update_reserved_qty_for_for_sub_assembly(self): + from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_reserved_qty_for_sub_assembly, + ) + + reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly( + self.item_code, self.warehouse + ) + + if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan: + return + + self.reserved_qty_for_production_plan = flt(reserved_qty_for_production_plan) + self.set_projected_qty() + + self.db_set( + { + "projected_qty": self.projected_qty, + "reserved_qty_for_production_plan": flt(self.reserved_qty_for_production_plan), + }, + update_modified=True, + ) + def update_reserved_qty_for_production(self): """Update qty reserved for production from Production Item tables in open work orders"""