From ebd8f2f45bc8bfdb5e6a32b17901a97c040491af Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 1 Aug 2022 22:29:34 +0530 Subject: [PATCH 001/898] chore: verison bump and change log --- erpnext/__init__.py | 2 +- erpnext/change_log/v14/v14_0_0.md | 105 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 erpnext/change_log/v14/v14_0_0.md diff --git a/erpnext/__init__.py b/erpnext/__init__.py index e0f0c98e9c94..6b6590f944ed 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.0.0-dev" +__version__ = "14.0.0" def get_default_company(user=None): diff --git a/erpnext/change_log/v14/v14_0_0.md b/erpnext/change_log/v14/v14_0_0.md new file mode 100644 index 000000000000..b7ec7b4435f1 --- /dev/null +++ b/erpnext/change_log/v14/v14_0_0.md @@ -0,0 +1,105 @@ +# Version 14.0.0 Release Notes + +### Accounting + +- [Improved Indian Compliance and GST APIs](https://docs.erpnext.com/docs/v14/user/manual/en/regional/india) +- [Common Party Accounting](https://docs.erpnext.com/docs/v14/user/manual/en/accounts/articles/common_party_accounting) +- [Provisional accounting for expenses](https://github.com/frappe/erpnext/pull/29451) +- [Discount Accounting](https://github.com/frappe/erpnext/pull/26359) +- [New Payment Reconciliation Tool](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/payment-reconciliation) +- [Coupon Code in POS](https://github.com/frappe/erpnext/pull/27004) +- [Configurable cost center allocation](https://docs.erpnext.com/docs/v14/user/manual/en/cost_center_allocation) +- [Payment Ledger](https://docs.erpnext.com/docs/v14/user/manual/en/accounts/articles/payment_ledger) +- [Cash and Non trade discounts in Sales Invoice](https://github.com/frappe/erpnext/pull/31405) +- [Improved TaxJar Integration](https://docs.erpnext.com/docs/v14/user/manual/en/erpnext_integration/taxjar_integration) +- [KSA E-Invoicing](https://docs.erpnext.com/docs/v14/user/manual/en/simplified_ksa_vat_management_and_reporting) +- [South Africa VAT Audit Report](https://docs.erpnext.com/docs/v14/user/manual/en/regional/south_africa/vat_audit_report) +- [E Invoice Eway Bill Distance is calculated automatically](https://github.com/frappe/erpnext/pull/30908) +- [Payment Terms Status report](https://github.com/frappe/erpnext/pull/29137) +- [Merge POS invoices based on customer group](https://github.com/frappe/erpnext/pull/27471) +- [Ledger Merger](https://github.com/frappe/erpnext/pull/28812) +- [Increase number of supported currency exchanges](https://github.com/frappe/erpnext/pull/26763) + + + +### Stock +- [LIFO Valuation](https://github.com/frappe/erpnext/pull/29296) +- [Batch-wise Valuation Rates](https://github.com/frappe/erpnext/pull/29804) +- [Better Barcode Scanning](https://github.com/frappe/erpnext/pull/30516) +- [Over transfer allowance for material transfers](https://github.com/frappe/erpnext/pull/26264) +- [Scanning in Pick List](https://github.com/frappe/erpnext/pull/30832) +- [GLE reposting with progress and chunking for backdated entries](https://github.com/frappe/erpnext/pull/31343) + +### E-Commerce +- [Redesigned E-commerce Portal](https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce) +- [E-commerce Search](https://docs.erpnext.com/docs/v14/user/manual/en/e_commerce/e_commerce_search) + + +### Assets +- [Asset Splitting](https://github.com/frappe/erpnext/pull/29350) +- [Grouped Asset](https://github.com/frappe/erpnext/pull/29334) +- [Asset Repair](https://github.com/frappe/erpnext/pull/25798) +- [Consume serialized items during Asset Repair](https://github.com/frappe/erpnext/pull/28349) + +### Manufacturing +- [Faster BOM Update Tool](https://github.com/frappe/erpnext/pull/31078) +- [Scrap Item in Job Card](https://github.com/frappe/erpnext/pull/27518) +- [Process Loss in manufacturing](https://github.com/frappe/erpnext/pull/26151) +- [Production Plan Summary Report](https://github.com/frappe/erpnext/pull/26240) +- [Work Order Consumed Materials Report](https://github.com/frappe/erpnext/pull/28500) +- [Provision to close the Work Order](https://github.com/frappe/erpnext/pull/28150) +- [Provision to aggregate subassembly items in production plan](https://github.com/frappe/erpnext/pull/28939) + +### Subcontracting +- [New Subcontracting Module](https://github.com/frappe/erpnext/pull/30955) +- [Subcontracted Purchase Order from the Production Plan](https://github.com/frappe/erpnext/pull/26240) + + +### CRM +- [Refreshed CRM Flows](https://github.com/frappe/erpnext/pull/31311) +- [New Prospect document](https://github.com/frappe/erpnext/pull/27102) +- [CRM Settings Page](https://docs.erpnext.com/docs/v13/user/manual/en/CRM/crm_settings) +- [Competitor Tagging in Opportunity and Quotation](https://github.com/frappe/erpnext/pull/28050) +- [Sales Pipeline Analytics Report](https://github.com/frappe/erpnext/pull/26639) +- [Opportunity Summary by Sales Stage Report](https://github.com/frappe/erpnext/pull/26639) + + +### HR & Payroll +- [Organizational Chart](https://github.com/frappe/erpnext/pull/26261) +- [Full and Final Settlement](https://github.com/frappe/erpnext/pull/26364) +- [Income tax computation Report](https://github.com/frappe/erpnext/pull/29963) +- [Employee Grievance](https://github.com/frappe/erpnext/pull/25705) +- [Tax for recurring additional salary](https://github.com/frappe/erpnext/pull/27459) +- [Tracking Multi-round interview](https://github.com/frappe/erpnext/pull/25482) +- [Exit Interview and Employee Exits Report](https://github.com/frappe/erpnext/pull/28741) +- [Leave Type configuration to allow over allocation](https://github.com/frappe/erpnext/pull/30940) +- [Employee Reminders](https://github.com/frappe/erpnext/pull/25735) +- [Refactored Employee Leave Balance](https://github.com/frappe/erpnext/pull/29439) + +### Healthcare +- [Treatment Plan Template](https://github.com/frappe/erpnext/pull/26557) +- [Capacity for Service Unit, concurrent appointments based on capacity, Patient Appointments](https://github.com/frappe/erpnext/pull/27219) +- [UOM specific barcode](https://docs.erpnext.com/docs/v14/user/manual/en/stock/articles/track-items-using-barcode#uom-specific-barcode) +- [Redesigned Patient History and Patient Progress](https://github.com/frappe/erpnext/pull/27100) + + +### New apps +The following modules has been separated out from ERPNext and new apps has been created. + +- [HR and Payroll](https://github.com/frappe/hrms) +- [Healthcare](https://github.com/frappe/health) +- [Education](https://github.com/frappe/education) +- [E-commerce Integration](https://github.com/frappe/ecommerce_integrations) +- [Hospitality](https://github.com/frappe/hospitality) +- [Non-Profit](https://github.com/frappe/non_profit) +- [Agriculture](https://github.com/frappe/agriculture) +- [Datev Integration](https://github.com/alyf-de/erpnext_datev) +- [Germany Localisation](https://github.com/alyf-de/erpnext_germany) + +### Others +- [Unicommerce Integration](https://docs.erpnext.com/docs/v13/user/manual/en/erpnext_integration/unicommerce_integration) +- [Bulk Transaction Processing](https://github.com/frappe/erpnext/pull/28580) +- [Refactored Document Naming Settings](https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/settings/document-naming-settings) +- [Project Portal Enhancements](https://github.com/frappe/erpnext/pull/26090) +- [Refund entry against loans](https://github.com/frappe/erpnext/pull/29460) +- [Bank Reconciliation for loan documents](https://github.com/frappe/erpnext/pull/29865) \ No newline at end of file From 248cc48842362ac0cc2a9eab957efaf087e2591a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Aug 2022 12:11:36 +0530 Subject: [PATCH 002/898] ci: setup releases for v14 (#31759) [skip ci] --- .github/workflows/release.yml | 10 ++++++---- .releaserc | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a46002820c0..d61caa98708c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Generate Semantic Release on: push: branches: - - version-13 + - version-14 jobs: release: name: Release @@ -13,10 +13,12 @@ jobs: with: fetch-depth: 0 persist-credentials: false - - name: Setup Node.js v14 + + - name: Setup Node.js uses: actions/setup-node@v2 with: - node-version: 14 + node-version: 16 + - name: Setup dependencies run: | npm install @semantic-release/git @semantic-release/exec --no-save @@ -28,4 +30,4 @@ jobs: GIT_AUTHOR_EMAIL: "developers@frappe.io" GIT_COMMITTER_NAME: "Frappe PR Bot" GIT_COMMITTER_EMAIL: "developers@frappe.io" - run: npx semantic-release \ No newline at end of file + run: npx semantic-release diff --git a/.releaserc b/.releaserc index 8a758ed30a69..454adc7094b8 100644 --- a/.releaserc +++ b/.releaserc @@ -1,5 +1,5 @@ { - "branches": ["version-13"], + "branches": ["version-14"], "plugins": [ "@semantic-release/commit-analyzer", { "preset": "angular", @@ -21,4 +21,4 @@ ], "@semantic-release/github" ] -} \ No newline at end of file +} From 6dce122825ef3e50d34f95524a75d9177a5193fe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 11:16:41 +0530 Subject: [PATCH 003/898] fix: specify allowed doctype in queries (backport #31761) (#31764) fix: specify allowed doctype in queries (#31761) (cherry picked from commit 9baa2229761c5415f29646a1a5bed4a3f4981e05) Co-authored-by: Sagar Vora --- erpnext/controllers/queries.py | 40 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 243ebb66e252..4f8b5c79d240 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -18,8 +18,9 @@ @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): + doctype = "Employee" conditions = [] - fields = get_fields("Employee", ["name", "employee_name"]) + fields = get_fields(doctype, ["name", "employee_name"]) return frappe.db.sql( """select {fields} from `tabEmployee` @@ -49,7 +50,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def lead_query(doctype, txt, searchfield, start, page_len, filters): - fields = get_fields("Lead", ["name", "lead_name", "company_name"]) + doctype = "Lead" + fields = get_fields(doctype, ["name", "lead_name", "company_name"]) return frappe.db.sql( """select {fields} from `tabLead` @@ -77,6 +79,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def customer_query(doctype, txt, searchfield, start, page_len, filters): + doctype = "Customer" conditions = [] cust_master_name = frappe.defaults.get_user_default("cust_master_name") @@ -85,9 +88,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): else: fields = ["name", "customer_name", "customer_group", "territory"] - fields = get_fields("Customer", fields) + fields = get_fields(doctype, fields) - searchfields = frappe.get_meta("Customer").get_search_fields() + searchfields = frappe.get_meta(doctype).get_search_fields() searchfields = " or ".join(field + " like %(txt)s" for field in searchfields) return frappe.db.sql( @@ -116,6 +119,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def supplier_query(doctype, txt, searchfield, start, page_len, filters): + doctype = "Supplier" supp_master_name = frappe.defaults.get_user_default("supp_master_name") if supp_master_name == "Supplier Name": @@ -123,7 +127,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): else: fields = ["name", "supplier_name", "supplier_group"] - fields = get_fields("Supplier", fields) + fields = get_fields(doctype, fields) return frappe.db.sql( """select {field} from `tabSupplier` @@ -147,6 +151,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def tax_account_query(doctype, txt, searchfield, start, page_len, filters): + doctype = "Account" company_currency = erpnext.get_company_currency(filters.get("company")) def get_accounts(with_account_type_filter): @@ -197,13 +202,14 @@ def get_accounts(with_account_type_filter): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): + doctype = "Item" conditions = [] if isinstance(filters, str): filters = json.loads(filters) # Get searchfields from meta and use in Item Link field query - meta = frappe.get_meta("Item", cached=True) + meta = frappe.get_meta(doctype, cached=True) searchfields = meta.get_search_fields() # these are handled separately @@ -257,7 +263,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals filters.pop("supplier", None) description_cond = "" - if frappe.db.count("Item", cache=True) < 50000: + if frappe.db.count(doctype, cache=True) < 50000: # scan description only if items are less than 50000 description_cond = "or tabItem.description LIKE %(txt)s" return frappe.db.sql( @@ -300,8 +306,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def bom(doctype, txt, searchfield, start, page_len, filters): + doctype = "BOM" conditions = [] - fields = get_fields("BOM", ["name", "item"]) + fields = get_fields(doctype, ["name", "item"]) return frappe.db.sql( """select {fields} @@ -331,6 +338,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_project_name(doctype, txt, searchfield, start, page_len, filters): + doctype = "Project" cond = "" if filters and filters.get("customer"): cond = """(`tabProject`.customer = %s or @@ -338,8 +346,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): frappe.db.escape(filters.get("customer")) ) - fields = get_fields("Project", ["name", "project_name"]) - searchfields = frappe.get_meta("Project").get_search_fields() + fields = get_fields(doctype, ["name", "project_name"]) + searchfields = frappe.get_meta(doctype).get_search_fields() searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields]) return frappe.db.sql( @@ -366,7 +374,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): - fields = get_fields("Delivery Note", ["name", "customer", "posting_date"]) + doctype = "Delivery Note" + fields = get_fields(doctype, ["name", "customer", "posting_date"]) return frappe.db.sql( """ @@ -402,6 +411,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_batch_no(doctype, txt, searchfield, start, page_len, filters): + doctype = "Batch" cond = "" if filters.get("posting_date"): cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)" @@ -420,7 +430,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): if filters.get("is_return"): having_clause = "" - meta = frappe.get_meta("Batch", cached=True) + meta = frappe.get_meta(doctype, cached=True) searchfields = meta.get_search_fields() search_columns = "" @@ -496,6 +506,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_account_list(doctype, txt, searchfield, start, page_len, filters): + doctype = "Account" filter_list = [] if isinstance(filters, dict): @@ -514,7 +525,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt]) return frappe.desk.reportview.execute( - "Account", + doctype, filters=filter_list, fields=["name", "parent_account"], limit_start=start, @@ -553,6 +564,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): if not filters: filters = {} + doctype = "Account" condition = "" if filters.get("company"): condition += "and tabAccount.company = %(company)s" @@ -628,6 +640,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): if not filters: filters = {} + doctype = "Account" condition = "" if filters.get("company"): condition += "and tabAccount.company = %(company)s" @@ -650,6 +663,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): @frappe.validate_and_sanitize_search_inputs def warehouse_query(doctype, txt, searchfield, start, page_len, filters): # Should be used when item code is passed in filters. + doctype = "Warehouse" conditions, bin_conditions = [], [] filter_dict = get_doctype_wise_filters(filters) From 30921319135b84fbf96782437f2210ec2c0373d7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 11:17:44 +0530 Subject: [PATCH 004/898] fix: linter (backport #31763) (#31766) fix: linter (#31763) (cherry picked from commit 9c580dde399d12a325b5c0b574f66a59f84dd2ac) Co-authored-by: Devin Slauenwhite --- .../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 c7c746bedefe..e937edbeb289 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -165,7 +165,7 @@ def get_currency_fields(self): "range4", "range5", "future_amount", - "remaining_balance" + "remaining_balance", ] def get_voucher_balance(self, ple): From 53b9d61c46e6ce9b97eaec5779166ab28034eb8f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 11:22:54 +0530 Subject: [PATCH 005/898] =?UTF-8?q?fix:=20getting=20error=20to=20show=20sa?= =?UTF-8?q?les=20invoice=20group=20or=20print=20rep=E2=80=A6=20(backport?= =?UTF-8?q?=20#31756)=20(#31767)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: getting error to show sales invoice group or print rep… (#31756) fix: formatter getting error to show sales invoice group or print report. 1 - When I view the Gross Profit report in Sales Invoice mode, the table is all broken. Error on browser console: TypeError: Cannot read properties of undefined (reading 'indent') 2 - When I try to print, no matter the Group (Sales Invoice, Item Code, Item Group...) nothing happens. in browser log console I have the following error: TypeError: Cannot read properties of undefined (reading 'content') i fixed both errors and all working perfectly. (cherry picked from commit ea88451875e802a47d3e46e4dbcca33c1278662c) Co-authored-by: HarryPaulo --- erpnext/accounts/report/gross_profit/gross_profit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 3d37b5898c14..21205c31634f 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -44,14 +44,14 @@ frappe.query_reports["Gross Profit"] = { "parent_field": "parent_invoice", "initial_depth": 3, "formatter": function(value, row, column, data, default_formatter) { - if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) { + if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) { column._options = "Sales Invoice"; } else { column._options = "Item"; } value = default_formatter(value, row, column, data); - if (data && (data.indent == 0.0 || row[1].content == "Total")) { + if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); value = $value.wrap("

").parent().html(); From 2defb89962fe96afd89a1d36e29888812b08025e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Aug 2022 16:15:32 +0530 Subject: [PATCH 006/898] ci: fix automated release regex (#31770) [skip ci] --- .releaserc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.releaserc b/.releaserc index 454adc7094b8..3c54123c8b5c 100644 --- a/.releaserc +++ b/.releaserc @@ -10,7 +10,7 @@ "@semantic-release/release-notes-generator", [ "@semantic-release/exec", { - "prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py' + "prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py' } ], [ From 8737c10ce4c95acc394dd9d5409fda63e73c6ec7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:49:16 +0530 Subject: [PATCH 007/898] fix(ecommerce): remove query to non-existing field (backport #31771) (#31773) fix(ecommerce): remove query to non-existing field (#31771) (cherry picked from commit 17b9bfd2497e6dc4b1179bd1eb9584e526606e2a) Co-authored-by: Ankush Menat --- erpnext/setup/doctype/item_group/item_group.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 6e940d0cfd4a..411176b70afe 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -142,10 +142,6 @@ def get_item_for_list_in_html(context): if (context.get("website_image") or "").startswith("files/"): context["website_image"] = "/" + quote(context["website_image"]) - context["show_availability_status"] = cint( - frappe.db.get_single_value("E Commerce Settings", "show_availability_status") - ) - products_template = "templates/includes/products_as_list.html" return frappe.get_template(products_template).render(context) From 5a28ba853706ba97cc467b85c9ec86a94816f33e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 29 Jul 2022 12:41:40 +0530 Subject: [PATCH 008/898] fix: set `billing_address` for purchases in `get_party_details` (cherry picked from commit a3625b3817cdadb2db0ff718ca4740e8753d6611) --- erpnext/accounts/party.py | 27 +++++++++++++++++--- erpnext/controllers/buying_controller.py | 1 + erpnext/public/js/controllers/transaction.js | 19 -------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e39f22b4cffd..67cf6443538a 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -207,7 +207,7 @@ def set_address_details( ) if company_address: - party_details.update({"company_address": company_address}) + party_details.company_address = company_address else: party_details.update(get_company_address(company)) @@ -219,12 +219,31 @@ def set_address_details( get_regional_address_details(party_details, doctype, company) elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]: + if shipping_address: + party_details.update( + shipping_address=shipping_address, + shipping_address_display=get_address_display(shipping_address), + **get_fetch_values(doctype, "shipping_address", shipping_address) + ) + if party_details.company_address: - party_details["shipping_address"] = shipping_address or party_details["company_address"] - party_details.shipping_address_display = get_address_display(party_details["shipping_address"]) + # billing address party_details.update( - get_fetch_values(doctype, "shipping_address", party_details.shipping_address) + billing_address=party_details.company_address, + billing_address_display=( + party_details.company_address_display or get_address_display(party_details.company_address) + ), + **get_fetch_values(doctype, "billing_address", party_details.company_address) ) + + # shipping address - if not already set + if not party_details.shipping_address: + party_details.update( + shipping_address=party_details.billing_address, + shipping_address_display=party_details.billing_address_display, + **get_fetch_values(doctype, "shipping_address", party_details.billing_address) + ) + get_regional_address_details(party_details, doctype, company) return party_details.get(billing_address_field), party_details.shipping_address_name diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 036733c0c345..c0f37455a082 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -86,6 +86,7 @@ def set_missing_values(self, for_validate=False): company=self.company, party_address=self.get("supplier_address"), shipping_address=self.get("shipping_address"), + company_address=self.get("billing_address"), fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"), ignore_permissions=self.flags.ignore_permissions, ) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 85485fc7a6f3..c0a8c9e088c7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1,7 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.provide('erpnext.accounts.dimensions'); erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals { setup() { @@ -794,24 +793,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe set_party_account(set_pricing); }); - // Get default company billing address in Purchase Invoice, Order and Receipt - if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) { - frappe.call({ - method: "erpnext.setup.doctype.company.company.get_default_company_address", - args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""}, - debounce: 2000, - callback: function(r) { - if (r.message) { - me.frm.set_value("billing_address", r.message); - } else { - if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) { - me.frm.set_value("company_address", ""); - } - } - } - }); - } - } else { set_party_account(set_pricing); } From 4c825332396662efb2c8463d18d9764d93c4e830 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 29 Jul 2022 13:20:34 +0530 Subject: [PATCH 009/898] fix: set `company_address` for purchases in `party.js` (cherry picked from commit d05082987f47d9f3582447524b314862951507e0) --- erpnext/public/js/utils/party.js | 62 ++++++++++++++------------------ 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a492b32a9f69..58594b0a13d4 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -3,25 +3,14 @@ frappe.provide("erpnext.utils"); +const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']; +const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; + erpnext.utils.get_party_details = function(frm, method, args, callback) { if (!method) { method = "erpnext.accounts.party.get_party_details"; } - if (args) { - if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { - if (frm.doc.company_address && (!args.company_address)) { - args.company_address = frm.doc.company_address; - } - } - - if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { - if (frm.doc.shipping_address && (!args.shipping_address)) { - args.shipping_address = frm.doc.shipping_address; - } - } - } - if (!args) { if ((frm.doctype != "Purchase Order" && frm.doc.customer) || (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) { @@ -45,41 +34,44 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { }; } - if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { - if (!args) { + if (!args) { + if (in_list(SALES_DOCTYPES, frm.doc.doctype)) { args = { party: frm.doc.customer || frm.doc.party_name, party_type: 'Customer' - } - } - if (frm.doc.company_address && (!args.company_address)) { - args.company_address = frm.doc.company_address; + }; } - if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) { - args.shipping_address_name = frm.doc.shipping_address_name; - } - } - - if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { - if (!args) { + if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) { args = { party: frm.doc.supplier, party_type: 'Supplier' - } + }; } + } - if (frm.doc.shipping_address && (!args.shipping_address)) { - args.shipping_address = frm.doc.shipping_address; - } + if (!args || !args.party) return; + + args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; + args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template); + } + + if (in_list(SALES_DOCTYPES, frm.doc.doctype)) { + if (!args.company_address && frm.doc.company_address) { + args.company_address = frm.doc.company_address; } + } - if (args) { - args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; - args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template); + if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) { + if (!args.company_address && frm.doc.billing_address) { + args.company_address = frm.doc.billing_address; + } + + if (!args.shipping_address && frm.doc.shipping_address) { + args.shipping_address = frm.doc.shipping_address; } } - if (!args || !args.party) return; + if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) { if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date", From 1d1f12f9491f0ac26c25e81269a4ba100c3a3757 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 4 Aug 2022 09:48:48 +0530 Subject: [PATCH 010/898] fix: intercompany SO throws exception (cherry picked from commit af0a353b79ed3b6a079c142be69da9cc75df2929) --- 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 f8c26d1e9262..19a234d9df3e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2103,13 +2103,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item" source_document_warehouse_field = "target_warehouse" target_document_warehouse_field = "from_warehouse" + received_items = get_received_items(source_name, target_doctype, target_detail_field) else: source_doc = frappe.get_doc(doctype, source_name) target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order" source_document_warehouse_field = "from_warehouse" target_document_warehouse_field = "target_warehouse" - - received_items = get_received_items(source_name, target_doctype, target_detail_field) + received_items = {} validate_inter_company_transaction(source_doc, doctype) details = get_inter_company_details(source_doc, doctype) From 0faa7b04326db9f896d647d9dee5fa3b79f4051c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Jul 2022 11:57:27 +0530 Subject: [PATCH 011/898] fix: posting_date of linked vouchers should not affect outstanding posting_date filter should not be applied for linked vouchers. (cherry picked from commit 5f1562c5b21da32e7f963a8b052e0eb26b7e7684) --- .../accounts/doctype/payment_entry/payment_entry.py | 4 +++- .../payment_reconciliation.py | 12 ++++++++---- erpnext/accounts/utils.py | 13 ++++++++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index af5a5e249d2b..48edda90324d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1184,6 +1184,7 @@ def get_outstanding_reference_documents(args): ple = qb.DocType("Payment Ledger Entry") common_filter = [] + posting_and_due_date = [] # confirm that Supplier is not blocked if args.get("party_type") == "Supplier": @@ -1224,7 +1225,7 @@ def get_outstanding_reference_documents(args): condition += " and {0} between '{1}' and '{2}'".format( fieldname, args.get(date_fields[0]), args.get(date_fields[1]) ) - common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])]) + posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])]) if args.get("company"): condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) @@ -1235,6 +1236,7 @@ def get_outstanding_reference_documents(args): args.get("party"), args.get("party_account"), common_filter=common_filter, + posting_date=posting_and_due_date, min_outstanding=args.get("outstanding_amt_greater_than"), max_outstanding=args.get("outstanding_amt_less_than"), ) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 5ed34d34a32a..601fc87a227a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -22,6 +22,7 @@ class PaymentReconciliation(Document): def __init__(self, *args, **kwargs): super(PaymentReconciliation, self).__init__(*args, **kwargs) self.common_filter_conditions = [] + self.ple_posting_date_filter = [] @frappe.whitelist() def get_unreconciled_entries(self): @@ -150,6 +151,7 @@ def get_dr_or_cr_notes(self): return_outstanding = ple_query.get_voucher_outstandings( vouchers=return_invoices, common_filter=self.common_filter_conditions, + posting_date=self.ple_posting_date_filter, min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None, max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None, get_payments=True, @@ -187,6 +189,7 @@ def get_invoice_entries(self): self.party, self.receivable_payable_account, common_filter=self.common_filter_conditions, + posting_date=self.ple_posting_date_filter, min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None, max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None, ) @@ -350,6 +353,7 @@ def validate_allocation(self): def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False): self.common_filter_conditions.clear() + self.ple_posting_date_filter.clear() ple = qb.DocType("Payment Ledger Entry") self.common_filter_conditions.append(ple.company == self.company) @@ -359,15 +363,15 @@ def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=Fal if get_invoices: if self.from_invoice_date: - self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date)) + self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date)) if self.to_invoice_date: - self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date)) + self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date)) elif get_return_invoices: if self.from_payment_date: - self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date)) + self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date)) if self.to_payment_date: - self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date)) + self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date)) def get_conditions(self, get_payments=False): condition = " and company = '{0}' ".format(self.company) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9dafef74f4a4..018e8f9301a2 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -823,7 +823,13 @@ def get_held_invoices(party_type, party): def get_outstanding_invoices( - party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None + party_type, + party, + account, + common_filter=None, + posting_date=None, + min_outstanding=None, + max_outstanding=None, ): ple = qb.DocType("Payment Ledger Entry") @@ -850,6 +856,7 @@ def get_outstanding_invoices( ple_query = QueryPaymentLedger() invoice_list = ple_query.get_voucher_outstandings( common_filter=common_filter, + posting_date=posting_date, min_outstanding=min_outstanding, max_outstanding=max_outstanding, get_invoices=True, @@ -1501,6 +1508,7 @@ def __init__(self): # query filters self.vouchers = [] self.common_filter = [] + self.voucher_posting_date = [] self.min_outstanding = None self.max_outstanding = None @@ -1571,6 +1579,7 @@ def query_for_outstanding(self): .where(ple.delinked == 0) .where(Criterion.all(filter_on_voucher_no)) .where(Criterion.all(self.common_filter)) + .where(Criterion.all(self.voucher_posting_date)) .groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party) ) @@ -1652,6 +1661,7 @@ def get_voucher_outstandings( self, vouchers=None, common_filter=None, + posting_date=None, min_outstanding=None, max_outstanding=None, get_payments=False, @@ -1671,6 +1681,7 @@ def get_voucher_outstandings( self.reset() self.vouchers = vouchers self.common_filter = common_filter or [] + self.voucher_posting_date = posting_date or [] self.min_outstanding = min_outstanding self.max_outstanding = max_outstanding self.get_payments = get_payments From 80bf47170f222c852c0efb3131d32bb10520462f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 29 Jul 2022 15:05:01 +0530 Subject: [PATCH 012/898] test: posting_date should not affect outstanding amount calculation (cherry picked from commit ef312b8fc46355197a9a9502dd2ade697635fe77) --- .../test_payment_reconciliation.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 625382a3e948..dae029b4084a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -283,6 +283,41 @@ def test_filter_posting_date(self): self.assertEqual(len(pr.get("invoices")), 2) self.assertEqual(len(pr.get("payments")), 2) + def test_filter_posting_date_case2(self): + """ + Posting date should not affect outstanding amount calculation + """ + + from_date = add_days(nowdate(), -30) + to_date = nowdate() + self.create_payment_entry(amount=25, posting_date=from_date).submit() + self.create_sales_invoice(rate=25, qty=1, posting_date=to_date) + + pr = self.create_payment_reconciliation() + pr.from_invoice_date = pr.from_payment_date = from_date + pr.to_invoice_date = pr.to_payment_date = to_date + 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.reconcile() + + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + pr.from_invoice_date = pr.from_payment_date = to_date + pr.to_invoice_date = pr.to_payment_date = to_date + + pr.get_unreconciled_entries() + + self.assertEqual(len(pr.invoices), 0) + def test_filter_invoice_limit(self): # check filter condition - invoice limit transaction_date = nowdate() From 65bb1d8cc2b25f161fe2a02e3e41047648fa78b9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Jul 2022 07:11:16 +0530 Subject: [PATCH 013/898] fix: add asset repair to accounting dimension list (cherry picked from commit 452584c4bd512f1eb0e80acbab5fd1dc0982fc9c) --- erpnext/hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index aa10e3174491..c4f0c59c3869 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -507,6 +507,7 @@ "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", + "Asset Repair", "Loyalty Program", "Stock Reconciliation", "POS Profile", From 04bdff736b3ae999282b0b11857da1584cd3180e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 4 Aug 2022 16:48:03 +0530 Subject: [PATCH 014/898] chore: patch for creating existing dimensions in asset repair (cherry picked from commit 80f508c4b12ddfead304674a7eea0f8a7cadbc19) --- erpnext/patches.txt | 1 + ..._accounting_dimensions_for_asset_repair.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1d5f5d7f9987..c7dc27e2944e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -268,6 +268,7 @@ erpnext.patches.v13_0.enable_ksa_vat_docs #1 erpnext.patches.v13_0.show_india_localisation_deprecation_warning 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 [post_model_sync] execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py b/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py new file mode 100644 index 000000000000..61a5c86386c9 --- /dev/null +++ b/erpnext/patches/v13_0/create_accounting_dimensions_for_asset_repair.py @@ -0,0 +1,29 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + accounting_dimensions = frappe.db.get_all( + "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"] + ) + + if not accounting_dimensions: + return + + for d in accounting_dimensions: + doctype = "Asset Repair" + 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) From d7db8ed12ef8b1c394b4d81ba6f7c89eeb737db9 Mon Sep 17 00:00:00 2001 From: Akash Krishna Date: Tue, 9 Aug 2022 17:41:02 +0530 Subject: [PATCH 015/898] Tds report (#31801) * fix: TDS Computation Summary Report not loading, too many values to unpack (cherry picked from commit 32b30bc5de0347a0c5614ccb7b05262e5877cbc1) --- .../report/tds_computation_summary/tds_computation_summary.py | 4 ++-- .../report/tds_payable_monthly/tds_payable_monthly.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index 08d200868232..db3d5d44a0c8 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -14,9 +14,9 @@ def execute(filters=None): filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name") columns = get_columns(filters) - tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) + tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters) - res = get_result(filters, tds_docs, tds_accounts, tax_category_map) + res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map) final_result = group_by_supplier_and_category(res) return columns, final_result diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 16e0ac1de603..f2809a99c554 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -26,7 +26,6 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ supplier_map = get_supplier_pan_map() tax_rate_map = get_tax_rate_map(filters) gle_map = get_gle_map(tds_docs) - print(journal_entry_party_map) out = [] for name, details in gle_map.items(): From c3fd802351093baed37ce0c393d86fe574c88bf9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Aug 2022 18:51:27 +0530 Subject: [PATCH 016/898] refactor: use browser native lazy loading (backport #31814) (#31815) refactor: use browser native lazy loading (#31814) (cherry picked from commit 08d7c48dc762610360873cc516adf9ffd62430f7) Co-authored-by: Ankush Menat --- .../doctype/homepage_section/test_homepage_section.py | 6 +++++- erpnext/templates/includes/macros.html | 2 +- erpnext/templates/pages/home.html | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py index 27b0169c5959..27c8fe4c95ac 100644 --- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py @@ -57,7 +57,11 @@ def test_homepage_section_card(self): self.assertEqual(cards[0].h5.text, "Card 1") self.assertEqual(cards[0].a["href"], "/card-1") self.assertEqual(cards[1].p.text, "Subtitle 2") - self.assertEqual(cards[1].find(class_="website-image-lazy")["data-src"], "test.jpg") + + img = cards[1].find(class_="card-img-top") + + self.assertEqual(img["src"], "test.jpg") + self.assertEqual(img["loading"], "lazy") # cleanup frappe.db.rollback() diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index 3e20e50c3d81..f56dc3a45497 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -46,7 +46,7 @@

{{ section.name }}

{% if card.image %} -
+ {{ card.title }} {% endif %}
{{ card.title }}
diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html index 4c69b8388ded..27d966ad42e1 100644 --- a/erpnext/templates/pages/home.html +++ b/erpnext/templates/pages/home.html @@ -37,7 +37,7 @@

{{ _('Products') }}

{% for item in homepage.products %}
-
+ {{ item.item_name }}
{{ item.item_name }}
{{ _('More details') }} From 735a60807ae1f540056330a19723f6ce2d5ec15b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 9 Aug 2022 19:06:57 +0530 Subject: [PATCH 017/898] fix: limited options for no-of-employees in crm (cherry picked from commit 7ecd67605f660523cec6122da83dbc2515646d0c) --- erpnext/crm/doctype/lead/lead.json | 6 +++--- erpnext/crm/doctype/opportunity/opportunity.json | 4 ++-- erpnext/crm/doctype/prospect/prospect.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index c946ae499946..99c00ad6e693 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -340,8 +340,8 @@ "fieldname": "no_of_employees", "fieldtype": "Select", "label": "No of Employees", - "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+" - }, + "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+" + }, { "fieldname": "column_break_22", "fieldtype": "Column Break" @@ -514,7 +514,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2022-07-22 15:55:03.176094", + "modified": "2022-08-09 18:26:17.101521", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 68a2156981f6..fed0c7c79a20 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -463,7 +463,7 @@ "fieldname": "no_of_employees", "fieldtype": "Select", "label": "No of Employees", - "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+" + "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+" }, { "fieldname": "annual_revenue", @@ -622,7 +622,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2022-07-22 18:46:32.858696", + "modified": "2022-08-09 18:26:37.235964", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 7f33c08c132c..820a6c72ea10 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -82,7 +82,7 @@ "fieldname": "no_of_employees", "fieldtype": "Select", "label": "No. of Employees", - "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+" + "options": "1-10\n11-50\n51-200\n201-500\n501-1000\n1000+" }, { "fieldname": "annual_revenue", @@ -218,7 +218,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-06-22 15:10:26.887502", + "modified": "2022-08-09 18:26:56.950185", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", From 35e9bfca388b3c8f50e5e65d357dd735822bba8b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 9 Aug 2022 19:28:26 +0530 Subject: [PATCH 018/898] fix: map old data as per new options of no-of-employees (cherry picked from commit 909945c0ac214b3ad613ae944509e8bdec47c940) --- erpnext/patches.txt | 3 ++- .../patches/v14_0/fix_crm_no_of_employees.py | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v14_0/fix_crm_no_of_employees.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c7dc27e2944e..e5beacde2b4b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,4 +309,5 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.crm_ux_cleanup erpnext.patches.v14_0.remove_india_localisation # 14-07-2022 erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation -erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 \ No newline at end of file +erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 +erpnext.patches.v14_0.fix_crm_no_of_employees \ No newline at end of file diff --git a/erpnext/patches/v14_0/fix_crm_no_of_employees.py b/erpnext/patches/v14_0/fix_crm_no_of_employees.py new file mode 100644 index 000000000000..268eb95732ea --- /dev/null +++ b/erpnext/patches/v14_0/fix_crm_no_of_employees.py @@ -0,0 +1,26 @@ +import frappe + + +def execute(): + options = { + "11-20": "11-50", + "21-30": "11-50", + "31-100": "51-200", + "101-500": "201-500", + "500-1000": "501-1000", + ">1000": "1000+", + } + + for doctype in ("Lead", "Opportunity", "Prospect"): + frappe.reload_doctype(doctype) + for key, value in options.items(): + frappe.db.sql( + """ + update `tab{doctype}` + set no_of_employees = %s + where no_of_employees = %s + """.format( + doctype=doctype + ), + (value, key), + ) From 66e5202642dcdf8fd80571f0b05d8076b7c0c0f9 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Mon, 8 Aug 2022 16:29:13 +0530 Subject: [PATCH 019/898] fix: process loan interest accrual (cherry picked from commit 9ef8d5c5c314ec03f46e982ce8aa36487dbb7559) --- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 0aeb44891842..730d33752ee2 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -135,7 +135,11 @@ def calculate_accrual_amount_for_demand_loans( def make_accrual_interest_entry_for_demand_loans( posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular" ): - query_filters = {"status": ("in", ["Disbursed", "Partially Disbursed"]), "docstatus": 1} + query_filters = { + "status": ("in", ["Disbursed", "Partially Disbursed"]), + "docstatus": 1, + "is_term_loan": 0, + } if loan_type: query_filters.update({"loan_type": loan_type}) From 8fdbbf374df6e8b00b8d11c7812d28a719fb394f Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Mon, 8 Aug 2022 17:35:31 +0530 Subject: [PATCH 020/898] fix: term loan interest calculation (cherry picked from commit 534d7ce64b47c6c9272f7bedf79f4cdce825e0f2) --- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 730d33752ee2..4978f1fcb89b 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -233,6 +233,7 @@ def get_term_loans(date, term_loan=None, loan_type=None): AND l.is_term_loan =1 AND rs.payment_date <= %s AND rs.is_accrued=0 {0} + AND rs.interest_amount > 0 AND l.status = 'Disbursed' ORDER BY rs.payment_date""".format( condition From 1aa96defda1c34b99e3362692d2936c78d76a195 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 11 Aug 2022 19:31:31 +0530 Subject: [PATCH 021/898] fix: Unable to make payment entry against Fees using education app (cherry picked from commit 79ac50d0f7503cc053e7e58a382d81a705b7afbf) --- .../doctype/payment_entry/payment_entry.py | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index af5a5e249d2b..94b6ecb66e6f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -181,7 +181,11 @@ def set_missing_values(self): frappe.throw(_("Party is mandatory")) _party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name" - self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name) + + if frappe.db.has_column(self.party_type, _party_name): + self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name) + else: + self.party_name = frappe.db.get_value(self.party_type, self.party, "name") if self.party: if not self.party_balance: @@ -295,6 +299,9 @@ def validate_mandatory(self): def validate_reference_documents(self): valid_reference_doctypes = self.get_valid_reference_doctypes() + if not valid_reference_doctypes: + return + for d in self.get("references"): if not d.allocated_amount: continue @@ -362,7 +369,7 @@ def validate_paid_invoices(self): if not d.allocated_amount: continue - if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"): + if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"): outstanding_amount, is_return = frappe.get_cached_value( d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"] ) @@ -1200,7 +1207,7 @@ def get_outstanding_reference_documents(args): party_account_currency = get_account_currency(args.get("party_account")) company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency") - # Get positive outstanding sales /purchase invoices/ Fees + # Get positive outstanding sales /purchase invoices condition = "" if args.get("voucher_type") and args.get("voucher_no"): condition = " and voucher_type={0} and voucher_no={1}".format( @@ -1595,10 +1602,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre elif reference_doctype != "Journal Entry": if not total_amount: if party_account_currency == company_currency: - total_amount = ref_doc.base_grand_total + # for handling cases that don't have multi-currency (base field) + total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total") exchange_rate = 1 else: - total_amount = ref_doc.grand_total + total_amount = ref_doc.get("grand_total") if not exchange_rate: # Get the exchange rate from the original ref doc # or get it based on the posting date of the ref doc. @@ -1609,7 +1617,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre if reference_doctype in ("Sales Invoice", "Purchase Invoice"): outstanding_amount = ref_doc.get("outstanding_amount") else: - outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid) + outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid")) else: # Get the exchange rate based on the posting date of the ref doc. @@ -1627,16 +1635,23 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre @frappe.whitelist() -def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None): +def get_payment_entry( + dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None +): reference_doc = None doc = frappe.get_doc(dt, dn) if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0: frappe.throw(_("Can only make payment against unbilled {0}").format(dt)) - party_type = set_party_type(dt) + if not party_type: + party_type = set_party_type(dt) + party_account = set_party_account(dt, dn, doc, party_type) party_account_currency = set_party_account_currency(dt, party_account, doc) - payment_type = set_payment_type(dt, doc) + + if not payment_type: + payment_type = set_payment_type(dt, doc) + grand_total, outstanding_amount = set_grand_total_and_outstanding_amount( party_amount, dt, party_account_currency, doc ) @@ -1786,8 +1801,6 @@ def set_party_account(dt, dn, doc, party_type): party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to elif dt == "Purchase Invoice": party_account = doc.credit_to - elif dt == "Fees": - party_account = doc.receivable_account else: party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company) return party_account @@ -1803,8 +1816,7 @@ def set_party_account_currency(dt, party_account, doc): def set_payment_type(dt, doc): if ( - dt == "Sales Order" - or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0) + dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0) ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0): payment_type = "Receive" else: @@ -1822,18 +1834,15 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre else: grand_total = doc.rounded_total or doc.grand_total outstanding_amount = doc.outstanding_amount - elif dt == "Fees": - grand_total = doc.grand_total - outstanding_amount = doc.outstanding_amount elif dt == "Dunning": grand_total = doc.grand_total outstanding_amount = doc.grand_total else: if party_account_currency == doc.company_currency: - grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total) + grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total")) else: - grand_total = flt(doc.get("rounded_total") or doc.grand_total) - outstanding_amount = grand_total - flt(doc.advance_paid) + grand_total = flt(doc.get("rounded_total") or doc.get("grand_total")) + outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid)) return grand_total, outstanding_amount From 82f1dd268d47c5baebc34770e339cdc1f25a9eb2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:00:54 +0530 Subject: [PATCH 022/898] fix: check item_code in all rows of po_items (backport #31741) (#31842) fix: check item_code in all rows of po_items (#31741) fix: check the item code in each row of PO items (cherry picked from commit 0047e18a9b121e45798f7b7a6345feef85ee7c01) Co-authored-by: Sagar Sharma --- .../manufacturing/doctype/production_plan/production_plan.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 70ccb7827850..46e820542b97 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -639,6 +639,9 @@ def get_sub_assembly_items(self, manufacturing_type=None): sub_assembly_items_store = [] # temporary store to process all subassembly items for row in self.po_items: + if not row.item_code: + frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx)) + bom_data = [] get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) From 74664a34c0b24b71aeb9169f51b39f01f1791af2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 12:19:59 +0530 Subject: [PATCH 023/898] fix: contact search in request for quotation (backport #31828) (#31840) fix: contact search in request for quotation (#31828) (cherry picked from commit e5e88bb9f1728ce85aecda7a305dd70378c5099e) Co-authored-by: Sagar Sharma --- .../request_for_quotation/request_for_quotation.js | 9 ++++++--- .../request_for_quotation/request_for_quotation.py | 12 ------------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 4e29ee53eaf0..31a4837d463f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -15,9 +15,12 @@ frappe.ui.form.on("Request for Quotation",{ frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) { let d = locals[cdt][cdn]; return { - query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts", - filters: {'supplier': d.supplier} - } + query: "frappe.contacts.doctype.contact.contact.contact_query", + filters: { + link_doctype: "Supplier", + link_name: d.supplier || "" + } + }; } }, diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 3ef57bb70ffa..ee28eb6ce2d4 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -286,18 +286,6 @@ def get_list_context(context=None): return list_context -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql( - """select `tabContact`.name from `tabContact`, `tabDynamic Link` - where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s - and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent - limit %(page_len)s offset %(start)s""", - {"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")}, - ) - - @frappe.whitelist() def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None): def postprocess(source, target_doc): From 010a0ca0a9cecae9341b74e8e31dcde151d9aac1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:46:33 +0530 Subject: [PATCH 024/898] fix: incorrect produced-qty in production-plan-item (backport #31706) (#31861) fix: incorrect produced-qty in production-plan-item (#31706) (cherry picked from commit 538cd6fdcf2cc627515b7effd0e98f3538a7a11a) Co-authored-by: Sagar Sharma --- .../production_plan/production_plan.py | 1 - .../production_plan/test_production_plan.py | 60 ++++++++++++++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 46e820542b97..2cdf8d3ea9ff 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -482,7 +482,6 @@ def prepare_data_for_sub_assembly_items(self, row, wo_data): "bom_no", "stock_uom", "bom_level", - "production_plan_item", "schedule_date", ]: if row.get(field): diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 040e791e00a1..e2415ad848e5 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -11,8 +11,9 @@ get_warehouse_list, ) from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError +from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, @@ -583,9 +584,6 @@ def test_production_plan_pending_qty_with_sales_order(self): Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel) """ from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record - from erpnext.manufacturing.doctype.work_order.work_order import ( - make_stock_entry as make_se_from_wo, - ) make_stock_entry( item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 @@ -629,9 +627,6 @@ def test_production_plan_pending_qty_with_sales_order(self): def test_production_plan_pending_qty_independent_items(self): "Test Prod Plan impact if items are added independently (no from SO or MR)." from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record - from erpnext.manufacturing.doctype.work_order.work_order import ( - make_stock_entry as make_se_from_wo, - ) make_stock_entry( item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 @@ -728,6 +723,57 @@ def test_temporary_name_relinking(self): for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items): self.assertEqual(po_item.name, subassy_item.production_plan_item) + def test_produced_qty_for_multi_level_bom_item(self): + # Create Items and BOMs + rm_item = make_item(properties={"is_stock_item": 1}).name + sub_assembly_item = make_item(properties={"is_stock_item": 1}).name + fg_item = make_item(properties={"is_stock_item": 1}).name + + make_stock_entry( + item_code=rm_item, + qty=60, + to_warehouse="Work In Progress - _TC", + rate=99, + purpose="Material Receipt", + ) + + make_bom(item=sub_assembly_item, raw_materials=[rm_item], rm_qty=3) + make_bom(item=fg_item, raw_materials=[sub_assembly_item], rm_qty=4) + + # Step - 1: Create Production Plan + pln = create_production_plan(item_code=fg_item, planned_qty=5, skip_getting_mr_items=1) + pln.get_sub_assembly_items() + + # Step - 2: Create Work Orders + pln.make_work_order() + work_orders = frappe.get_all("Work Order", filters={"production_plan": pln.name}, pluck="name") + sa_wo = fg_wo = None + for work_order in work_orders: + wo_doc = frappe.get_doc("Work Order", work_order) + if wo_doc.production_plan_item: + wo_doc.update( + {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"} + ) + fg_wo = wo_doc.name + else: + wo_doc.update( + {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Work In Progress - _TC"} + ) + sa_wo = wo_doc.name + wo_doc.submit() + + # Step - 3: Complete Work Orders + se = frappe.get_doc(make_se_from_wo(sa_wo, "Manufacture")) + se.submit() + + se = frappe.get_doc(make_se_from_wo(fg_wo, "Manufacture")) + se.submit() + + # Step - 4: Check Production Plan Item Produced Qty + pln.load_from_db() + self.assertEqual(pln.status, "Completed") + self.assertEqual(pln.po_items[0].produced_qty, 5) + def create_production_plan(**args): """ From abe18945a60594b01f926b2073b736a56ab043fc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:37:40 +0530 Subject: [PATCH 025/898] fix: incorrect rate in BOM exploded items (backport #31513) (#31864) fix: incorrect rate in BOM exploded items (#31513) (cherry picked from commit 313625c3497d7db2d86a2835c458ac1028c27fe8) Co-authored-by: Sagar Sharma --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/manufacturing/doctype/bom/test_bom.py | 28 +++++++++++++++++++ .../doctype/bom_item/bom_item.json | 3 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index b29f6710e174..70637d3ef206 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -189,8 +189,8 @@ def validate(self): self.validate_transfer_against() self.set_routing_operations() self.validate_operations() - self.update_exploded_items(save=False) self.calculate_cost() + self.update_exploded_items(save=False) self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False) self.validate_scrap_items() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index a190cc7430b9..27f3cc905b96 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -611,6 +611,34 @@ def test_set_default_bom_for_item_having_single_bom(self): bom.reload() self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) + def test_exploded_items_rate(self): + rm_item = make_item( + properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} + ).name + fg_item = make_item(properties={"is_stock_item": 1}).name + + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True) + + bom.rm_cost_as_per = "Last Purchase Rate" + bom.save() + self.assertEqual(bom.items[0].base_rate, 89) + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + + bom.rm_cost_as_per = "Price List" + bom.save() + self.assertEqual(bom.items[0].base_rate, 0.0) + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + + bom.rm_cost_as_per = "Valuation Rate" + bom.save() + self.assertEqual(bom.items[0].base_rate, 99) + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + + bom.submit() + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 0a8ae7b4a73c..c5266119dc20 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -184,6 +184,7 @@ "in_list_view": 1, "label": "Rate", "options": "currency", + "read_only": 1, "reqd": 1 }, { @@ -288,7 +289,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-05-19 02:32:43.785470", + "modified": "2022-07-28 10:20:51.559010", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", From ce5fc5b457b6168ce6d1c591d808a9d3903e210f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Aug 2022 13:48:56 +0530 Subject: [PATCH 026/898] fix: not able to issue expired batches (cherry picked from commit 795c94384a5dd8919b0dc989e19d8b9ee81071d4) --- erpnext/controllers/stock_controller.py | 14 +++++++++- erpnext/stock/doctype/batch/test_batch.py | 8 +++++- .../doctype/stock_entry/test_stock_entry.py | 27 ++++++++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index e27718a9b4cd..f9fc5f60bbc6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -36,6 +36,10 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError): pass +class BatchExpiredError(frappe.ValidationError): + pass + + class StockController(AccountsController): def validate(self): super(StockController, self).validate() @@ -77,6 +81,10 @@ def make_gl_entries(self, gl_entries=None, from_repost=False): def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + is_material_issue = False + if self.doctype == "Stock Entry" and self.purpose == "Material Issue": + is_material_issue = True + for d in self.get("items"): if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no: serial_nos = frappe.get_all( @@ -93,6 +101,9 @@ def validate_serialized_batch(self): ) ) + if is_material_issue: + continue + if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") @@ -100,7 +111,8 @@ def validate_serialized_batch(self): frappe.throw( _("Row #{0}: The batch {1} has already expired.").format( d.idx, get_link_to_form("Batch", d.get("batch_no")) - ) + ), + BatchExpiredError, ) def clean_serial_nos(self): diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 3e470d4ce4e3..271e2e029847 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -473,7 +473,13 @@ def make_new_batch(**args): "doctype": "Batch", "batch_id": args.batch_id, "item": args.item_code, + "expiry_date": args.expiry_date, } - ).insert() + ) + + if args.expiry_date: + batch.expiry_date = args.expiry_date + + batch.insert() return batch diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index a2f997867003..b574b718fe1c 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,7 +5,7 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowdate, nowtime +from frappe.utils import add_days, flt, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -1589,6 +1589,31 @@ def test_reposting_for_depedent_warehouse(self): self.assertEqual(obj.items[index].basic_rate, 200) self.assertEqual(obj.items[index].basic_amount, 2000) + def test_batch_expiry(self): + from erpnext.controllers.stock_controller import BatchExpiredError + from erpnext.stock.doctype.batch.test_batch import make_new_batch + + item_code = "Test Batch Expiry Test Item - 001" + item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10) + + item_doc.has_batch_no = 1 + item_doc.save() + + batch = make_new_batch( + batch_id=frappe.generate_hash("", 5), item_code=item_doc.name, expiry_date=add_days(today(), -1) + ) + + se = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=4, + to_warehouse="_Test Warehouse - _TC", + batch_no=batch.name, + do_not_save=True, + ) + + self.assertRaises(BatchExpiredError, se.save) + def make_serialized_item(**args): args = frappe._dict(args) From 354a9d616960e68d6d79b50568d03f55a1f0d85e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Aug 2022 11:56:13 +0530 Subject: [PATCH 027/898] fix: delete custom fields on deletion of inventory dimension (cherry picked from commit 0b39a0123e2e696007bbe196dad490d4534be610) --- erpnext/controllers/stock_controller.py | 17 +++- .../inventory_dimension.js | 31 +++++++- .../inventory_dimension.json | 15 +++- .../inventory_dimension.py | 78 ++++++++++++++----- .../test_inventory_dimension.py | 27 +++++++ 5 files changed, 143 insertions(+), 25 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f9fc5f60bbc6..49f85cdba574 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -386,9 +386,24 @@ def get_sl_entries(self, d, args): def update_inventory_dimensions(self, row, sl_dict) -> None: dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self) for dimension in dimensions: - if dimension and row.get(dimension.source_fieldname): + if not dimension: + continue + + if row.get(dimension.source_fieldname): sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname) + if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent: + sl_dict[dimension.target_fieldname] = self.get(dimension.fetch_from_parent) + + # Get value based on doctype name + if not sl_dict.get(dimension.target_fieldname): + fieldname = frappe.get_cached_value( + "DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" + ) + + if fieldname and self.get(fieldname): + sl_dict[dimension.target_fieldname] = self.get(fieldname) + def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): from erpnext.stock.stock_ledger import make_sl_entries diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js index 91a21f4e7225..07cb73b1d56b 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js @@ -35,14 +35,39 @@ frappe.ui.form.on('Inventory Dimension', { refresh(frm) { if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger && frm.doc.__onload.has_stock_ledger.length) { - let msg = __('Stock transactions exists against this dimension, user can not update document.'); - frm.dashboard.add_comment(msg, 'blue', true); + let allow_to_edit_fields = ['disabled', 'fetch_from_parent', + 'type_of_transaction', 'condition']; frm.fields.forEach((field) => { - if (field.df.fieldname !== 'disabled') { + if (!in_list(allow_to_edit_fields, field.df.fieldname)) { frm.set_df_property(field.df.fieldname, "read_only", "1"); } }); } + + if (!frm.is_new()) { + frm.add_custom_button(__('Delete Dimension'), () => { + frm.trigger('delete_dimension'); + }); + } + }, + + delete_dimension(frm) { + let msg = (` + Custom fields related to this dimension will be deleted on deletion of dimension. +
Do you want to delete {0} dimension? + `); + + frappe.confirm(__(msg, [frm.doc.name.bold()]), () => { + frappe.call({ + method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension', + args: { + dimension: frm.doc.name + }, + callback: function() { + frappe.set_route('List', 'Inventory Dimension'); + } + }); + }); } }); diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index 8b334d13d7d0..03e7fda84117 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "autoname": "field:dimension_name", "creation": "2022-06-17 13:04:16.554051", "doctype": "DocType", @@ -22,6 +21,7 @@ "document_type", "istable", "type_of_transaction", + "fetch_from_parent", "column_break_16", "condition", "applicable_condition_example_section", @@ -101,12 +101,14 @@ "fieldname": "target_fieldname", "fieldtype": "Data", "label": "Target Fieldname (Stock Ledger Entry)", + "no_copy": 1, "read_only": 1 }, { "fieldname": "source_fieldname", "fieldtype": "Data", "label": "Source Fieldname", + "no_copy": 1, "read_only": 1 }, { @@ -123,7 +125,7 @@ "fieldname": "type_of_transaction", "fieldtype": "Select", "label": "Type of Transaction", - "options": "\nInward\nOutward" + "options": "\nInward\nOutward\nBoth" }, { "fieldname": "html_19", @@ -140,11 +142,18 @@ { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "depends_on": "istable", + "description": "Set fieldname or DocType name like Supplier, Customer etc.", + "fieldname": "fetch_from_parent", + "fieldtype": "Data", + "label": "Fetch Value From Parent Form" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-07-19 21:06:11.824976", + "modified": "2022-08-17 11:43:24.722441", "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 5a9541f060ab..4ff8f33b4095 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -43,13 +43,37 @@ def do_not_update_document(self): return old_doc = self._doc_before_save + allow_to_edit_fields = [ + "disabled", + "fetch_from_parent", + "type_of_transaction", + "condition", + ] + for field in frappe.get_meta("Inventory Dimension").fields: - if field.fieldname != "disabled" and old_doc.get(field.fieldname) != self.get(field.fieldname): + if field.fieldname not in allow_to_edit_fields and old_doc.get(field.fieldname) != self.get( + field.fieldname + ): msg = f"""The user can not change value of the field {bold(field.label)} because stock transactions exists against the dimension {bold(self.name)}.""" frappe.throw(_(msg), DoNotChangeError) + def on_trash(self): + self.delete_custom_fields() + + def delete_custom_fields(self): + filters = {"fieldname": self.source_fieldname} + + if self.document_type: + filters["dt"] = self.document_type + + for field in frappe.get_all("Custom Field", filters=filters): + frappe.delete_doc("Custom Field", field.name) + + msg = f"Deleted custom fields related to the dimension {self.name}" + frappe.msgprint(_(msg)) + def reset_value(self): if self.apply_to_all_doctypes: self.istable = 0 @@ -76,30 +100,35 @@ def on_update(self): self.add_custom_fields() def add_custom_fields(self): - dimension_field = dict( - fieldname=self.source_fieldname, - fieldtype="Link", - insert_after="warehouse", - options=self.reference_document, - label=self.dimension_name, - ) + dimension_fields = [ + dict( + fieldname="inventory_dimension", + fieldtype="Section Break", + insert_after="warehouse", + label="Inventory Dimension", + collapsible=1, + ), + dict( + fieldname=self.source_fieldname, + fieldtype="Link", + insert_after="inventory_dimension", + options=self.reference_document, + label=self.dimension_name, + ), + ] custom_fields = {} if self.apply_to_all_doctypes: for doctype in get_inventory_documents(): - if not frappe.db.get_value( - "Custom Field", {"dt": doctype[0], "fieldname": self.source_fieldname} - ): - custom_fields.setdefault(doctype[0], dimension_field) - elif not frappe.db.get_value( - "Custom Field", {"dt": self.document_type, "fieldname": self.source_fieldname} - ): - custom_fields.setdefault(self.document_type, dimension_field) + custom_fields.setdefault(doctype[0], dimension_fields) + else: + custom_fields.setdefault(self.document_type, dimension_fields) if not frappe.db.get_value( "Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname} ): + dimension_field = dimension_fields[1] dimension_field["fieldname"] = self.target_fieldname custom_fields["Stock Ledger Entry"] = dimension_field @@ -143,7 +172,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None): elif ( row.type_of_transaction == "Outward" if doc.docstatus == 1 - else row.type_of_transaction != "Inward" + else row.type_of_transaction != "Outward" ) and sl_dict.actual_qty > 0: continue @@ -166,7 +195,14 @@ def get_document_wise_inventory_dimensions(doctype) -> dict: if not frappe.local.document_wise_inventory_dimensions.get(doctype): dimensions = frappe.get_all( "Inventory Dimension", - fields=["name", "source_fieldname", "condition", "target_fieldname", "type_of_transaction"], + fields=[ + "name", + "source_fieldname", + "condition", + "target_fieldname", + "type_of_transaction", + "fetch_from_parent", + ], filters={"disabled": 0}, or_filters={"document_type": doctype, "apply_to_all_doctypes": 1}, ) @@ -194,3 +230,9 @@ def get_inventory_dimensions(): frappe.local.inventory_dimensions = dimensions return frappe.local.inventory_dimensions + + +@frappe.whitelist() +def delete_dimension(dimension): + doc = frappe.get_doc("Inventory Dimension", dimension) + doc.delete() diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 998a0e9d6a6d..cc90b74ee85f 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -8,6 +8,7 @@ CanNotBeChildDoc, CanNotBeDefaultDimension, DoNotChangeError, + delete_dimension, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -42,6 +43,32 @@ def test_validate_inventory_dimension(self): self.assertRaises(CanNotBeDefaultDimension, inv_dim1.insert) + def test_delete_inventory_dimension(self): + inv_dim1 = create_inventory_dimension( + reference_document="Shelf", + type_of_transaction="Outward", + dimension_name="From Shelf", + apply_to_all_doctypes=0, + document_type="Stock Entry Detail", + condition="parent.purpose == 'Material Issue'", + ) + + inv_dim1.save() + + custom_field = frappe.db.get_value( + "Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name" + ) + + self.assertTrue(custom_field) + + delete_dimension(inv_dim1.name) + + custom_field = frappe.db.get_value( + "Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name" + ) + + self.assertFalse(custom_field) + def test_inventory_dimension(self): warehouse = "Shelf Warehouse - _TC" item_code = "_Test Item" From aa5aaa113eaecc7b0ae9df1bb1d4ce20e7ce18a4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:31:18 +0530 Subject: [PATCH 028/898] fix: Make expense account editable in Subcontracting Receipt Item (backport #31848) (#31870) fix: Make expense account editable in Subcontracting Receipt Item (#31848) (cherry picked from commit 2d04e7141271eaded41d53bbc9a22b37fbd5816f) Co-authored-by: Sagar Sharma --- erpnext/controllers/stock_controller.py | 8 +++++++- .../subcontracting_receipt.js | 7 +++++++ .../subcontracting_receipt_item.json | 14 +++++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f9fc5f60bbc6..900e34e27f90 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -322,7 +322,13 @@ def check_expense_account(self, item): ) if ( self.doctype - not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") + not in ( + "Purchase Receipt", + "Purchase Invoice", + "Stock Reconciliation", + "Stock Entry", + "Subcontracting Receipt", + ) and not is_expense_account ): frappe.throw( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index b2506cd143d8..35fec8bc33c0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -48,6 +48,13 @@ frappe.ui.form.on('Subcontracting Receipt', { is_group: 0 } })); + + frm.set_query("expense_account", "items", function () { + return { + query: "erpnext.controllers.queries.get_expense_account", + filters: { 'company': frm.doc.company } + }; + }); }, refresh: (frm) => { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index e2785ce0cdd9..bf15f0f6882d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -49,11 +49,12 @@ "col_break5", "batch_no", "rejected_serial_no", - "expense_account", "manufacture_details", "manufacturer", "column_break_16", "manufacturer_part_no", + "accounting_details_section", + "expense_account", "accounting_dimensions_section", "project", "dimension_col_break", @@ -363,10 +364,8 @@ { "fieldname": "expense_account", "fieldtype": "Link", - "hidden": 1, "label": "Expense Account", - "options": "Account", - "read_only": 1 + "options": "Account" }, { "collapsible": 1, @@ -456,12 +455,17 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-04-21 12:07:55.899701", + "modified": "2022-08-15 11:34:00.970042", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 04d3571dd9989d2716da49174a028d2db86cd628 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 17:26:15 +0530 Subject: [PATCH 029/898] fix: Transit filter for Default Target Warehouse in SE (backport #31839) (#31873) fix: Transit filter for Default Target Warehouse in SE (#31839) (cherry picked from commit f1a612245c05aa56f7654694495d4ec5b9d3d8b9) Co-authored-by: Sagar Sharma --- .../stock/doctype/stock_entry/stock_entry.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 1c514a90eeee..e3a8438d95c2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -583,18 +583,23 @@ frappe.ui.form.on('Stock Entry', { }, add_to_transit: function(frm) { - if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') { - frm.set_value('to_warehouse', ''); + if(frm.doc.purpose=='Material Transfer') { + var filters = { + 'is_group': 0, + 'company': frm.doc.company + } + + if(frm.doc.add_to_transit){ + filters['warehouse_type'] = 'Transit'; + frm.set_value('to_warehouse', ''); + frm.trigger('set_transit_warehouse'); + } + frm.fields_dict.to_warehouse.get_query = function() { return { - filters:{ - 'warehouse_type' : 'Transit', - 'is_group': 0, - 'company': frm.doc.company - } + filters:filters }; }; - frm.trigger('set_transit_warehouse'); } }, From b637d4d5f167160b30ef3d97fbe8f82787a015f5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 18 Aug 2022 11:16:15 +0530 Subject: [PATCH 030/898] fix: Make expense account editable in Purchase Receipt Item (backport #31730) (#31878) fix: Make expense account editable in Purchase Receipt Item (#31730) Co-authored-by: Sagar Sharma (cherry picked from commit 1a6508972e3555aabd06d93a46af9cc513e32819) Co-authored-by: Deepesh Garg --- .../purchase_receipt_item/purchase_receipt_item.json | 6 ++---- 1 file changed, 2 insertions(+), 4 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 c97dbee91142..39833b5e9105 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -792,10 +792,8 @@ { "fieldname": "expense_account", "fieldtype": "Link", - "hidden": 1, "label": "Expense Account", - "options": "Account", - "read_only": 1 + "options": "Account" }, { "fieldname": "accounting_dimensions_section", @@ -1001,7 +999,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-06-17 05:32:16.483178", + "modified": "2022-07-28 19:27:54.880781", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 623f56a95cf7af12621efeba1cfcde9ae6276368 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 17 Aug 2022 18:21:43 +0530 Subject: [PATCH 031/898] fix(projects): Add missing comma Added with https://github.com/frappe/erpnext/pull/31360 (cherry picked from commit d38778e40086d31593d3d34f926d9aa4324c963c) --- erpnext/projects/doctype/project/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index e78e4b657787..a2be9367688b 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -379,7 +379,7 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters): {fcond} {mcond} order by (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end), - (case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end) + (case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end), idx desc, name, full_name limit %(page_len)s offset %(start)s""".format( From 50ad61245350879b564e203276a8c91a062a19bb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 18 Aug 2022 12:04:07 +0530 Subject: [PATCH 032/898] fix: Add dimension section in subcontracting doctypes (backport #31849) (#31877) * fix: Add dimension section in subcontracting doctypes (#31849) (cherry picked from commit 8704ca783d3f3280113d11444baa8acfeb78c2a3) # Conflicts: # erpnext/patches.txt * chore: conflicts Co-authored-by: Sagar Sharma --- erpnext/hooks.py | 4 ++ erpnext/patches.txt | 3 +- ...g_dimensions_in_subcontracting_doctypes.py | 47 +++++++++++++++++++ .../subcontracting_order.json | 28 ++++++++++- .../subcontracting_order_item.json | 28 ++++++++++- .../subcontracting_receipt.json | 28 ++++++++++- .../subcontracting_receipt_item.json | 6 +-- 7 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c4f0c59c3869..a08feb44476d 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -520,6 +520,10 @@ "Purchase Order", "Purchase Receipt", "Sales Order", + "Subcontracting Order", + "Subcontracting Order Item", + "Subcontracting Receipt", + "Subcontracting Receipt Item", ] # get matching queries for Bank Reconciliation diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c7dc27e2944e..6d1f37a9fafa 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,4 +309,5 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.crm_ux_cleanup erpnext.patches.v14_0.remove_india_localisation # 14-07-2022 erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation -erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 \ No newline at end of file +erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 +erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes \ No newline at end of file diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py new file mode 100644 index 000000000000..b349c07f6df2 --- /dev/null +++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py @@ -0,0 +1,47 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + accounting_dimensions = frappe.db.get_all( + "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"] + ) + + if not accounting_dimensions: + return + + count = 1 + for d in accounting_dimensions: + + if count % 2 == 0: + insert_after_field = "dimension_col_break" + else: + insert_after_field = "accounting_dimensions_section" + + for doctype in [ + "Subcontracting Order", + "Subcontracting Order Item", + "Subcontracting Receipt", + "Subcontracting Receipt Item", + ]: + + 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": insert_after_field, + } + + try: + create_custom_field(doctype, df, ignore_validate=True) + frappe.clear_cache(doctype=doctype) + except Exception: + pass + + count += 1 diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json index c6e76c76d762..f98f559d5cab 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json @@ -19,6 +19,10 @@ "transaction_date", "schedule_date", "amended_from", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", "address_and_contact_section", "supplier_address", "address_display", @@ -422,12 +426,34 @@ "fieldtype": "Select", "label": "Distribute Additional Costs Based On ", "options": "Qty\nAmount" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-04-11 21:02:44.097841", + "modified": "2022-08-15 14:08:49.204218", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index 291f47a6340c..3675a4ea08a3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -40,6 +40,10 @@ "manufacture_section", "manufacturer", "manufacturer_part_no", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", "section_break_34", "page_break" ], @@ -304,13 +308,35 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-04-11 21:28:06.585338", + "modified": "2022-08-15 14:25:45.177703", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index e9638144a797..94304865603f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -17,6 +17,10 @@ "posting_time", "is_return", "return_against", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", "section_addresses", "supplier_address", "contact_person", @@ -569,11 +573,33 @@ { "fieldname": "section_break_47", "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions " + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } ], "is_submittable": 1, "links": [], - "modified": "2022-04-18 13:15:12.011682", + "modified": "2022-08-15 14:30:29.447307", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index bf15f0f6882d..437fc41f5e43 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -56,9 +56,9 @@ "accounting_details_section", "expense_account", "accounting_dimensions_section", - "project", - "dimension_col_break", "cost_center", + "dimension_col_break", + "project", "section_break_80", "page_break" ], @@ -465,7 +465,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-08-15 11:34:00.970042", + "modified": "2022-08-15 14:51:10.613347", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 028e939cca2eadaa8bc5a057dddda5cd72a503b3 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 18 Aug 2022 08:30:58 +0000 Subject: [PATCH 033/898] chore(release): Bumped to Version 14.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [14.0.1](https://github.com/frappe/erpnext/compare/v14.0.0...v14.0.1) (2022-08-18) ### Bug Fixes * add asset repair to accounting dimension list ([65bb1d8](https://github.com/frappe/erpnext/commit/65bb1d8cc2b25f161fe2a02e3e41047648fa78b9)) * Add dimension section in subcontracting doctypes (backport [#31849](https://github.com/frappe/erpnext/issues/31849)) ([#31877](https://github.com/frappe/erpnext/issues/31877)) ([50ad612](https://github.com/frappe/erpnext/commit/50ad61245350879b564e203276a8c91a062a19bb)) * check item_code in all rows of po_items (backport [#31741](https://github.com/frappe/erpnext/issues/31741)) ([#31842](https://github.com/frappe/erpnext/issues/31842)) ([82f1dd2](https://github.com/frappe/erpnext/commit/82f1dd268d47c5baebc34770e339cdc1f25a9eb2)) * contact search in request for quotation (backport [#31828](https://github.com/frappe/erpnext/issues/31828)) ([#31840](https://github.com/frappe/erpnext/issues/31840)) ([74664a3](https://github.com/frappe/erpnext/commit/74664a34c0b24b71aeb9169f51b39f01f1791af2)) * delete custom fields on deletion of inventory dimension ([354a9d6](https://github.com/frappe/erpnext/commit/354a9d616960e68d6d79b50568d03f55a1f0d85e)) * **ecommerce:** remove query to non-existing field (backport [#31771](https://github.com/frappe/erpnext/issues/31771)) ([#31773](https://github.com/frappe/erpnext/issues/31773)) ([8737c10](https://github.com/frappe/erpnext/commit/8737c10ce4c95acc394dd9d5409fda63e73c6ec7)) * getting error to show sales invoice group or print rep… (backport [#31756](https://github.com/frappe/erpnext/issues/31756)) ([#31767](https://github.com/frappe/erpnext/issues/31767)) ([53b9d61](https://github.com/frappe/erpnext/commit/53b9d61c46e6ce9b97eaec5779166ab28034eb8f)) * incorrect produced-qty in production-plan-item (backport [#31706](https://github.com/frappe/erpnext/issues/31706)) ([#31861](https://github.com/frappe/erpnext/issues/31861)) ([010a0ca](https://github.com/frappe/erpnext/commit/010a0ca0a9cecae9341b74e8e31dcde151d9aac1)) * incorrect rate in BOM exploded items (backport [#31513](https://github.com/frappe/erpnext/issues/31513)) ([#31864](https://github.com/frappe/erpnext/issues/31864)) ([abe1894](https://github.com/frappe/erpnext/commit/abe18945a60594b01f926b2073b736a56ab043fc)) * intercompany SO throws exception ([1d1f12f](https://github.com/frappe/erpnext/commit/1d1f12f9491f0ac26c25e81269a4ba100c3a3757)) * linter (backport [#31763](https://github.com/frappe/erpnext/issues/31763)) ([#31766](https://github.com/frappe/erpnext/issues/31766)) ([3092131](https://github.com/frappe/erpnext/commit/30921319135b84fbf96782437f2210ec2c0373d7)) * Make expense account editable in Purchase Receipt Item (backport [#31730](https://github.com/frappe/erpnext/issues/31730)) ([#31878](https://github.com/frappe/erpnext/issues/31878)) ([b637d4d](https://github.com/frappe/erpnext/commit/b637d4d5f167160b30ef3d97fbe8f82787a015f5)) * Make expense account editable in Subcontracting Receipt Item (backport [#31848](https://github.com/frappe/erpnext/issues/31848)) ([#31870](https://github.com/frappe/erpnext/issues/31870)) ([aa5aaa1](https://github.com/frappe/erpnext/commit/aa5aaa113eaecc7b0ae9df1bb1d4ce20e7ce18a4)) * not able to issue expired batches ([ce5fc5b](https://github.com/frappe/erpnext/commit/ce5fc5b457b6168ce6d1c591d808a9d3903e210f)) * posting_date of linked vouchers should not affect outstanding ([0faa7b0](https://github.com/frappe/erpnext/commit/0faa7b04326db9f896d647d9dee5fa3b79f4051c)) * **projects:** Add missing comma ([623f56a](https://github.com/frappe/erpnext/commit/623f56a95cf7af12621efeba1cfcde9ae6276368)) * set `billing_address` for purchases in `get_party_details` ([5a28ba8](https://github.com/frappe/erpnext/commit/5a28ba853706ba97cc467b85c9ec86a94816f33e)) * set `company_address` for purchases in `party.js` ([4c82533](https://github.com/frappe/erpnext/commit/4c825332396662efb2c8463d18d9764d93c4e830)) * specify allowed doctype in queries (backport [#31761](https://github.com/frappe/erpnext/issues/31761)) ([#31764](https://github.com/frappe/erpnext/issues/31764)) ([6dce122](https://github.com/frappe/erpnext/commit/6dce122825ef3e50d34f95524a75d9177a5193fe)) * Transit filter for Default Target Warehouse in SE (backport [#31839](https://github.com/frappe/erpnext/issues/31839)) ([#31873](https://github.com/frappe/erpnext/issues/31873)) ([04d3571](https://github.com/frappe/erpnext/commit/04d3571dd9989d2716da49174a028d2db86cd628)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6b6590f944ed..57081b1ba10a 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.0.0" +__version__ = "14.0.1" def get_default_company(user=None): From ae5c05081d30277a50723a9c2fb6e24f70341b74 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 18 Aug 2022 17:41:01 +0530 Subject: [PATCH 034/898] chore: remove unwanted field "provisional_expense_account" from SCR (backport #31847) (#31886) chore: remove unwanted field "provisional_expense_account" from SCR (#31847) (cherry picked from commit 7e88eb549f7059e494455c642e966bb72d0dc5e7) Co-authored-by: Sagar Sharma --- .../subcontracting_receipt.json | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 94304865603f..cb5b8c06ddd0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -51,8 +51,6 @@ "in_words", "bill_no", "bill_date", - "accounting_details_section", - "provisional_expense_account", "more_info", "status", "column_break_39", @@ -524,19 +522,6 @@ "options": "Company", "read_only": 1 }, - { - "collapsible": 1, - "fieldname": "accounting_details_section", - "fieldtype": "Section Break", - "label": "Accounting Details" - }, - { - "fieldname": "provisional_expense_account", - "fieldtype": "Link", - "hidden": 1, - "label": "Provisional Expense Account", - "options": "Account" - }, { "default": "0", "fieldname": "is_return", @@ -599,7 +584,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2022-08-15 14:30:29.447307", + "modified": "2022-08-16 09:56:41.199435", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From 6656d23e45261c6a32750aa3652510e3e40a8650 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 18 Aug 2022 15:31:20 +0000 Subject: [PATCH 035/898] perf: use `create_custom_fields` (#31853) * perf: use `create_custom_fields` * fix: default must be a string (cherry picked from commit aafb7352830342ddef3e0cc8cf520b885957098f) --- .../tally_migration/tally_migration.py | 41 ++++++++------ .../woocommerce_settings.py | 41 +++++++------- erpnext/setup/install.py | 55 +++++++++---------- 3 files changed, 68 insertions(+), 69 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 7d676e423596..cd4aaee2c45a 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -12,7 +12,9 @@ import frappe from bs4 import BeautifulSoup as bs from frappe import _ -from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.custom.doctype.custom_field.custom_field import ( + create_custom_fields as _create_custom_fields, +) from frappe.model.document import Document from frappe.utils.data import format_datetime @@ -577,22 +579,25 @@ def create_fiscal_years(vouchers): new_year.save() oldest_year = new_year - def create_custom_fields(doctypes): - tally_guid_df = { - "fieldtype": "Data", - "fieldname": "tally_guid", - "read_only": 1, - "label": "Tally GUID", - } - tally_voucher_no_df = { - "fieldtype": "Data", - "fieldname": "tally_voucher_no", - "read_only": 1, - "label": "Tally Voucher Number", - } - for df in [tally_guid_df, tally_voucher_no_df]: - for doctype in doctypes: - create_custom_field(doctype, df) + def create_custom_fields(): + _create_custom_fields( + { + ("Journal Entry", "Purchase Invoice", "Sales Invoice"): [ + { + "fieldtype": "Data", + "fieldname": "tally_guid", + "read_only": 1, + "label": "Tally GUID", + }, + { + "fieldtype": "Data", + "fieldname": "tally_voucher_no", + "read_only": 1, + "label": "Tally Voucher Number", + }, + ] + } + ) def create_price_list(): frappe.get_doc( @@ -628,7 +633,7 @@ def create_price_list(): create_fiscal_years(vouchers) create_price_list() - create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) + create_custom_fields() total = len(vouchers) is_last = False diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py index 2e18776a927e..4aa98aab56bd 100644 --- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py +++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py @@ -6,7 +6,7 @@ import frappe from frappe import _ -from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.model.document import Document from frappe.utils.nestedset import get_root_of @@ -19,27 +19,24 @@ def validate(self): def create_delete_custom_fields(self): if self.enable_sync: - custom_fields = {} - # create - for doctype in ["Customer", "Sales Order", "Item", "Address"]: - df = dict( - fieldname="woocommerce_id", - label="Woocommerce ID", - fieldtype="Data", - read_only=1, - print_hide=1, - ) - create_custom_field(doctype, df) - - for doctype in ["Customer", "Address"]: - df = dict( - fieldname="woocommerce_email", - label="Woocommerce Email", - fieldtype="Data", - read_only=1, - print_hide=1, - ) - create_custom_field(doctype, df) + create_custom_fields( + { + ("Customer", "Sales Order", "Item", "Address"): dict( + fieldname="woocommerce_id", + label="Woocommerce ID", + fieldtype="Data", + read_only=1, + print_hide=1, + ), + ("Customer", "Address"): dict( + fieldname="woocommerce_email", + label="Woocommerce Email", + fieldtype="Data", + read_only=1, + print_hide=1, + ), + } + ) if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}): item_group = frappe.new_doc("Item Group") diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 7d7e6b5e076b..2076dde51999 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.utils import cint @@ -83,35 +83,32 @@ def setup_currency_exchange(): def create_print_setting_custom_fields(): - create_custom_field( - "Print Settings", - { - "label": _("Compact Item Print"), - "fieldname": "compact_item_print", - "fieldtype": "Check", - "default": 1, - "insert_after": "with_letterhead", - }, - ) - create_custom_field( - "Print Settings", + create_custom_fields( { - "label": _("Print UOM after Quantity"), - "fieldname": "print_uom_after_quantity", - "fieldtype": "Check", - "default": 0, - "insert_after": "compact_item_print", - }, - ) - create_custom_field( - "Print Settings", - { - "label": _("Print taxes with zero amount"), - "fieldname": "print_taxes_with_zero_amount", - "fieldtype": "Check", - "default": 0, - "insert_after": "allow_print_for_cancelled", - }, + "Print Settings": [ + { + "label": _("Compact Item Print"), + "fieldname": "compact_item_print", + "fieldtype": "Check", + "default": "1", + "insert_after": "with_letterhead", + }, + { + "label": _("Print UOM after Quantity"), + "fieldname": "print_uom_after_quantity", + "fieldtype": "Check", + "default": "0", + "insert_after": "compact_item_print", + }, + { + "label": _("Print taxes with zero amount"), + "fieldname": "print_taxes_with_zero_amount", + "fieldtype": "Check", + "default": "0", + "insert_after": "allow_print_for_cancelled", + }, + ] + } ) From da69cc5477c0648a026f1eef1c2d38d7e0a82a6e Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 18 Aug 2022 16:45:11 +0530 Subject: [PATCH 036/898] fix: additional-cost in items table (cherry picked from commit d7ed4093d8f494affb9929f7ad86cc954e48a8e1) --- .../subcontracting_order.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 71cdc94a3aed..1fd074646178 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -83,23 +83,23 @@ def set_missing_values(self): self.set_missing_values_in_items() def set_missing_values_in_additional_costs(self): - if self.get("additional_costs"): - self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs")) - - if self.total_additional_costs: - if self.distribute_additional_costs_based_on == "Amount": - total_amt = sum(flt(item.amount) for item in self.get("items")) - for item in self.items: - item.additional_cost_per_qty = ( - (item.amount * self.total_additional_costs) / total_amt - ) / item.qty - else: - total_qty = sum(flt(item.qty) for item in self.get("items")) - additional_cost_per_qty = self.total_additional_costs / total_qty - for item in self.items: - item.additional_cost_per_qty = additional_cost_per_qty + self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs")) + + if self.total_additional_costs: + if self.distribute_additional_costs_based_on == "Amount": + total_amt = sum(flt(item.amount) for item in self.get("items")) + for item in self.items: + item.additional_cost_per_qty = ( + (item.amount * self.total_additional_costs) / total_amt + ) / item.qty + else: + total_qty = sum(flt(item.qty) for item in self.get("items")) + additional_cost_per_qty = self.total_additional_costs / total_qty + for item in self.items: + item.additional_cost_per_qty = additional_cost_per_qty else: - self.total_additional_costs = 0 + for item in self.items: + item.additional_cost_per_qty = 0 def set_missing_values_in_service_items(self): for idx, item in enumerate(self.get("service_items")): From d48487ada2c733ed9e88900f0244069a4ba8d0ef Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 18 Aug 2022 17:16:29 +0530 Subject: [PATCH 037/898] fix: base_amount and exchange_rate in additional-cost table (cherry picked from commit eabd3135f0f9d0ca788729c9391fd72ee8a6f871) --- erpnext/stock/landed_taxes_and_charges_common.js | 2 +- .../subcontracting_order/subcontracting_order.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js index ff8a69fb0337..78bfd5dd1ac7 100644 --- a/erpnext/stock/landed_taxes_and_charges_common.js +++ b/erpnext/stock/landed_taxes_and_charges_common.js @@ -1,4 +1,4 @@ -let document_list = ['Landed Cost Voucher', 'Stock Entry']; +let document_list = ['Landed Cost Voucher', 'Stock Entry', 'Subcontracting Order']; document_list.forEach((doctype) => { frappe.ui.form.on(doctype, { diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index dbd337afd438..c20f8ab6653c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -3,6 +3,8 @@ frappe.provide('erpnext.buying'); +{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; + frappe.ui.form.on('Subcontracting Order', { setup: (frm) => { frm.get_field("items").grid.cannot_add_rows = true; @@ -136,6 +138,16 @@ frappe.ui.form.on('Subcontracting Order', { } }); +frappe.ui.form.on('Landed Cost Taxes and Charges', { + amount: function (frm, cdt, cdn) { + frm.events.set_base_amount(frm, cdt, cdn); + }, + + expense_account: function (frm, cdt, cdn) { + frm.events.set_account_currency(frm, cdt, cdn); + } +}); + erpnext.buying.SubcontractingOrderController = class SubcontractingOrderController { setup() { this.frm.custom_make_buttons = { From 7ff54145711a68399f36fdb84aace052c6625db2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 18 Aug 2022 17:20:22 +0530 Subject: [PATCH 038/898] chore: move "set_missing_values_in_additional_costs" from SCO to SC" (cherry picked from commit ea82fe5bc20e4a06c40b9840c2ec536208c3fc3b) --- .../controllers/subcontracting_controller.py | 19 +++++++++++++++++++ .../subcontracting_order.py | 19 ------------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 2a2f8f562e7f..a944cb8fcf7a 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -720,6 +720,25 @@ def set_subcontracting_order_status(self): sco_doc = frappe.get_doc("Subcontracting Order", sco) sco_doc.update_status() + def set_missing_values_in_additional_costs(self): + self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs")) + + if self.total_additional_costs: + if self.distribute_additional_costs_based_on == "Amount": + total_amt = sum(flt(item.amount) for item in self.get("items")) + for item in self.items: + item.additional_cost_per_qty = ( + (item.amount * self.total_additional_costs) / total_amt + ) / item.qty + else: + total_qty = sum(flt(item.qty) for item in self.get("items")) + additional_cost_per_qty = self.total_additional_costs / total_qty + for item in self.items: + item.additional_cost_per_qty = additional_cost_per_qty + else: + for item in self.items: + item.additional_cost_per_qty = 0 + @frappe.whitelist() def get_current_stock(self): if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]: diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 1fd074646178..0495fb417490 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -82,25 +82,6 @@ def set_missing_values(self): self.set_missing_values_in_supplied_items() self.set_missing_values_in_items() - def set_missing_values_in_additional_costs(self): - self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs")) - - if self.total_additional_costs: - if self.distribute_additional_costs_based_on == "Amount": - total_amt = sum(flt(item.amount) for item in self.get("items")) - for item in self.items: - item.additional_cost_per_qty = ( - (item.amount * self.total_additional_costs) / total_amt - ) / item.qty - else: - total_qty = sum(flt(item.qty) for item in self.get("items")) - additional_cost_per_qty = self.total_additional_costs / total_qty - for item in self.items: - item.additional_cost_per_qty = additional_cost_per_qty - else: - for item in self.items: - item.additional_cost_per_qty = 0 - def set_missing_values_in_service_items(self): for idx, item in enumerate(self.get("service_items")): self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty From 9e60dd32e863d286b51b144faeb0b55e5c34a9de Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 18 Aug 2022 19:50:00 +0530 Subject: [PATCH 039/898] fix: recalculate rate of items based on "Recalculate Rate" checkbox (cherry picked from commit 2fc683368403cf6341bf09fcfbd367f9777efa94) --- .../subcontracting_receipt/subcontracting_receipt.py | 2 +- .../subcontracting_receipt_item.json | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 0c4ec6fb76f6..51007c563583 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -125,7 +125,7 @@ def set_missing_values_in_items(self): item.rm_cost_per_qty = item.rm_supp_cost / item.qty rm_supp_cost.pop(item.name) - if self.is_new() and item.rm_supp_cost > 0: + if item.recalculate_rate: item.rate = ( item.rm_cost_per_qty + (item.service_cost_per_qty or 0) + item.additional_cost_per_qty ) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 437fc41f5e43..dcde63599295 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -29,6 +29,7 @@ "rate_and_amount", "rate", "amount", + "recalculate_rate", "column_break_19", "rm_cost_per_qty", "service_cost_per_qty", @@ -460,12 +461,18 @@ "fieldname": "accounting_details_section", "fieldtype": "Section Break", "label": "Accounting Details" + }, + { + "default": "1", + "fieldname": "recalculate_rate", + "fieldtype": "Check", + "label": "Recalculate Rate" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-08-15 14:51:10.613347", + "modified": "2022-08-18 19:42:24.313449", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 3b222339b84ba9d8e0d1f4ea8798ea419f623d71 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 18 Aug 2022 20:26:34 +0530 Subject: [PATCH 040/898] chore: add additional-cost table in SCR (cherry picked from commit 256b4245d51d28cccbca2d0c0037961b0a16112f) # Conflicts: # erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json --- .../stock/landed_taxes_and_charges_common.js | 2 +- .../subcontracting_receipt.js | 12 +++++++ .../subcontracting_receipt.json | 36 +++++++++++++++++++ .../subcontracting_receipt.py | 1 + 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js index 78bfd5dd1ac7..1d76a3d95a13 100644 --- a/erpnext/stock/landed_taxes_and_charges_common.js +++ b/erpnext/stock/landed_taxes_and_charges_common.js @@ -1,4 +1,4 @@ -let document_list = ['Landed Cost Voucher', 'Stock Entry', 'Subcontracting Order']; +let document_list = ['Landed Cost Voucher', 'Stock Entry', 'Subcontracting Order', 'Subcontracting Receipt']; document_list.forEach((doctype) => { frappe.ui.form.on(doctype, { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 35fec8bc33c0..aff76eb50fa5 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -3,6 +3,8 @@ frappe.provide('erpnext.buying'); +{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %}; + frappe.ui.form.on('Subcontracting Receipt', { setup: (frm) => { frm.get_field('supplied_items').grid.cannot_add_rows = true; @@ -128,6 +130,16 @@ frappe.ui.form.on('Subcontracting Receipt', { }, }); +frappe.ui.form.on('Landed Cost Taxes and Charges', { + amount: function (frm, cdt, cdn) { + frm.events.set_base_amount(frm, cdt, cdn); + }, + + expense_account: function (frm, cdt, cdn) { + frm.events.set_account_currency(frm, cdt, cdn); + } +}); + frappe.ui.form.on('Subcontracting Receipt Item', { item_code(frm) { set_missing_values(frm); diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index cb5b8c06ddd0..ee96c50ba146 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -47,6 +47,10 @@ "raw_material_details", "get_current_stock", "supplied_items", + "additional_costs_section", + "distribute_additional_costs_based_on", + "additional_costs", + "total_additional_costs", "section_break_46", "in_words", "bill_no", @@ -580,11 +584,43 @@ "fieldtype": "Link", "label": "Project", "options": "Project" + }, + { + "collapsible": 1, + "collapsible_depends_on": "total_additional_costs", + "depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)", + "fieldname": "additional_costs_section", + "fieldtype": "Section Break", + "label": "Additional Costs" + }, + { + "default": "Qty", + "fieldname": "distribute_additional_costs_based_on", + "fieldtype": "Select", + "label": "Distribute Additional Costs Based On ", + "options": "Qty\nAmount" + }, + { + "fieldname": "additional_costs", + "fieldtype": "Table", + "label": "Additional Costs", + "options": "Landed Cost Taxes and Charges" + }, + { + "fieldname": "total_additional_costs", + "fieldtype": "Currency", + "label": "Total Additional Costs", + "print_hide_if_no_value": 1, + "read_only": 1 } ], "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-08-16 09:56:41.199435", +======= + "modified": "2022-08-18 15:48:57.419191", +>>>>>>> 256b4245d5 (chore: add additional-cost table in SCR) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 51007c563583..f8b71ea02833 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -103,6 +103,7 @@ def on_cancel(self): @frappe.whitelist() def set_missing_values(self): + self.set_missing_values_in_additional_costs() self.set_missing_values_in_supplied_items() self.set_missing_values_in_items() From e099e10c8eef3c497be2298ff087c315fee393a2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 19 Aug 2022 11:46:27 +0530 Subject: [PATCH 041/898] fix: test "test_pending_and_received_qty" (cherry picked from commit addd7347d888701f5ff9031a37aeaa25c97dda14) --- .../test_subcontracted_item_to_be_received.py | 25 ++++++++++++------- .../controllers/subcontracting_controller.py | 4 +-- .../subcontracting_order.py | 6 ++--- .../subcontracting_receipt.py | 6 ++--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py index c772c1a1b170..d13d9701f31e 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py @@ -4,6 +4,8 @@ # Decompiled by https://python-decompiler.com +import copy + import frappe from frappe.tests.utils import FrappeTestCase @@ -11,10 +13,12 @@ execute, ) from erpnext.controllers.tests.test_subcontracting_controller import ( + get_rm_items, get_subcontracting_order, make_service_item, + make_stock_in_entry, + make_stock_transfer_entry, ) -from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, ) @@ -36,15 +40,18 @@ def test_pending_and_received_qty(self): sco = get_subcontracting_order( service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC" ) - make_stock_entry( - item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100 - ) - make_stock_entry( - item_code="_Test Item Home Desktop 100", - target="_Test Warehouse 1 - _TC", - qty=100, - basic_rate=100, + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), ) + make_subcontracting_receipt_against_sco(sco.name) sco.reload() col, data = execute( diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index a944cb8fcf7a..1372c89d470b 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -490,7 +490,7 @@ def __set_supplied_items(self): row.item_code, row.get(self.subcontract_data.order_field), ) and transfer_item.qty > 0: - qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0 + qty = flt(self.__get_qty_based_on_material_transfer(row, transfer_item)) transfer_item.qty -= qty self.__add_supplied_item(row, transfer_item.get("item_details"), qty) @@ -749,7 +749,7 @@ def get_current_stock(self): {"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse}, "actual_qty", ) - item.current_stock = flt(actual_qty) or 0 + item.current_stock = flt(actual_qty) @property def sub_contracted_items(self): diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 0495fb417490..156f027617fb 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -95,9 +95,7 @@ def set_missing_values_in_supplied_items(self): def set_missing_values_in_items(self): total_qty = total = 0 for item in self.items: - item.rate = ( - item.rm_cost_per_qty + item.service_cost_per_qty + (item.additional_cost_per_qty or 0) - ) + item.rate = item.rm_cost_per_qty + item.service_cost_per_qty + flt(item.additional_cost_per_qty) item.amount = item.qty * item.rate total_qty += flt(item.qty) total += flt(item.amount) @@ -168,7 +166,7 @@ def update_status(self, status=None, update_modified=False): total_required_qty = total_supplied_qty = 0 for item in self.supplied_items: total_required_qty += item.required_qty - total_supplied_qty += item.supplied_qty or 0 + total_supplied_qty += flt(item.supplied_qty) if total_supplied_qty: status = "Partial Material Transferred" if total_supplied_qty >= total_required_qty: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index f8b71ea02833..021d9aa85473 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -3,7 +3,7 @@ import frappe from frappe import _ -from frappe.utils import cint, getdate, nowdate +from frappe.utils import cint, flt, getdate, nowdate from erpnext.controllers.subcontracting_controller import SubcontractingController @@ -128,10 +128,10 @@ def set_missing_values_in_items(self): if item.recalculate_rate: item.rate = ( - item.rm_cost_per_qty + (item.service_cost_per_qty or 0) + item.additional_cost_per_qty + flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) ) - item.received_qty = item.qty + (item.rejected_qty or 0) + item.received_qty = item.qty + flt(item.rejected_qty) item.amount = item.qty * item.rate total_qty += item.qty total_amount += item.amount From 9a29e3c9f2f97b9e422538a3c9ae44b88e98e31e Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 19 Aug 2022 11:10:00 +0530 Subject: [PATCH 042/898] chore: add test for additional-cost (cherry picked from commit c247cf728c6aaaa55a47c8148df807944a515a6e) --- .../tests/test_subcontracting_controller.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 4fab8058b86f..bc503f5440a1 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -36,6 +36,36 @@ def test_remove_empty_rows(self): sco.remove_empty_rows() self.assertEqual((len_before - 1), len(sco.service_items)) + def test_set_missing_values_in_additional_costs(self): + sco = get_subcontracting_order(do_not_submit=1) + + rate_without_additional_cost = sco.items[0].rate + amount_without_additional_cost = sco.items[0].amount + + additional_amount = 120 + sco.append( + "additional_costs", + { + "expense_account": "Cost of Goods Sold - _TC", + "description": "Test", + "amount": additional_amount, + }, + ) + sco.save() + + additional_cost_per_qty = additional_amount / sco.items[0].qty + + self.assertEqual(sco.items[0].additional_cost_per_qty, additional_cost_per_qty) + self.assertEqual(rate_without_additional_cost + additional_cost_per_qty, sco.items[0].rate) + self.assertEqual(amount_without_additional_cost + additional_amount, sco.items[0].amount) + + sco.additional_costs = [] + sco.save() + + self.assertEqual(sco.items[0].additional_cost_per_qty, 0) + self.assertEqual(rate_without_additional_cost, sco.items[0].rate) + self.assertEqual(amount_without_additional_cost, sco.items[0].amount) + def test_create_raw_materials_supplied(self): sco = get_subcontracting_order() sco.supplied_items = None From 376293326b49d7c9daeb7007f72b13dfaf265797 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 19 Aug 2022 15:30:50 +0530 Subject: [PATCH 043/898] chore: conflicts --- .../subcontracting_receipt/subcontracting_receipt.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index ee96c50ba146..3aa8e61ae091 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -616,11 +616,7 @@ ], "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-08-16 09:56:41.199435", -======= "modified": "2022-08-18 15:48:57.419191", ->>>>>>> 256b4245d5 (chore: add additional-cost table in SCR) "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From 200a971743066450d95509a8deb3af1d601a557e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:11:13 +0530 Subject: [PATCH 044/898] fix(pos): edge case while closing pos (#31892) --- .../doctype/pos_closing_entry/pos_closing_entry.js | 9 +++++++++ .../pos_closing_entry/pos_closing_entry.json | 13 +++++++++++-- .../doctype/pos_closing_entry/pos_closing_entry.py | 3 +++ .../pos_invoice_merge_log.json | 11 ++++++++++- .../pos_invoice_merge_log/pos_invoice_merge_log.py | 7 ++++++- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 98f3420d87ed..1d596c1bfbb9 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -36,6 +36,15 @@ frappe.ui.form.on('POS Closing Entry', { }); set_html_data(frm); + + if (frm.doc.docstatus == 1) { + if (!frm.doc.posting_date) { + frm.set_value("posting_date", frappe.datetime.nowdate()); + } + if (!frm.doc.posting_time) { + frm.set_value("posting_time", frappe.datetime.now_time()); + } + } }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index d6e35c6a50d6..9d15e6cf357a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -11,6 +11,7 @@ "period_end_date", "column_break_3", "posting_date", + "posting_time", "pos_opening_entry", "status", "section_break_5", @@ -51,7 +52,6 @@ "fieldtype": "Datetime", "in_list_view": 1, "label": "Period End Date", - "read_only": 1, "reqd": 1 }, { @@ -219,6 +219,13 @@ "fieldtype": "Small Text", "label": "Error", "read_only": 1 + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "reqd": 1 } ], "is_submittable": 1, @@ -228,10 +235,11 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-10-20 16:19:25.340565", + "modified": "2022-08-01 11:37:14.991228", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -278,5 +286,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 49aab0d0bbf0..655c4ec00355 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -15,6 +15,9 @@ class POSClosingEntry(StatusUpdater): def validate(self): + self.posting_date = self.posting_date or frappe.utils.nowdate() + self.posting_time = self.posting_time or frappe.utils.nowtime() + if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json index d76208707800..a0594556474e 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "posting_date", + "posting_time", "merge_invoices_based_on", "column_break_3", "pos_closing_entry", @@ -105,12 +106,19 @@ "label": "Customer Group", "mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", "options": "Customer Group" + }, + { + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "no_copy": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-09-14 11:17:19.001142", + "modified": "2022-08-01 11:36:42.456429", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Merge Log", @@ -173,5 +181,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file 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 5003a1d6a819..81a234a20a74 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 @@ -9,7 +9,7 @@ from frappe.core.page.background_jobs.background_jobs import get_info from frappe.model.document import Document from frappe.model.mapper import map_child_doc, map_doc -from frappe.utils import cint, flt, getdate, nowdate +from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime from frappe.utils.background_jobs import enqueue from frappe.utils.scheduler import is_scheduler_inactive @@ -99,6 +99,7 @@ def process_merging_into_sales_invoice(self, data): sales_invoice.is_consolidated = 1 sales_invoice.set_posting_time = 1 sales_invoice.posting_date = getdate(self.posting_date) + sales_invoice.posting_time = get_time(self.posting_time) sales_invoice.save() sales_invoice.submit() @@ -115,6 +116,7 @@ def process_merging_into_credit_note(self, data): credit_note.is_consolidated = 1 credit_note.set_posting_time = 1 credit_note.posting_date = getdate(self.posting_date) + credit_note.posting_time = get_time(self.posting_time) # TODO: return could be against multiple sales invoice which could also have been consolidated? # credit_note.return_against = self.consolidated_invoice credit_note.save() @@ -402,6 +404,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): merge_log.posting_date = ( getdate(closing_entry.get("posting_date")) if closing_entry else nowdate() ) + merge_log.posting_time = ( + get_time(closing_entry.get("posting_time")) if closing_entry else nowtime() + ) merge_log.customer = customer merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None From 0db912998a8a971fdc010bc9f7017658e0d88db4 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 19 Aug 2022 20:44:13 +0530 Subject: [PATCH 045/898] chore: allow subcontracting receipt backdated entry (cherry picked from commit f8c11847bbff996d20cc6385d61fe5df3fe75019) --- .../subcontracting_receipt.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 3aa8e61ae091..872d18e15eaf 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -15,6 +15,7 @@ "company", "posting_date", "posting_time", + "set_posting_time", "is_return", "return_against", "accounting_dimensions_section", @@ -138,6 +139,7 @@ "label": "Date", "no_copy": 1, "print_width": "100px", + "read_only_depends_on": "eval: !doc.set_posting_time", "reqd": 1, "search_index": 1, "width": "100px" @@ -150,6 +152,7 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", + "read_only_depends_on": "eval: !doc.set_posting_time", "reqd": 1, "width": "100px" }, @@ -612,11 +615,19 @@ "label": "Total Additional Costs", "print_hide_if_no_value": 1, "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.docstatus==0", + "fieldname": "set_posting_time", + "fieldtype": "Check", + "label": "Edit Posting Date and Time", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2022-08-18 15:48:57.419191", + "modified": "2022-08-19 19:50:16.935124", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From 19d29d186135bb0f9bd2111a6294a2d98547808c Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 19 Aug 2022 20:52:26 +0530 Subject: [PATCH 046/898] chore: add option for "Subcontracting Receipt" in "Voucher Type" (cherry picked from commit f92f3e0208cdfd6812c32a5820f7b08791f64e3c) --- .../doctype/repost_item_valuation/repost_item_valuation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index 4cd40bf38ec8..d6e00eada725 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Repost Item Valuation', { return { filters: { name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note', - 'Sales Invoice', 'Stock Entry', 'Stock Reconciliation']] + 'Sales Invoice', 'Stock Entry', 'Stock Reconciliation', 'Subcontracting Receipt']] } }; }); From 6be77d5729906c6e43c7744b07289b490e05c891 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 20 Aug 2022 19:28:21 +0530 Subject: [PATCH 047/898] fix: make rate field read-only in subcontracting receipt item (backport #31905) (#31906) fix: make rate field read-only in subcontracting receipt item (#31905) (cherry picked from commit 588ca68171ed8c8f65d9a6c27323e29a2ac30e94) Co-authored-by: Sagar Sharma --- .../subcontracting_receipt_item.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index dcde63599295..fd86895b9e32 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -194,6 +194,8 @@ "label": "Rate", "options": "currency", "print_width": "100px", + "read_only": 1, + "read_only_depends_on": "eval: doc.recalculate_rate", "width": "100px" }, { @@ -472,7 +474,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-08-18 19:42:24.313449", + "modified": "2022-08-20 17:16:48.269164", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 78b39d6ca4e4a54833c52c9614b7a8ee72207ffa Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 19 Aug 2022 12:30:47 +0530 Subject: [PATCH 048/898] fix: TDS calculation for advance payment "against_voucher": ["is", "not set"] was used in query due to which if TDS was added on "advance" payment vouchers and then reconciled against purchase invoice. it will not find those vouchers and consider this as first-time threshold due to which it will calculate Tax for all transactions. (cherry picked from commit a4521437825a960e14556fa3963bd1bd1a55a2dc) --- .../doctype/tax_withholding_category/tax_withholding_category.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index a519d8be7368..6004e2b19b21 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -318,7 +318,6 @@ def get_advance_vouchers( "is_cancelled": 0, "party_type": party_type, "party": ["in", parties], - "against_voucher": ["is", "not set"], } if company: From a76732613e605ef0b3fd3618612b32e7fa2bbed5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 16 Aug 2022 16:39:28 +0530 Subject: [PATCH 049/898] fix: incorrect tax amt due to different exchange rate in PR and PI (cherry picked from commit 5fd0770372c07dbec89669e1dcdb4d98f1fbca90) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index de3927e45b1f..e6ff1281d91a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1791,4 +1791,6 @@ def update_item(obj, target, source_parent): target_doc, ) + doc.set_onload("ignore_price_list", True) + return doc From 981add9b6f99b63c21c464e4a7e0c4ffd28cb5bb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 16 Aug 2022 09:27:42 +0530 Subject: [PATCH 050/898] fix: incorrect buying amount in Gross Profit rpt (cherry picked from commit 967dd398e7ff7207aaa4c7f5808bcac6084d44cb) --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 526ea9d6e2ee..54af22591547 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -616,7 +616,7 @@ def get_buying_amount(self, row, item_code): previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0 if previous_stock_value: - return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) + return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) else: return flt(row.qty) * self.get_average_buying_rate(row, item_code) else: From 0bfb774bdf90864133c04898b6ae57a5a8317f59 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 18:38:21 +0530 Subject: [PATCH 051/898] fix: don't allow to create SCR directly (backport #31924) (#31926) fix: don't allow to create SCR directly (#31924) (cherry picked from commit bf5c43322a2fdf299a031ee2b864f5da0d9e15a9) Co-authored-by: Sagar Sharma --- .../doctype/subcontracting_receipt/subcontracting_receipt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 872d18e15eaf..84e95548e17c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -625,9 +625,10 @@ "print_hide": 1 } ], + "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2022-08-19 19:50:16.935124", + "modified": "2022-08-22 17:30:40.827517", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From b6d2de2cc11b7abf955d7765db8d2874752a5a9a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 21 Aug 2022 17:51:05 +0530 Subject: [PATCH 052/898] fix: Cash and non trade discount calculation (cherry picked from commit 3b15966cc9fb2cf91eca9105f767d8775845f7fa) --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 2 +- .../doctype/sales_invoice/sales_invoice.js | 4 ++ .../doctype/sales_invoice/sales_invoice.py | 16 ------- erpnext/controllers/accounts_controller.py | 44 ++++++++++--------- erpnext/controllers/taxes_and_totals.py | 16 ++++--- .../public/js/controllers/taxes_and_totals.js | 10 +++++ 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 1987c8340d45..7227b95818f9 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -366,7 +366,7 @@ def update_outstanding_amt( if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: ref_doc = frappe.get_doc(against_voucher_type, against_voucher) - # Didn't use db_set for optimisation purpose + # Didn't use db_set for optimization purpose ref_doc.outstanding_amount = bal frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index ba4cdd606e96..a06da72ccbfc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -479,9 +479,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e is_cash_or_non_trade_discount() { this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount); + this.frm.set_df_property("additional_discount_account", "reqd", this.frm.doc.is_cash_or_non_trade_discount); + if (!this.frm.doc.is_cash_or_non_trade_discount) { this.frm.set_value("additional_discount_account", ""); } + + this.calculate_taxes_and_totals(); } }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 19a234d9df3e..4008863e9ba7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1033,22 +1033,6 @@ def make_customer_gl_entry(self, gl_entries): ) ) - if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"): - gl_entries.append( - self.get_gl_dict( - { - "account": self.additional_discount_account, - "against": self.debit_to, - "debit": self.base_discount_amount, - "debit_in_account_currency": self.discount_amount, - "cost_center": self.cost_center, - "project": self.project, - }, - self.currency, - item=self, - ) - ) - def make_tax_gl_entries(self, gl_entries): enable_discount_accounting = cint( frappe.db.get_single_value("Selling Settings", "enable_discount_accounting") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 70d2bc68a11a..28070d237747 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1109,17 +1109,17 @@ def make_discount_gl_entries(self, gl_entries): frappe.db.get_single_value("Selling Settings", "enable_discount_accounting") ) - if enable_discount_accounting: - if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" - rev_dr_cr = "debit" - supplier_or_customer = self.supplier + if self.doctype == "Purchase Invoice": + dr_or_cr = "credit" + rev_dr_cr = "debit" + supplier_or_customer = self.supplier - else: - dr_or_cr = "debit" - rev_dr_cr = "credit" - supplier_or_customer = self.customer + else: + dr_or_cr = "debit" + rev_dr_cr = "credit" + supplier_or_customer = self.customer + if enable_discount_accounting: for item in self.get("items"): if item.get("discount_amount") and item.get("discount_account"): discount_amount = item.discount_amount * item.qty @@ -1173,18 +1173,22 @@ def make_discount_gl_entries(self, gl_entries): ) ) - if self.get("discount_amount") and self.get("additional_discount_account"): - gl_entries.append( - self.get_gl_dict( - { - "account": self.additional_discount_account, - "against": supplier_or_customer, - dr_or_cr: self.discount_amount, - "cost_center": self.cost_center, - }, - item=self, - ) + if ( + (enable_discount_accounting or self.is_cash_or_non_trade_discount) + and self.get("additional_discount_account") + and self.get("discount_amount") + ): + gl_entries.append( + self.get_gl_dict( + { + "account": self.additional_discount_account, + "against": supplier_or_customer, + dr_or_cr: self.discount_amount, + "cost_center": self.cost_center, + }, + item=self, ) + ) def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 0d8cffe03f8b..bc38d08b809c 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -37,6 +37,11 @@ def calculate(self): self.set_discount_amount() self.apply_discount_amount() + # Update grand total as per cash and non trade discount + if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): + self.doc.grand_total -= self.doc.discount_amount + self.doc.base_grand_total -= self.doc.base_discount_amount + self.calculate_shipping_charges() if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]: @@ -500,9 +505,6 @@ def calculate_totals(self): else: self.doc.grand_total = flt(self.doc.net_total) - if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): - self.doc.grand_total -= self.doc.discount_amount - if self.doc.get("taxes"): self.doc.total_taxes_and_charges = flt( self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), @@ -597,16 +599,16 @@ def apply_discount_amount(self): if not self.doc.apply_discount_on: frappe.throw(_("Please select Apply Discount On")) + self.doc.base_discount_amount = flt( + self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") + ) + if self.doc.apply_discount_on == "Grand Total" and self.doc.get( "is_cash_or_non_trade_discount" ): self.discount_amount_applied = True return - self.doc.base_discount_amount = flt( - self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") - ) - total_for_discount_amount = self.get_total_for_discount_amount() taxes = self.doc.get("taxes") net_total = 0 diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 16b0b4a866fe..3b9e23a3f510 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -39,6 +39,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this._calculate_taxes_and_totals(); this.calculate_discount_amount(); + // # Update grand total as per cash and non trade discount + if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { + this.frm.doc.grand_total -= this.frm.doc.discount_amount; + this.frm.doc.base_grand_total -= this.frm.doc.base_discount_amount; + } + await this.calculate_shipping_charges(); // Advance calculation applicable to Sales /Purchase Invoice @@ -633,6 +639,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.base_discount_amount = flt(this.frm.doc.discount_amount * this.frm.doc.conversion_rate, precision("base_discount_amount")); + if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { + return + } + var total_for_discount_amount = this.get_total_for_discount_amount(); var net_total = 0; // calculate item amount after Discount Amount From 3afb625ff86f30269ce75f22f5b7c0d7f890ef34 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Aug 2022 08:57:58 +0530 Subject: [PATCH 053/898] fix: Test cases (cherry picked from commit ae3dce0cbdca40294739b547d85bfca9b1e28c0f) --- 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 28070d237747..e689d567a17a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1174,7 +1174,7 @@ def make_discount_gl_entries(self, gl_entries): ) if ( - (enable_discount_accounting or self.is_cash_or_non_trade_discount) + (enable_discount_accounting or self.get("is_cash_or_non_trade_discount")) and self.get("additional_discount_account") and self.get("discount_amount") ): From 856a64b77c4d1e7bb966b0c605c2c050cc8d4630 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 23 Aug 2022 09:12:20 +0530 Subject: [PATCH 054/898] chore: Linting issues (cherry picked from commit 1cb7ae16ab156da2fd43ec915316947b0b8db964) --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 3b9e23a3f510..4c3e9dcf0a78 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -640,7 +640,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { precision("base_discount_amount")); if (this.frm.doc.apply_discount_on == "Grand Total" && this.frm.doc.is_cash_or_non_trade_discount) { - return + return; } var total_for_discount_amount = this.get_total_for_discount_amount(); From 5778f227ee0bb8f962c65a991046a4754a51fba7 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 23 Aug 2022 05:28:37 +0000 Subject: [PATCH 055/898] chore(release): Bumped to Version 14.0.2 ## [14.0.2](https://github.com/frappe/erpnext/compare/v14.0.1...v14.0.2) (2022-08-23) ### Bug Fixes * additional-cost in items table ([da69cc5](https://github.com/frappe/erpnext/commit/da69cc5477c0648a026f1eef1c2d38d7e0a82a6e)) * base_amount and exchange_rate in additional-cost table ([d48487a](https://github.com/frappe/erpnext/commit/d48487ada2c733ed9e88900f0244069a4ba8d0ef)) * Cash and non trade discount calculation ([b6d2de2](https://github.com/frappe/erpnext/commit/b6d2de2cc11b7abf955d7765db8d2874752a5a9a)) * don't allow to create SCR directly (backport [#31924](https://github.com/frappe/erpnext/issues/31924)) ([#31926](https://github.com/frappe/erpnext/issues/31926)) ([0bfb774](https://github.com/frappe/erpnext/commit/0bfb774bdf90864133c04898b6ae57a5a8317f59)) * incorrect buying amount in Gross Profit rpt ([981add9](https://github.com/frappe/erpnext/commit/981add9b6f99b63c21c464e4a7e0c4ffd28cb5bb)) * incorrect tax amt due to different exchange rate in PR and PI ([a767326](https://github.com/frappe/erpnext/commit/a76732613e605ef0b3fd3618612b32e7fa2bbed5)) * limited options for no-of-employees in crm ([735a608](https://github.com/frappe/erpnext/commit/735a60807ae1f540056330a19723f6ce2d5ec15b)) * make rate field read-only in subcontracting receipt item (backport [#31905](https://github.com/frappe/erpnext/issues/31905)) ([#31906](https://github.com/frappe/erpnext/issues/31906)) ([6be77d5](https://github.com/frappe/erpnext/commit/6be77d5729906c6e43c7744b07289b490e05c891)) * map old data as per new options of no-of-employees ([35e9bfc](https://github.com/frappe/erpnext/commit/35e9bfca388b3c8f50e5e65d357dd735822bba8b)) * **pos:** edge case while closing pos ([#31892](https://github.com/frappe/erpnext/issues/31892)) ([200a971](https://github.com/frappe/erpnext/commit/200a971743066450d95509a8deb3af1d601a557e)) * process loan interest accrual ([66e5202](https://github.com/frappe/erpnext/commit/66e5202642dcdf8fd80571f0b05d8076b7c0c0f9)) * recalculate rate of items based on "Recalculate Rate" checkbox ([9e60dd3](https://github.com/frappe/erpnext/commit/9e60dd32e863d286b51b144faeb0b55e5c34a9de)) * TDS calculation for advance payment ([78b39d6](https://github.com/frappe/erpnext/commit/78b39d6ca4e4a54833c52c9614b7a8ee72207ffa)) * term loan interest calculation ([8fdbbf3](https://github.com/frappe/erpnext/commit/8fdbbf374df6e8b00b8d11c7812d28a719fb394f)) * test "test_pending_and_received_qty" ([e099e10](https://github.com/frappe/erpnext/commit/e099e10c8eef3c497be2298ff087c315fee393a2)) * Test cases ([3afb625](https://github.com/frappe/erpnext/commit/3afb625ff86f30269ce75f22f5b7c0d7f890ef34)) * Unable to make payment entry against Fees using education app ([1aa96de](https://github.com/frappe/erpnext/commit/1aa96defda1c34b99e3362692d2936c78d76a195)) ### Performance Improvements * use `create_custom_fields` ([#31853](https://github.com/frappe/erpnext/issues/31853)) ([6656d23](https://github.com/frappe/erpnext/commit/6656d23e45261c6a32750aa3652510e3e40a8650)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 57081b1ba10a..2bbbfdade073 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.0.1" +__version__ = "14.0.2" def get_default_company(user=None): From 4b609322ba1105e80f4b12be140e991345606ca8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 23 Aug 2022 16:09:26 +0530 Subject: [PATCH 056/898] chore: add Work Order test dependencies (backport #31936) (#31937) chore: add Work Order test dependencies (#31936) (cherry picked from commit fe73d55f702163386e413b61dd504f93a8e8479c) Co-authored-by: HENRY Florian --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index b556d9974a23..a53c42c5ec83 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -26,6 +26,8 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.utils import get_bin +test_dependencies = ["BOM"] + class TestWorkOrder(FrappeTestCase): def setUp(self): From a7d23abc2ff7aad33fbac13c8e101fdd3bf4cb00 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 13:30:25 +0530 Subject: [PATCH 057/898] fix: Route condition set for stock ledger (backport #31935) (#31945) fix: Route condition set for stock ledger (#31935) (cherry picked from commit 0e26df331c2358a9683f2e59b9b0ed3bfccad61e) Co-authored-by: Solufyin <34390782+Solufyin@users.noreply.github.com> --- erpnext/stock/doctype/item/item_dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py index 897acb744870..34bb4d12257d 100644 --- a/erpnext/stock/doctype/item/item_dashboard.py +++ b/erpnext/stock/doctype/item/item_dashboard.py @@ -5,7 +5,7 @@ def get_data(): return { "heatmap": True, "heatmap_message": _("This is based on stock movement. See {0} for details").format( - '' + _("Stock Ledger") + "" + '' + _("Stock Ledger") + "" ), "fieldname": "item_code", "non_standard_fieldnames": { From 362976fa42439eb72dcf1f3efa451142b191a2d9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 18:26:08 +0530 Subject: [PATCH 058/898] fix: Explicitly commit "log_error" since its getting called during GET request (backport #31952) (#31953) fix: Explicitly commit "log_error" since its getting called during GET request (#31952) (cherry picked from commit 122f1c0cedf583451e85eb68093ede6f0ede43e4) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- erpnext/erpnext_integrations/exotel_integration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py index fd9f74e8c9b7..fd0f78357599 100644 --- a/erpnext/erpnext_integrations/exotel_integration.py +++ b/erpnext/erpnext_integrations/exotel_integration.py @@ -26,6 +26,7 @@ def handle_incoming_call(**kwargs): except Exception as e: frappe.db.rollback() exotel_settings.log_error("Error in Exotel incoming call") + frappe.db.commit() @frappe.whitelist(allow_guest=True) From 15915d7053f9be87b888c5137c820a09cf37599a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 24 Aug 2022 17:05:13 +0530 Subject: [PATCH 059/898] fix: display amount in account currency if party is supplied (cherry picked from commit e5b04d54ffbc6c2bed5df8280e39f9afb7c6555b) --- .../report/accounts_receivable/accounts_receivable.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e937edbeb289..b713b018f7c7 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -187,7 +187,11 @@ def update_voucher_balance(self, ple): if not row: return - amount = ple.amount + # amount in "Party Currency", if its supplied. If not, amount in company currency + if self.filters.get(scrub(self.party_type)): + amount = ple.amount_in_account_currency + else: + amount = ple.amount amount_in_account_currency = ple.amount_in_account_currency # update voucher @@ -685,7 +689,7 @@ def get_ple_entries(self): ple.party, ple.posting_date, ple.due_date, - ple.account_currency.as_("currency"), + ple.account_currency, ple.amount, ple.amount_in_account_currency, ) From fb387426d64747abe64a480a5d1d46b151d47f2c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Aug 2022 11:36:00 +0530 Subject: [PATCH 060/898] refactor: disable discount accounting on Buying module(PI) (cherry picked from commit a956e20f294a5d66d6589c479398eefc632eff43) --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e6ff1281d91a..fea81e9c272b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -575,7 +575,6 @@ def get_gl_entries(self, warehouse_account=None): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) - self.make_discount_gl_entries(gl_entries) if self.check_asset_cwip_enabled(): self.get_asset_gl_entry(gl_entries) @@ -807,7 +806,7 @@ def make_item_gl_entries(self, gl_entries): ) if not item.is_fixed_asset: - dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting) + dummy, amount = self.get_amount_and_base_amount(item, None) else: amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) @@ -1165,7 +1164,7 @@ def make_tax_gl_entries(self, gl_entries): ) for tax in self.get("taxes"): - amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting) + amount, base_amount = self.get_tax_amounts(tax, None) if tax.category in ("Total", "Valuation and Total") and flt(base_amount): account_currency = get_account_currency(tax.account_head) From 2577747c5c345bafa4e3e5639b4c4f17c6aa57df Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Aug 2022 15:17:27 +0530 Subject: [PATCH 061/898] test: remove discount accounting tests (cherry picked from commit 277ef04b600412fa8cc60abc90ba254c35b2ecf6) --- .../purchase_invoice/test_purchase_invoice.py | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index e55d3a70afe9..0a4f25b87699 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -338,59 +338,6 @@ def test_purchase_invoice_with_exchange_rate_difference(self): self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) - @change_settings("Buying Settings", {"enable_discount_accounting": 1}) - def test_purchase_invoice_with_discount_accounting_enabled(self): - - discount_account = create_account( - account_name="Discount Account", - parent_account="Indirect Expenses - _TC", - company="_Test Company", - ) - pi = make_purchase_invoice(discount_account=discount_account, rate=45) - - expected_gle = [ - ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()], - ["Creditors - _TC", 0.0, 225.0, nowdate()], - ["Discount Account - _TC", 0.0, 25.0, nowdate()], - ] - - check_gl_entries(self, pi.name, expected_gle, nowdate()) - - @change_settings("Buying Settings", {"enable_discount_accounting": 1}) - def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self): - - additional_discount_account = create_account( - account_name="Discount Account", - parent_account="Indirect Expenses - _TC", - company="_Test Company", - ) - - pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC") - pi.apply_discount_on = "Grand Total" - pi.additional_discount_account = additional_discount_account - pi.additional_discount_percentage = 10 - pi.disable_rounded_total = 1 - pi.append( - "taxes", - { - "charge_type": "On Net Total", - "account_head": "_Test Account VAT - _TC", - "cost_center": "Main - _TC", - "description": "Test", - "rate": 10, - }, - ) - pi.submit() - - expected_gle = [ - ["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()], - ["_Test Account VAT - _TC", 25.0, 0.0, nowdate()], - ["Creditors - _TC", 0.0, 247.5, nowdate()], - ["Discount Account - _TC", 0.0, 27.5, nowdate()], - ] - - check_gl_entries(self, pi.name, expected_gle, nowdate()) - def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() From 94ebfa765c38e8da2e351bb3e2a3154bffd3e655 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Aug 2022 17:16:01 +0530 Subject: [PATCH 062/898] fix: Purposes not set (cherry picked from commit f9a7b31b5b39da7f3e12ad8274330dafda6ce2a8) --- .../doctype/maintenance_schedule/maintenance_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 09d44297123a..3dc6b0f9008c 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -415,7 +415,7 @@ def update_serial(source, target, parent): }, "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", - "condition": lambda doc: doc.item_name == item_name, + "condition": lambda doc: doc.item_name == item_name if item_name else True, "field_map": {"sales_person": "service_person"}, "postprocess": update_serial, }, From 309da9644273118cd4efd6f60b810acd60307394 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sun, 21 Aug 2022 12:09:08 +0530 Subject: [PATCH 063/898] fix: Add docstatus filter for voucher_no in Repost Item Valuation (cherry picked from commit 520306dc8751ab39cee7675a481b00e6dfeaa1b0) --- .../doctype/repost_item_valuation/repost_item_valuation.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index d6e00eada725..eae73050b23e 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -24,7 +24,8 @@ frappe.ui.form.on('Repost Item Valuation', { frm.set_query("voucher_no", () => { return { filters: { - company: frm.doc.company + company: frm.doc.company, + docstatus: 1 } }; }); From 4cf9fb08e19533f4d2a0674ab09ec045f49ec05b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 25 Aug 2022 11:44:12 +0530 Subject: [PATCH 064/898] fix: default supplier not set in the PP (cherry picked from commit 5fd468d9ec74cef05a6142c2a1112ee8c6c45ae3) --- .../production_plan/production_plan.py | 28 +++++++++++++++++++ .../production_plan/test_production_plan.py | 25 +++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 2cdf8d3ea9ff..66d458bf7501 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -656,6 +656,8 @@ def get_sub_assembly_items(self, manufacturing_type=None): row.idx = idx + 1 self.append("sub_assembly_items", row) + self.set_default_supplier_for_subcontracting_order() + def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): "Modify bom_data, set additional details." for data in bom_data: @@ -667,6 +669,32 @@ def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_typ "Subcontract" if data.is_sub_contracted_item else "In House" ) + def set_default_supplier_for_subcontracting_order(self): + items = [ + d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract" + ] + + if not items: + return + + default_supplier = frappe._dict( + frappe.get_all( + "Item Default", + fields=["parent", "default_supplier"], + filters={"parent": ("in", items), "default_supplier": ("is", "set")}, + as_list=1, + ) + ) + + if not default_supplier: + return + + for row in self.sub_assembly_items: + if row.type_of_manufacturing != "Subcontract": + continue + + row.supplier = default_supplier.get(row.production_item) + def combine_subassembly_items(self, sub_assembly_items_store): "Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No." key_wise_data = {} diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index e2415ad848e5..1d2d1bd9a841 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -281,6 +281,31 @@ def test_production_plan_combine_items(self): pln.reload() pln.cancel() + def test_production_plan_subassembly_default_supplier(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + bom_tree_1 = {"Test Laptop": {"Test Motherboard": {"Test Motherboard Wires": {}}}} + bom = create_nested_bom(bom_tree_1, prefix="") + + item_doc = frappe.get_doc("Item", "Test Motherboard") + company = "_Test Company" + + item_doc.is_sub_contracted_item = 1 + for row in item_doc.item_defaults: + if row.company == company and not row.default_supplier: + row.default_supplier = "_Test Supplier" + + if not item_doc.item_defaults: + item_doc.append("item_defaults", {"company": company, "default_supplier": "_Test Supplier"}) + + item_doc.save() + + plan = create_production_plan(item_code="Test Laptop", use_multi_level_bom=1, do_not_submit=True) + plan.get_sub_assembly_items() + plan.set_default_supplier_for_subcontracting_order() + + self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier") + def test_production_plan_combine_subassembly(self): """ Test combining Sub assembly items belonging to the same BOM in Prod Plan. From 193502ce03c1fbff0c1b78f77d2c1dd23907375f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 25 Aug 2022 12:13:17 +0530 Subject: [PATCH 065/898] fix: material request connection on work order (cherry picked from commit 9ab10def4936424e9825bf0a999a20bb6039d31e) --- .../doctype/work_order/work_order_dashboard.py | 2 +- .../doctype/material_request/material_request.json | 14 ++++++++++++-- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py index 465460f95d8b..d0dcc5593241 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py +++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py @@ -7,6 +7,6 @@ def get_data(): "non_standard_fieldnames": {"Batch": "reference_name"}, "transactions": [ {"label": _("Transactions"), "items": ["Stock Entry", "Job Card", "Pick List"]}, - {"label": _("Reference"), "items": ["Serial No", "Batch"]}, + {"label": _("Reference"), "items": ["Serial No", "Batch", "Material Request"]}, ], } diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index cb46a6c3681c..35931307af83 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -37,7 +37,8 @@ "tc_name", "terms", "reference", - "job_card" + "job_card", + "work_order" ], "fields": [ { @@ -309,16 +310,24 @@ "label": "Transfer Status", "options": "\nNot Started\nIn Transit\nCompleted", "read_only": 1 + }, + { + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work Order", + "read_only": 1 } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:16:12.737743", + "modified": "2022-08-25 11:49:28.155048", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -386,5 +395,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title" } \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index e3a8438d95c2..1bbe57080714 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -174,6 +174,8 @@ frappe.ui.form.on('Stock Entry', { if(!items.length) { items = frm.doc.items; } + + mr.work_order = frm.doc.work_order; items.forEach(function(item) { var mr_item = frappe.model.add_child(mr, 'items'); mr_item.item_code = item.item_code; From c645995ae347a0ef52ad2f1cfa61b499bbc6403f Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 24 Aug 2022 15:52:00 +0200 Subject: [PATCH 066/898] chore: update french translation (cherry picked from commit 264f98af140cb8b73c417ef18bca16b838926927) --- erpnext/translations/fr.csv | 104 +++++++++++++++++------------------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index dbc319476be8..1869411a1f0c 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -329,11 +329,11 @@ Average Rate,Prix moyen, Avg Daily Outgoing,Moy Quotidienne Sortante, Avg. Buying Price List Rate,Moyenne de la liste de prix d'achat, Avg. Selling Price List Rate,Prix moyen de la liste de prix de vente, -Avg. Selling Rate,Moy. Taux de vente, +Avg. Selling Rate,Moy. prix de vente, BOM,Nomenclature, BOM Browser,Explorateur Nomenclature, BOM No,N° Nomenclature, -BOM Rate,Valeur nomenclature, +BOM Rate,Cout nomenclature, BOM Stock Report,Rapport de Stock des nomenclatures, BOM and Manufacturing Quantity are required,Nomenclature et quantité de production sont nécessaires, BOM does not contain any stock item,Nomenclature ne contient aucun article en stock, @@ -561,9 +561,9 @@ Colour,Couleur, Combined invoice portion must equal 100%,La portion combinée de la facture doit être égale à 100%, Commercial,Commercial, Commission,Commission, -Commission Rate %,Taux de commission%, +Commission Rate %,Pourcentage de commission, Commission on Sales,Commission sur les ventes, -Commission rate cannot be greater than 100,Taux de commission ne peut pas être supérieure à 100, +Commission rate cannot be greater than 100,Pourcentage de commission ne peut pas être supérieure à 100, 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é, @@ -1072,7 +1072,7 @@ For Warehouse is required before Submit,Pour l’Entrepôt est requis avant de V "For an item {0}, quantity must be negative number","Pour l'article {0}, la quantité doit être un nombre négatif", "For an item {0}, quantity must be positive number","Pour un article {0}, la quantité doit être un nombre positif", "For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry","Pour la carte de travail {0}, vous pouvez uniquement saisir une entrée de stock de type "Transfert d'article pour fabrication".", -"For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included","Pour la ligne {0} dans {1}. Pour inclure {2} dans le prix de l'Article, les lignes {3} doivent également être incluses", +"For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included","Pour la ligne {0} dans {1}. Pour inclure {2} dans le prix de l'article, les lignes {3} doivent également être incluses", For row {0}: Enter Planned Qty,Pour la ligne {0}: entrez la quantité planifiée, "For {0}, only credit accounts can be linked against another debit entry","Pour {0}, seuls les comptes de crédit peuvent être liés avec une autre écriture de débit", "For {0}, only debit accounts can be linked against another credit entry","Pour {0}, seuls les comptes de débit peuvent être liés avec une autre écriture de crédit", @@ -1235,7 +1235,7 @@ ITC Reversed,CTI inversé, Identifying Decision Makers,Identifier les décideurs, "If Auto Opt In is checked, then the customers will be automatically linked with the concerned Loyalty Program (on save)","Si l'option adhésion automatique est cochée, les clients seront automatiquement liés au programme de fidélité concerné (après l'enregistrement)", "If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.","Si plusieurs Règles de Prix continuent de prévaloir, les utilisateurs sont invités à définir manuellement la priorité pour résoudre les conflits.", -"If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.","Si la règle de tarification sélectionnée est définie pour le «Prix Unitaire», elle écrase la liste de prix. Le prix unitaire de la règle de tarification est le prix unitaire final, donc aucune autre réduction supplémentaire ne doit être appliquée. Par conséquent, dans les transactions telles que la commande client, la commande d'achat, etc., elle sera récupérée dans le champ ""Prix Unitaire"", plutôt que dans le champ ""Tarif de la liste de prix"".", +"If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.","Si la règle de tarification sélectionnée est définie pour le 'Prix Unitaire', elle écrase la liste de prix. Le prix unitaire de la règle de tarification est le prix unitaire final, donc aucune autre réduction supplémentaire ne doit être appliquée. Par conséquent, dans les transactions telles que la commande client, la commande d'achat, etc., elle sera récupérée dans le champ 'Prix Unitaire', plutôt que dans le champ 'Tarif de la liste de prix'.", "If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.","Si deux Règles de Prix ou plus sont trouvées sur la base des conditions ci-dessus, une Priorité est appliquée. La Priorité est un nombre compris entre 0 et 20 avec une valeur par défaut de zéro (vide). Les nombres les plus élévés sont prioritaires s'il y a plusieurs Règles de Prix avec mêmes conditions.", "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.", @@ -1269,7 +1269,7 @@ Income,Revenus, Income Account,Compte de Produits, Income Tax,Impôt sur le revenu, Incoming,Entrant, -Incoming Rate,Taux d'Entrée, +Incoming Rate,Prix d'Entrée, Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.,Nombre incorrect d'Écritures Grand Livre trouvées. Vous avez peut-être choisi le mauvais Compte dans la transaction., Increment cannot be 0,Incrément ne peut pas être 0, Increment for Attribute {0} cannot be 0,Incrément pour l'Attribut {0} ne peut pas être 0, @@ -1365,7 +1365,7 @@ Item Variants,Variantes de l'Article, Item Variants updated,Variantes d'article mises à jour, Item has variants.,L'article a des variantes., Item must be added using 'Get Items from Purchase Receipts' button,L'article doit être ajouté à l'aide du bouton 'Obtenir des éléments de Reçus d'Achat', -Item valuation rate is recalculated considering landed cost voucher amount,Le taux d'évaluation de l'article est recalculé compte tenu du montant du bon de prix au débarquement, +Item valuation rate is recalculated considering landed cost voucher amount,Le taux de valorisation de l'article est recalculé compte tenu du montant du bon de prix au débarquement, Item variant {0} exists with same attributes,La variante de l'article {0} existe avec les mêmes caractéristiques, Item {0} does not exist,Article {0} n'existe pas, Item {0} does not exist in the system or has expired,L'article {0} n'existe pas dans le système ou a expiré, @@ -2147,7 +2147,7 @@ Previous Financial Year is not closed,L’Exercice Financier Précédent n’est Price,Prix, Price List,Liste de prix, Price List Currency not selected,Devise de la Liste de Prix non sélectionnée, -Price List Rate,Taux de la Liste des Prix, +Price List Rate,Prix de la Liste des Prix, Price List master.,Données de Base des Listes de Prix, Price List must be applicable for Buying or Selling,La Liste de Prix doit être applicable pour les Achats et les Ventes, Price List {0} is disabled or does not exist,Liste des Prix {0} est désactivée ou n'existe pas, @@ -2288,8 +2288,8 @@ Quotations: ,Devis :, Quotes to Leads or Customers.,Devis de Prospects 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,Taux, -Rate:,Taux:, +Rate,Prix, +Rate:,Prix:, Rating,Évaluation, Raw Material,Matières Premières, Raw Materials,Matières premières, @@ -2426,7 +2426,7 @@ 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}, -Row # {0}: Rate cannot be greater than the rate used in {1} {2},Ligne # {0}: Le Taux ne peut pas être supérieur au taux utilisé dans {1} {2}, +Row # {0}: Rate cannot be greater than the rate used in {1} {2},Ligne # {0}: Le prix ne peut pas être supérieur au prix utilisé dans {1} {2}, Row # {0}: Serial No is mandatory,Ligne # {0} : N° de série est obligatoire, Row # {0}: Serial No {1} does not match with {2} {3},Ligne # {0} : N° de série {1} ne correspond pas à {2} {3}, Row #{0} (Payment Table): Amount must be negative,Row # {0} (Table de paiement): le montant doit être négatif, @@ -2434,7 +2434,7 @@ Row #{0} (Payment Table): Amount must be positive,Ligne #{0} (Table de paiement) Row #{0}: Account {1} does not belong to company {2},Ligne # {0}: le compte {1} n'appartient pas à la société {2}, Row #{0}: Allocated Amount cannot be greater than outstanding amount.,Ligne # {0}: montant attribué ne peut pas être supérieur au montant en souffrance., "Row #{0}: Asset {1} cannot be submitted, it is already {2}","Ligne #{0} : L’Actif {1} ne peut pas être soumis, il est déjà {2}", -Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.,Ligne n ° {0}: impossible de définir le tarif si le montant est supérieur au montant facturé pour l'élément {1}., +Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.,Ligne n ° {0}: impossible de définir le prix si le montant est supérieur au montant facturé pour l'élément {1}., Row #{0}: Clearance date {1} cannot be before Cheque Date {2},Ligne #{0} : Date de compensation {1} ne peut pas être antérieure à la Date du Chèque {2}, Row #{0}: Duplicate entry in References {1} {2},Ligne # {0}: entrée en double dans les références {1} {2}, Row #{0}: Expected Delivery Date cannot be before Purchase Order Date,Ligne {0}: la date de livraison prévue ne peut pas être avant la date de commande, @@ -2898,7 +2898,7 @@ Sync has been temporarily disabled because maximum retries have been exceeded,La 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 %,Taux de TDS%, +TDS Rate %,Pourcentage de TDS, Tap items to add them here,Choisissez des articles pour les ajouter ici, Target,Cible, Target ({}),Cible ({}), @@ -3190,7 +3190,7 @@ Update Print Format,Mettre à Jour le Format d'Impression, Update Response,Mettre à jour la réponse, Update bank payment dates with journals.,Mettre à jour les dates de paiement bancaires avec les journaux., Update in progress. It might take a while.,Mise à jour en cours. Ça peut prendre un moment., -Update rate as per last purchase,Taux de mise à jour selon le dernier achat, +Update rate as per last purchase,Mettre à jour les prix selon le dernier prix achat, Update stock must be enable for the purchase invoice {0},La mise à jour du stock doit être activée pour la facture d'achat {0}, 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)., @@ -3326,7 +3326,6 @@ You are not authorized to add or update entries before {0},Vous n'êtes pas auto You are not authorized to approve leaves on Block Dates,Vous n'êtes pas autorisé à approuver les congés sur les Dates Bloquées, You are not authorized to set Frozen value,Vous n'êtes pas autorisé à définir des valeurs gelées, You are not present all day(s) between compensatory leave request days,Vous n'êtes pas présent(e) tous les jours vos demandes de congé compensatoire, -You can not change rate if BOM mentioned agianst any item,Vous ne pouvez pas modifier le taux si la nomenclature est mentionnée pour un article, You can not enter current voucher in 'Against Journal Entry' column,Vous ne pouvez pas entrer le bon actuel dans la colonne 'Pour l'Écriture de Journal', You can only have Plans with the same billing cycle in a Subscription,Vous ne pouvez avoir que des plans ayant le même cycle de facturation dans le même abonnement, You can only redeem max {0} points in this order.,Vous pouvez uniquement échanger un maximum de {0} points dans cet commande., @@ -3971,7 +3970,7 @@ 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 taux ou la remise est requis pour la remise de prix., +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, @@ -4348,7 +4347,7 @@ Valid Upto date cannot be before Valid From date,La date de validité valide ne Valid From date not in Fiscal Year {0},Date de début de validité non comprise dans l'exercice {0}, Valid Upto date not in Fiscal Year {0},Valable jusqu'à la date hors exercice {0}, Group Roll No,Groupe Roll Non, -Maintain Same Rate Throughout Sales Cycle,Maintenir le Même Taux Durant le Cycle de Vente, +Maintain Same Rate Throughout Sales Cycle,Maintenir le même prix Durant le Cycle de Vente, "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}.","Ligne {1}: la quantité ({0}) ne peut pas être une fraction. Pour autoriser cela, désactivez «{2}» dans UdM {3}.", Must be Whole Number,Doit être un Nombre Entier, Please setup Razorpay Plan ID,Veuillez configurer l'ID du plan Razorpay, @@ -4961,7 +4960,7 @@ Threshold for Suggestion,Seuil de suggestion, System will notify to increase or decrease quantity or amount ,Le système notifiera d'augmenter ou de diminuer la quantité ou le montant, "Higher the number, higher the priority","Plus le nombre est grand, plus la priorité est haute", Apply Multiple Pricing Rules,Appliquer plusieurs règles de tarification, -Apply Discount on Rate,Appliquer une réduction sur le taux, +Apply Discount on Rate,Appliquer une réduction sur le prix, Validate Applied Rule,Valider la règle appliquée, Rule Description,Description de la règle, Pricing Rule Help,Aide pour les Règles de Tarification, @@ -5050,19 +5049,18 @@ End date of current invoice's period,Date de fin de la période de facturation e Update Auto Repeat Reference,Mettre à jour la référence de répétition automatique, Purchase Invoice Advance,Avance sur Facture d’Achat, Purchase Invoice Item,Article de la Facture d'Achat, -Quantity and Rate,Quantité et Taux, +Quantity and Rate,Quantité et Prix, Received Qty,Qté Reçue, Accepted Qty,Quantité acceptée, Rejected Qty,Qté Rejetée, UOM Conversion Factor,Facteur de Conversion de l'UDM, Discount on Price List Rate (%),Remise sur la Liste des Prix (%), Price List Rate (Company Currency),Taux de la Liste de Prix (Devise Société), -Rate ,Taux, Rate (Company Currency),Prix (Devise Société), Amount (Company Currency),Montant (Devise de la Société), Is Free Item,Est un article gratuit, -Net Rate,Taux Net, -Net Rate (Company Currency),Taux Net (Devise Société), +Net Rate,Prix Net, +Net Rate (Company Currency),Prix Net (Devise Société), Net Amount (Company Currency),Montant Net (Devise Société), Item Tax Amount Included in Value,Montant de la taxe incluse dans la valeur, Landed Cost Voucher Amount,Montant de la Référence de Coût au Débarquement, @@ -5080,7 +5078,7 @@ Enable Deferred Expense,Activer les frais reportés, Service Start Date,Date de début du service, Service End Date,Date de fin du service, Allow Zero Valuation Rate,Autoriser un Taux de Valorisation Égal à Zéro, -Item Tax Rate,Taux de la Taxe sur l'Article, +Item Tax Rate,Prix de la Taxe sur l'Article, Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,La table de détails de taxe est récupérée depuis les données de base de l'article comme une chaîne de caractères et stockée dans ce champ. Elle est utilisée pour les Taxes et Frais., Purchase Order Item,Article de la Commande d'Achat, Purchase Receipt Detail,Détail du reçu d'achat, @@ -5098,8 +5096,8 @@ On Previous Row Amount,Le Montant de la Rangée Précédente, On Previous Row Total,Le Total de la Rangée Précédente, On Item Quantity,Sur quantité d'article, Reference Row #,Ligne de Référence #, -Is this Tax included in Basic Rate?,Cette Taxe est-elle incluse dans le Taux de Base ?, -"If checked, the tax amount will be considered as already included in the Print Rate / Print Amount","Si cochée, le montant de la taxe sera considéré comme déjà inclus dans le Taux d'Impression / Prix d'Impression", +Is this Tax included in Basic Rate?,Cette Taxe est-elle incluse dans le Prix de Base ?, +"If checked, the tax amount will be considered as already included in the Print Rate / Print Amount","Si cochée, le montant de la taxe sera considéré comme déjà inclus dans le Taux / Prix des documents (PDF, impressions)", Account Head,Compte Principal, Tax Amount After Discount Amount,Montant de la Taxe après Remise, Item Wise Tax Detail ,Détail de la taxe de l'article Wise, @@ -5147,7 +5145,7 @@ Accounting Details,Détails Comptabilité, Debit To,Débit Pour, Is Opening Entry,Est Écriture Ouverte, C-Form Applicable,Formulaire-C Applicable, -Commission Rate (%),Taux de Commission (%), +Commission Rate (%),Pourcentage de Commission, Sales Team1,Équipe des Ventes 1, Against Income Account,Pour le Compte de Produits, Sales Invoice Advance,Avance sur Facture de Vente, @@ -5157,9 +5155,9 @@ Customer's Item Code,Code de l'Article du Client, Brand Name,Nom de la Marque, Qty as per Stock UOM,Qté par UDM du Stock, Discount and Margin,Remise et Marge, -Rate With Margin,Tarif Avec Marge, -Discount (%) on Price List Rate with Margin,Remise (%) sur le Tarif de la Liste de Prix avec la Marge, -Rate With Margin (Company Currency),Taux avec marge (devise de l'entreprise), +Rate With Margin,Prix Avec Marge, +Discount (%) on Price List Rate with Margin,Remise (%) sur le prix de la Liste de Prix avec la Marge, +Rate With Margin (Company Currency),Prix avec marge (devise de l'entreprise), Delivered By Supplier,Livré par le Fournisseur, Deferred Revenue,Produits comptabilisés d'avance, Deferred Revenue Account,Compte de produits comptabilisés d'avance, @@ -5721,7 +5719,7 @@ Contact Mobile No,N° de Portable du Contact, Enter name of campaign if source of enquiry is campaign,Entrez le nom de la campagne si la source de l'enquête est une campagne, Opportunity Date,Date d'Opportunité, Opportunity Item,Article de l'Opportunité, -Basic Rate,Taux de Base, +Basic Rate,Prix de Base, Stage Name,Nom de scène, Social Media Post,Publication sur les réseaux sociaux, Post Status,Statut du message, @@ -7218,8 +7216,8 @@ Qty Consumed Per Unit,Qté Consommée Par Unité, Include Item In Manufacturing,Inclure l'article dans la fabrication, BOM Item,Article de la nomenclature, Item operation,Opération de l'article, -Rate & Amount,Taux et Montant, -Basic Rate (Company Currency),Taux de Base (Devise de la Société ), +Rate & Amount,Prix et Montant, +Basic Rate (Company Currency),Prix de Base (Devise de la Société ), Scrap %,% de Rebut, Original Item,Article original, BOM Operation,Opération de la nomenclature (gamme), @@ -7464,8 +7462,8 @@ Website Attribute,Attribut de site Web, Attribute,Attribut, Website Filter Field,Champ de filtrage de site Web, Activity Cost,Coût de l'Activité, -Billing Rate,Taux de Facturation, -Costing Rate,Taux des Coûts, +Billing Rate,Prix de Facturation, +Costing Rate,Tarifs des Coûts, title,Titre, Projects User,Utilisateur/Intervenant Projets, Default Costing Rate,Coût de Revient par Défaut, @@ -7963,7 +7961,7 @@ Reserved Quantity,Quantité Réservée, Actual Quantity,Quantité Réelle, Requested Quantity,Quantité Demandée, Reserved Qty for sub contract,Qté réservée pour le sous-contrat, -Moving Average Rate,Taux Mobile Moyen, +Moving Average Rate,Prix moyen pondéré, FCFS Rate,Montant PAPS, Customs Tariff Number,Tarifs Personnalisés, Tariff Number,Tarif, @@ -8311,7 +8309,7 @@ Total Additional Costs,Total des Coûts Additionnels, Customer or Supplier Details,Détails du Client ou du Fournisseur, Per Transferred,Par transféré, Stock Entry Detail,Détails de l'Écriture de Stock, -Basic Rate (as per Stock UOM),Taux de base (comme l’UDM du Stock), +Basic Rate (as per Stock UOM),Prix de base (comme l’UDM du Stock), Basic Amount,Montant de Base, Additional Cost,Frais Supplémentaire, Serial No / Batch,N° de Série / Lot, @@ -8323,7 +8321,7 @@ Stock Entry Child,Entrée de stock enfant, PO Supplied Item,PO article fourni, Reference Purchase Receipt,Reçu d'achat de référence, Stock Ledger Entry,Écriture du Livre d'Inventaire, -Outgoing Rate,Taux Sortant, +Outgoing Rate,Prix Sortant, Actual Qty After Transaction,Qté Réelle Après Transaction, Stock Value Difference,Différence de Valeur du Sock, Stock Queue (FIFO),File d'Attente du Stock (FIFO), @@ -8509,7 +8507,7 @@ Item Price Stock,Stock et prix de l'article, Item Prices,Prix des Articles, Item Shortage Report,Rapport de Rupture de Stock d'Article, Item Variant Details,Détails de la variante de l'article, -Item-wise Price List Rate,Taux de la Liste des Prix par Article, +Item-wise Price List Rate,Prix de la Liste des Prix par Article, Item-wise Purchase History,Historique d'Achats par Article, Item-wise Purchase Register,Registre des Achats par Article, Item-wise Sales History,Historique des Ventes par Article, @@ -8561,7 +8559,7 @@ Sales Partner Target Variance based on Item Group,Variance cible du partenaire c Sales Partner Transaction Summary,Récapitulatif des transactions du partenaire commercial, Sales Partners Commission,Commission des Partenaires de Vente, Invoiced Amount (Exclusive Tax),Montant facturé (taxe exclusive), -Average Commission Rate,Taux Moyen de la Commission, +Average Commission Rate,Coût Moyen de la Commission, Sales Payment Summary,Résumé du paiement des ventes, Sales Person Commission Summary,Récapitulatif de la commission des ventes, Sales Person Target Variance Based On Item Group,Écart cible du commercial basé sur le groupe de postes, @@ -8815,7 +8813,7 @@ Generate New Invoices Past Due Date,Générer de nouvelles factures en retard, New invoices will be generated as per schedule even if current invoices are unpaid or past due date,"De nouvelles factures seront générées selon le calendrier, même si les factures actuelles sont impayées ou en retard", Document Type ,Type de document, Subscription Price Based On,Prix d'abonnement basé sur, -Fixed Rate,Taux fixe, +Fixed Rate,Tarif fixe, Based On Price List,Basé sur la liste de prix, Monthly Rate,Tarif mensuel, Cancel Subscription After Grace Period,Annuler l'abonnement après la période de grâce, @@ -8886,10 +8884,6 @@ Practitioner Name,Nom du praticien, Enter a name for the Clinical Procedure Template,Entrez un nom pour le modèle de procédure clinique, Set the Item Code which will be used for billing the Clinical Procedure.,Définissez le code article qui sera utilisé pour facturer la procédure clinique., Select an Item Group for the Clinical Procedure Item.,Sélectionnez un groupe d'articles pour l'article de procédure clinique., -Clinical Procedure Rate,Taux de procédure clinique, -Check this if the Clinical Procedure is billable and also set the rate.,Cochez cette case si la procédure clinique est facturable et définissez également le tarif., -Check this if the Clinical Procedure utilises consumables. Click ,Vérifiez ceci si la procédure clinique utilise des consommables. Cliquez sur, - to know more,en savoir plus, "You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.","Vous pouvez également définir le service médical du modèle. Après avoir enregistré le document, un élément sera automatiquement créé pour facturer cette procédure clinique. Vous pouvez ensuite utiliser ce modèle lors de la création de procédures cliniques pour les patients. Les modèles vous évitent de remplir des données redondantes à chaque fois. Vous pouvez également créer des modèles pour d'autres opérations telles que des tests de laboratoire, des séances de thérapie, etc.", Descriptive Test Result,Résultat du test descriptif, Allow Blank,Autoriser le blanc, @@ -9033,7 +9027,7 @@ Work In Progress Warehouse,Entrepôt de travaux en cours, This Warehouse will be auto-updated in the Work In Progress Warehouse field of Work Orders.,Cet entrepôt sera mis à jour automatiquement dans le champ Entrepôt de travaux en cours des bons de travail., Finished Goods Warehouse,Entrepôt de produits finis, This Warehouse will be auto-updated in the Target Warehouse field of Work Order.,Cet entrepôt sera mis à jour automatiquement dans le champ Entrepôt cible de l'ordre de fabrication. -"If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials.","Si coché, le coût de la nomenclature sera automatiquement mis à jour en fonction du taux de valorisation / tarif tarifaire / dernier taux d'achat des matières premières.", +"If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials.","Si coché, le coût de la nomenclature sera automatiquement mis à jour en fonction du taux de valorisation / prix de la liste prix / dernier prix d'achat des matières premières.", Source Warehouses (Optional),Entrepôts d'origine (facultatif), "System will pickup the materials from the selected warehouses. If not specified, system will create material request for purchase.","Le système ramassera les matériaux dans les entrepôts sélectionnés. S'il n'est pas spécifié, le système créera une demande de matériel pour l'achat.", Lead Time,Délai de mise en œuvre, @@ -9107,7 +9101,7 @@ MAT-PR-RET-.YYYY.-,MAT-PR-RET-.YYYY.-, Track this Purchase Receipt against any Project,Suivre ce reçu d'achat par rapport à n'importe quel projet, Please Select a Supplier,Veuillez sélectionner un fournisseur, Add to Transit,Ajouter à Transit, -Set Basic Rate Manually,Définir manuellement le taux de base, +Set Basic Rate Manually,Définir manuellement le prix de base, "By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a ","Par défaut, le nom de l'article est défini selon le code d'article entré. Si vous souhaitez que les éléments soient nommés par un", Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.,Définissez un entrepôt par défaut pour les mouvements de stock. Ce sera récupéré dans l'entrepôt par défaut dans la base d'articles., "This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.","Cela permettra aux articles en stock d'être affichés avec des valeurs négatives. L'utilisation de cette option dépend de votre cas d'utilisation. Lorsque cette option n'est pas cochée, le système avertit avant d'entraver une transaction entraînant un stock négatif.", @@ -9495,7 +9489,7 @@ Normal Range: ,Plage normale:, Row #{0}: Check Out datetime cannot be less than Check In datetime,Ligne n ° {0}: la date de sortie ne peut pas être inférieure à la date de sortie, "Missing required details, did not create Inpatient Record","Détails requis manquants, n'a pas créé de dossier d'hospitalisation", Unbilled Invoices,Factures non facturées, -Standard Selling Rate should be greater than zero.,Le taux de vente standard doit être supérieur à zéro., +Standard Selling Rate should be greater than zero.,Le prix de vente standard doit être supérieur à zéro., Conversion Factor is mandatory,Le facteur de conversion est obligatoire, Row #{0}: Conversion Factor is mandatory,Ligne n ° {0}: le facteur de conversion est obligatoire, Sample Quantity cannot be negative or 0,La quantité d'échantillon ne peut pas être négative ou 0, @@ -9559,7 +9553,7 @@ The Request for Quotation can be accessed by clicking on the following button,La Regards,Cordialement, Please click on the following button to set your new password,Veuillez cliquer sur le bouton suivant pour définir votre nouveau mot de passe, Update Password,Mettre à jour le mot de passe, -Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {},Ligne n ° {}: le taux de vente de l'article {} est inférieur à son {}. La vente {} doit être au moins {}, +Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {},Ligne n ° {}: le prix de vente de l'article {} est inférieur à son {}. La vente {} doit être au moins {}, You can alternatively disable selling price validation in {} to bypass this validation.,Vous pouvez également désactiver la validation du prix de vente dans {} pour contourner cette validation., Invalid Selling Price,Prix de vente invalide, Address needs to be linked to a Company. Please add a row for Company in the Links table.,L'adresse doit être liée à une entreprise. Veuillez ajouter une ligne pour Entreprise dans le tableau Liens., @@ -9581,7 +9575,7 @@ Only select this if you have set up the Cash Flow Mapper documents,Sélectionnez Payment Channel,Canal de paiement, Is Purchase Order Required for Purchase Invoice & Receipt Creation?,Une Commande d'Achat est-il requis pour la création de factures d'achat et de reçus?, Is Purchase Receipt Required for Purchase Invoice Creation?,Un reçu d'achat est-il requis pour la création d'une facture d'achat?, -Maintain Same Rate Throughout the Purchase Cycle,Maintenir le même taux tout au long du cycle d'achat, +Maintain Same Rate Throughout the Purchase Cycle,Maintenir les même prix tout au long du cycle d'achat, Allow Item To Be Added Multiple Times in a Transaction,Autoriser l'ajout d'un article plusieurs fois dans une transaction, Suppliers,Fournisseurs, Send Emails to Suppliers,Envoyer des e-mails aux fournisseurs, @@ -9633,7 +9627,7 @@ Plan operations X days in advance,Planifier les opérations X jours à l'avance, Time Between Operations (Mins),Temps entre les opérations (minutes), Default: 10 mins,Par défaut: 10 minutes, Overproduction for Sales and Work Order,Surproduction pour les ventes et les bons de travail, -"Update BOM cost automatically via scheduler, based on the latest Valuation Rate/Price List Rate/Last Purchase Rate of raw materials","Mettre à jour automatiquement le coût de la nomenclature via le planificateur, en fonction du dernier taux de valorisation / tarif tarifaire / dernier taux d'achat de matières premières", +"Update BOM cost automatically via scheduler, based on the latest Valuation Rate/Price List Rate/Last Purchase Rate of raw materials","Mettre à jour automatiquement le coût de la nomenclature via le planificateur, en fonction du dernier taux de valorisation / prix de la liste de prix / dernier prix d'achat de matières premières", Purchase Order already created for all Sales Order items,Commande d'Achat déjà créé pour tous les articles de commande client, Select Items,Sélectionner des éléments, Against Default Supplier,Contre le fournisseur par défaut, @@ -9641,14 +9635,14 @@ Auto close Opportunity after the no. of days mentioned above,Opportunité de fer Is Sales Order Required for Sales Invoice & Delivery Note Creation?,Une commande client est-elle requise pour la création de factures clients et de bons de livraison?, Is Delivery Note Required for Sales Invoice Creation?,Un bon de livraison est-il nécessaire pour la création de factures de vente?, How often should Project and Company be updated based on Sales Transactions?,À quelle fréquence le projet et l'entreprise doivent-ils être mis à jour en fonction des transactions de vente?, -Allow User to Edit Price List Rate in Transactions,Autoriser l'utilisateur à modifier le tarif tarifaire dans les transactions, +Allow User to Edit Price List Rate in Transactions,Autoriser l'utilisateur à modifier le prix de la liste prix dans les transactions, Allow Item to Be Added Multiple Times in a Transaction,Autoriser l'ajout d'un article plusieurs fois dans une transaction, Allow Multiple Sales Orders Against a Customer's Purchase Order,Autoriser plusieurs commandes client par rapport à la commande d'achat d'un client, -Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Valider le prix de vente de l'article par rapport au taux d'achat ou au taux de valorisation, +Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Valider le prix de vente de l'article par rapport au prix d'achat ou au taux de valorisation, Hide Customer's Tax ID from Sales Transactions,Masquer le numéro d'identification fiscale du client dans les transactions de vente, "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.","Le pourcentage que vous êtes autorisé à recevoir ou à livrer plus par rapport à la quantité commandée. Par exemple, si vous avez commandé 100 unités et que votre allocation est de 10%, vous êtes autorisé à recevoir 110 unités.", Action If Quality Inspection Is Not Submitted,Action si l'inspection qualité n'est pas soumise, -Auto Insert Price List Rate If Missing,Taux de liste de prix d'insertion automatique s'il est manquant, +Auto Insert Price List Rate If Missing,Insérer automatiquement le prix dand liste de prix s'il est manquant, Automatically Set Serial Nos Based on FIFO,Définir automatiquement les numéros de série en fonction de FIFO, Set Qty in Transactions Based on Serial No Input,Définir la quantité dans les transactions en fonction du numéro de série, Raise Material Request When Stock Reaches Re-order Level,Augmenter la demande d'article lorsque le stock atteint le niveau de commande, @@ -9722,7 +9716,7 @@ No Inpatient Record found against patient {0},Aucun dossier d'hospitalisation tr An Inpatient Medication Order {0} against Patient Encounter {1} already exists.,Une ordonnance de médicament pour patients hospitalisés {0} contre rencontre avec un patient {1} existe déjà., Allow In Returns,Autoriser les retours, Hide Unavailable Items,Masquer les éléments non disponibles, -Apply Discount on Discounted Rate,Appliquer une remise sur un tarif réduit, +Apply Discount on Discounted Rate,Appliquer une remise sur un prix réduit, Therapy Plan Template,Modèle de plan de thérapie, Fetching Template Details,Récupération des détails du modèle, Linked Item Details,Détails de l'élément lié, From 3aec1175df4d4b6bbd4705043c8f3f1c4cb598f7 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 24 Aug 2022 21:20:23 +0200 Subject: [PATCH 067/898] chore: update fr translation (cherry picked from commit 1f6f2747d49604bd3707d9017a219989d8df57b8) --- 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 1869411a1f0c..b5de5784c510 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -658,7 +658,7 @@ Create Invoice,Créer une facture, Create Invoices,Créer des factures, Create Job Card,Créer une carte de travail, Create Journal Entry,Créer une entrée de journal, -Create Lead,Créer une piste, +Create Lead,Créer un Prospect, Create Leads,Créer des Prospects, Create Maintenance Visit,Créer une visite de maintenance, Create Material Request,Créer une demande de matériel, @@ -3881,7 +3881,7 @@ Only expired allocation can be cancelled,Seule l'allocation expirée peut être Only users with the {0} role can create backdated leave applications,Seuls les utilisateurs avec le rôle {0} peuvent créer des demandes de congé antidatées, Open,Ouvert, Open Contact,Contact ouvert, -Open Lead,Ouvrir le fil, +Open Lead,Ouvrir le Prospect, 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, From cb6cbf78188c1c77d1ab29c5c683578ab594c4f5 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 24 Aug 2022 21:29:22 +0200 Subject: [PATCH 068/898] chore: update fr translation (cherry picked from commit 299da5d596f29d195c224af3021edd26f752d390) --- erpnext/translations/fr.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index b5de5784c510..b2074618a6fc 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -9921,4 +9921,3 @@ Enable Reviews and Ratings,Activer les avis et notes Enable Recommendations,Activer les recommendations Item Search Settings,Paramétrage de la recherche d'article Purchase demande,Demande de materiel -Calendar,Calendier From f0f7afa6699bbf61229970923ef0664b242b9fec Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 24 Aug 2022 12:29:15 +0530 Subject: [PATCH 069/898] fix: gl entries for asset repair (cherry picked from commit b4a2eb2e65b0706ba7483215040855297b9b9ff8) --- erpnext/assets/doctype/asset/test_asset.py | 14 +- .../doctype/asset_repair/asset_repair.js | 2 +- .../doctype/asset_repair/asset_repair.json | 6 +- .../doctype/asset_repair/asset_repair.py | 118 +++++++++++------ .../doctype/asset_repair/test_asset_repair.py | 122 +++++++++++++++++- erpnext/setup/doctype/company/company.json | 10 +- 6 files changed, 208 insertions(+), 64 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 986b7001ff6d..132840e38c2c 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1454,12 +1454,14 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass return item -def set_depreciation_settings_in_company(): - company = frappe.get_doc("Company", "_Test Company") - company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC" - company.depreciation_expense_account = "_Test Depreciations - _TC" - company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" - company.depreciation_cost_center = "_Test Cost Center - _TC" +def set_depreciation_settings_in_company(company=None): + if not company: + company = "_Test Company" + company = frappe.get_doc("Company", company) + company.accumulated_depreciation_account = "_Test Accumulated Depreciations - " + company.abbr + company.depreciation_expense_account = "_Test Depreciations - " + company.abbr + company.disposal_account = "_Test Gain/Loss on Asset Disposal - " + company.abbr + company.depreciation_cost_center = "Main - " + company.abbr company.save() # Enable booking asset depreciation entry automatically diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index f5e4e723b44c..f9ed2cc34487 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -76,7 +76,7 @@ frappe.ui.form.on('Asset Repair Consumed Item', { 'warehouse': frm.doc.warehouse, 'qty': item.consumed_quantity, 'serial_no': item.serial_no, - 'company': frm.doc.company + 'company': frm.doc.company, }; frappe.call({ diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index ba3189887cec..accb5bf54b59 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -238,7 +238,6 @@ "no_copy": 1 }, { - "depends_on": "eval:!doc.__islocal", "fieldname": "purchase_invoice", "fieldtype": "Link", "label": "Purchase Invoice", @@ -257,6 +256,7 @@ "fieldname": "stock_entry", "fieldtype": "Link", "label": "Stock Entry", + "no_copy": 1, "options": "Stock Entry", "read_only": 1 } @@ -264,10 +264,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-25 13:14:38.307723", + "modified": "2022-08-16 15:55:25.023471", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -303,6 +304,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "asset_name", "track_changes": 1, "track_seen": 1 diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 5bf6011cf805..b4316ced62f7 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -1,11 +1,11 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - import frappe from frappe import _ from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours +import erpnext from erpnext.accounts.general_ledger import make_gl_entries from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.controllers.accounts_controller import AccountsController @@ -17,7 +17,7 @@ def validate(self): self.update_status() if self.get("stock_items"): - self.set_total_value() + self.set_stock_items_cost() self.calculate_total_repair_cost() def update_status(self): @@ -26,7 +26,7 @@ def update_status(self): else: self.asset_doc.set_status() - def set_total_value(self): + def set_stock_items_cost(self): for item in self.get("stock_items"): item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity) @@ -66,6 +66,7 @@ def before_cancel(self): if self.get("capitalize_repair_cost"): self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") self.make_gl_entries(cancel=True) + self.db_set("stock_entry", None) if ( frappe.db.get_value("Asset", self.asset, "calculate_depreciation") and self.increase_in_asset_life @@ -133,6 +134,7 @@ def decrease_stock_quantity(self): "qty": stock_item.consumed_quantity, "basic_rate": stock_item.valuation_rate, "serial_no": stock_item.serial_no, + "cost_center": self.cost_center, }, ) @@ -142,34 +144,61 @@ def decrease_stock_quantity(self): self.db_set("stock_entry", stock_entry.name) def increase_stock_quantity(self): - stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) - stock_entry.flags.ignore_links = True - stock_entry.cancel() + if self.stock_entry: + stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) + stock_entry.flags.ignore_links = True + stock_entry.cancel() def make_gl_entries(self, cancel=False): - if flt(self.repair_cost) > 0: + if flt(self.total_repair_cost) > 0: gl_entries = self.get_gl_entries() make_gl_entries(gl_entries, cancel) def get_gl_entries(self): gl_entries = [] - repair_and_maintenance_account = frappe.db.get_value( - "Company", self.company, "repair_and_maintenance_account" - ) + fixed_asset_account = get_asset_account( "fixed_asset_account", asset=self.asset, company=self.company ) - expense_account = ( + self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account) + self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account) + + return gl_entries + + def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account): + if flt(self.repair_cost) <= 0: + return + + pi_expense_account = ( frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account ) gl_entries.append( self.get_gl_dict( { - "account": expense_account, + "account": fixed_asset_account, + "debit": self.repair_cost, + "debit_in_account_currency": self.repair_cost, + "against": pi_expense_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "against_voucher_type": "Purchase Invoice", + "against_voucher": self.purchase_invoice, + "company": self.company, + }, + item=self, + ) + ) + + gl_entries.append( + self.get_gl_dict( + { + "account": pi_expense_account, "credit": self.repair_cost, "credit_in_account_currency": self.repair_cost, - "against": repair_and_maintenance_account, + "against": fixed_asset_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, @@ -180,17 +209,30 @@ def get_gl_entries(self): ) ) - if self.get("stock_consumption"): - # creating GL Entries for each row in Stock Items based on the Stock Entry created for it - stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) - for item in stock_entry.items: + def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account): + if not (self.get("stock_consumption") and self.get("stock_items")): + return + + # creating GL Entries for each row in Stock Items based on the Stock Entry created for it + stock_entry = frappe.get_doc("Stock Entry", self.stock_entry) + + default_expense_account = None + if not erpnext.is_perpetual_inventory_enabled(self.company): + default_expense_account = frappe.get_cached_value( + "Company", self.company, "default_expense_account" + ) + if not default_expense_account: + frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company)) + + for item in stock_entry.items: + if flt(item.amount) > 0: gl_entries.append( self.get_gl_dict( { - "account": item.expense_account, + "account": item.expense_account or default_expense_account, "credit": item.amount, "credit_in_account_currency": item.amount, - "against": repair_and_maintenance_account, + "against": fixed_asset_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, @@ -201,26 +243,24 @@ def get_gl_entries(self): ) ) - gl_entries.append( - self.get_gl_dict( - { - "account": fixed_asset_account, - "debit": self.total_repair_cost, - "debit_in_account_currency": self.total_repair_cost, - "against": expense_account, - "voucher_type": self.doctype, - "voucher_no": self.name, - "cost_center": self.cost_center, - "posting_date": getdate(), - "against_voucher_type": "Purchase Invoice", - "against_voucher": self.purchase_invoice, - "company": self.company, - }, - item=self, - ) - ) - - return gl_entries + gl_entries.append( + self.get_gl_dict( + { + "account": fixed_asset_account, + "debit": item.amount, + "debit_in_account_currency": item.amount, + "against": item.expense_account, + "voucher_type": self.doctype, + "voucher_no": self.name, + "cost_center": self.cost_center, + "posting_date": getdate(), + "against_voucher_type": "Stock Entry", + "against_voucher": self.stock_entry, + "company": self.company, + }, + item=self, + ) + ) def modify_depreciation_schedule(self): for row in self.asset_doc.finance_books: diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 4e7cf78090b1..6e06f52ac65d 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -6,6 +6,7 @@ import frappe from frappe.utils import flt, nowdate +from erpnext.assets.doctype.asset.asset import get_asset_account from erpnext.assets.doctype.asset.test_asset import ( create_asset, create_asset_data, @@ -125,10 +126,109 @@ def test_purchase_invoice(self): asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1) self.assertTrue(asset_repair.purchase_invoice) - def test_gl_entries(self): - asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1) - gl_entry = frappe.get_last_doc("GL Entry") - self.assertEqual(asset_repair.name, gl_entry.voucher_no) + def test_gl_entries_with_perpetual_inventory(self): + set_depreciation_settings_in_company(company="_Test Company with perpetual inventory") + + asset_category = frappe.get_doc("Asset Category", "Computers") + asset_category.append( + "accounts", + { + "company_name": "_Test Company with perpetual inventory", + "fixed_asset_account": "_Test Fixed Asset - TCP1", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1", + "depreciation_expense_account": "_Test Depreciations - TCP1", + }, + ) + asset_category.save() + + asset_repair = create_asset_repair( + capitalize_repair_cost=1, + stock_consumption=1, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + submit=1, + ) + + gl_entries = frappe.db.sql( + """ + select + account, + sum(debit) as debit, + sum(credit) as credit + from `tabGL Entry` + where + voucher_type='Asset Repair' + and voucher_no=%s + group by + account + """, + asset_repair.name, + as_dict=1, + ) + + self.assertTrue(gl_entries) + + fixed_asset_account = get_asset_account( + "fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company + ) + pi_expense_account = ( + frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account + ) + stock_entry_expense_account = ( + frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account + ) + + expected_values = { + fixed_asset_account: [asset_repair.total_repair_cost, 0], + pi_expense_account: [0, asset_repair.repair_cost], + stock_entry_expense_account: [0, 100], + } + + for d in gl_entries: + self.assertEqual(expected_values[d.account][0], d.debit) + self.assertEqual(expected_values[d.account][1], d.credit) + + def test_gl_entries_with_periodical_inventory(self): + frappe.db.set_value( + "Company", "_Test Company", "default_expense_account", "Cost of Goods Sold - _TC" + ) + asset_repair = create_asset_repair( + capitalize_repair_cost=1, + stock_consumption=1, + submit=1, + ) + + gl_entries = frappe.db.sql( + """ + select + account, + sum(debit) as debit, + sum(credit) as credit + from `tabGL Entry` + where + voucher_type='Asset Repair' + and voucher_no=%s + group by + account + """, + asset_repair.name, + as_dict=1, + ) + + self.assertTrue(gl_entries) + + fixed_asset_account = get_asset_account( + "fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company + ) + default_expense_account = frappe.get_cached_value( + "Company", asset_repair.company, "default_expense_account" + ) + + expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]} + + for d in gl_entries: + self.assertEqual(expected_values[d.account][0], d.debit) + self.assertEqual(expected_values[d.account][1], d.credit) def test_increase_in_asset_life(self): asset = create_asset(calculate_depreciation=1, submit=1) @@ -160,7 +260,7 @@ def create_asset_repair(**args): if args.asset: asset = args.asset else: - asset = create_asset(is_existing_asset=1, submit=1) + asset = create_asset(is_existing_asset=1, submit=1, company=args.company) asset_repair = frappe.new_doc("Asset Repair") asset_repair.update( { @@ -192,7 +292,7 @@ def create_asset_repair(**args): if args.submit: asset_repair.repair_status = "Completed" - asset_repair.cost_center = "_Test Cost Center - _TC" + asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center") if args.stock_consumption: stock_entry = frappe.get_doc( @@ -204,6 +304,8 @@ def create_asset_repair(**args): "t_warehouse": asset_repair.warehouse, "item_code": asset_repair.stock_items[0].item_code, "qty": asset_repair.stock_items[0].consumed_quantity, + "basic_rate": args.rate if args.get("rate") is not None else 100, + "cost_center": asset_repair.cost_center, }, ) stock_entry.submit() @@ -213,7 +315,13 @@ def create_asset_repair(**args): asset_repair.repair_cost = 1000 if asset.calculate_depreciation: asset_repair.increase_in_asset_life = 12 - asset_repair.purchase_invoice = make_purchase_invoice().name + pi = make_purchase_invoice( + company=asset.company, + expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"), + cost_center=asset_repair.cost_center, + warehouse=asset_repair.warehouse, + ) + asset_repair.purchase_invoice = pi.name asset_repair.submit() return asset_repair diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index f34ec56dc01a..f087d996ffe0 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -85,7 +85,6 @@ "depreciation_expense_account", "series_for_depreciation_entry", "expenses_included_in_asset_valuation", - "repair_and_maintenance_account", "column_break_40", "disposal_account", "depreciation_cost_center", @@ -234,7 +233,6 @@ "label": "Default Warehouse for Sales Return", "options": "Warehouse" }, - { "fieldname": "country", "fieldtype": "Link", @@ -678,12 +676,6 @@ "fieldtype": "Section Break", "label": "Fixed Asset Defaults" }, - { - "fieldname": "repair_and_maintenance_account", - "fieldtype": "Link", - "label": "Repair and Maintenance Account", - "options": "Account" - }, { "fieldname": "section_break_28", "fieldtype": "Section Break", @@ -709,7 +701,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2022-06-30 18:03:18.701314", + "modified": "2022-08-16 16:09:02.327724", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 4f832678cf59eda09b51956db2282c18a1ee24e0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Aug 2022 12:10:52 +0530 Subject: [PATCH 070/898] chore: fix against account (cherry picked from commit c1f6dd46d186750146697312d0e10e6ea5c86104) --- 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 b4316ced62f7..8758e9c17dbb 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -249,7 +249,7 @@ def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account): "account": fixed_asset_account, "debit": item.amount, "debit_in_account_currency": item.amount, - "against": item.expense_account, + "against": item.expense_account or default_expense_account, "voucher_type": self.doctype, "voucher_no": self.name, "cost_center": self.cost_center, From 4d8ced6c87147d5e7591f97200629a6245cba824 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:29:48 +0530 Subject: [PATCH 071/898] fix: add validation for PO in Stock Entry (backport #31974) (#31975) fix: add validation for PO in Stock Entry (#31974) (cherry picked from commit 8566832dd51bc78d04ae2b0268883b4ae890a98a) Co-authored-by: Sagar Sharma --- erpnext/stock/doctype/stock_entry/stock_entry.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f719c1efda19..c68d1db279db 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -116,6 +116,7 @@ def validate(self): self.validate_warehouse() self.validate_work_order() self.validate_bom() + self.validate_purchase_order() if self.purpose in ("Manufacture", "Repack"): self.mark_finished_and_scrap_items() @@ -946,6 +947,19 @@ def validate_bom(self): item_code = d.original_item or d.item_code validate_bom_no(item_code, d.bom_no) + def validate_purchase_order(self): + if self.purpose == "Send to Subcontractor" and self.get("purchase_order"): + is_old_subcontracting_flow = frappe.db.get_value( + "Purchase Order", self.purchase_order, "is_old_subcontracting_flow" + ) + + if not is_old_subcontracting_flow: + frappe.throw( + _("Please select Subcontracting Order instead of Purchase Order {0}").format( + self.purchase_order + ) + ) + def mark_finished_and_scrap_items(self): if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]): return From c407d1e51ac712dede5a67d4626c8054730b0787 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 25 Aug 2022 15:50:06 +0530 Subject: [PATCH 072/898] fix: restrict party types to Supplier/Customer for AR/AP report (cherry picked from commit 6aa8fd0f7b35d3bed60888b1aefec0d817f1c325) --- .../accounts/report/accounts_receivable/accounts_receivable.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index b713b018f7c7..e86a2e83b8a4 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -726,6 +726,7 @@ def get_sales_invoices_or_customers_based_on_sales_person(self): def prepare_conditions(self): self.qb_selection_filter = [] party_type_field = scrub(self.party_type) + self.qb_selection_filter.append(self.ple.party_type == self.party_type) self.add_common_filters(party_type_field=party_type_field) From 39ff0cc6d885ba31d1361a588fe9430cd009806e Mon Sep 17 00:00:00 2001 From: Solufyin <34390782+Solufyin@users.noreply.github.com> Date: Fri, 26 Aug 2022 11:18:56 +0530 Subject: [PATCH 073/898] fix: Purchase Order creation from Sales Order (cherry picked from commit bd4b4ddd8beae9f9bcaa2e6f46d5690ee2fd64c3) --- 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 8c03cb5b4156..09a9652cca6e 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -892,6 +892,7 @@ def set_missing_values(source, target): target.additional_discount_percentage = 0.0 target.discount_amount = 0.0 target.inter_company_order_reference = "" + target.shipping_rule = "" default_price_list = frappe.get_value("Supplier", supplier, "default_price_list") if default_price_list: @@ -1010,6 +1011,7 @@ def set_missing_values(source, target): target.additional_discount_percentage = 0.0 target.discount_amount = 0.0 target.inter_company_order_reference = "" + target.shipping_rule = "" target.customer = "" target.customer_name = "" target.run_method("set_missing_values") From 9b9df70632d424be65184a9ccfbd68074bc311e0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 26 Aug 2022 13:15:55 +0530 Subject: [PATCH 074/898] chore: remove precision on discount_percentage of Sales Invoice Item (cherry picked from commit c42fef541a37223e1a7878f7b24a98be8d8df1fc) --- .../doctype/sales_invoice_item/sales_invoice_item.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index b417c7de03ff..7cddf123e244 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -282,7 +282,6 @@ "label": "Discount (%) on Price List Rate with Margin", "oldfieldname": "adj_rate", "oldfieldtype": "Float", - "precision": "2", "print_hide": 1 }, { @@ -846,7 +845,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-06-17 05:33:15.335912", + "modified": "2022-08-26 12:06:31.205417", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", From f407c972d10a84950648f321226234ecf0f01035 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 23:15:20 +0530 Subject: [PATCH 075/898] chore: allow return of components in SCO (backport #31994) (#31995) chore: allow return of components in SCO (#31994) chore: allow return of components in sco (cherry picked from commit af5cbc881f03f93d0bc0b057a13e717a457ea6db) Co-authored-by: Sagar Sharma --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- .../subcontracting_order/subcontracting_order.js | 6 +++--- .../subcontracting_order/subcontracting_order.py | 11 +++++++++-- .../subcontracting_order_list.js | 1 + .../test_subcontracting_order.py | 15 ++++++++++++++- .../subcontracting_order_supplied_item.json | 5 ++--- .../subcontracting_receipt.json | 4 ++-- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c68d1db279db..7721efb639f6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2229,7 +2229,7 @@ def get_available_serial_nos(self, stock_entries): return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos))) def update_subcontracting_order_status(self): - if self.subcontracting_order and self.purpose == "Send to Subcontractor": + if self.subcontracting_order and self.purpose in ["Send to Subcontractor", "Material Transfer"]: from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( update_subcontracting_order_status, ) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index c20f8ab6653c..bbc58fe29b2e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -107,9 +107,9 @@ frappe.ui.form.on('Subcontracting Order', { get_materials_from_supplier: function (frm) { let sco_rm_details = []; - if (frm.doc.supplied_items && (frm.doc.per_received == 100)) { + if (frm.doc.supplied_items && frm.doc.per_received > 0) { frm.doc.supplied_items.forEach(d => { - if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) { + if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) { sco_rm_details.push(d.name); } }); @@ -160,7 +160,7 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll var me = this; if (doc.docstatus == 1) { - if (doc.status != 'Completed') { + if (!['Closed', 'Completed'].includes(doc.status)) { if (flt(doc.per_received) < 100) { cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create')); if (me.has_unsupplied_items()) { diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 156f027617fb..e6de72d494db 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -153,7 +153,7 @@ def populate_items_table(self): else: self.set_missing_values() - def update_status(self, status=None, update_modified=False): + def update_status(self, status=None, update_modified=True): if self.docstatus >= 1 and not status: if self.docstatus == 1: if self.status == "Draft": @@ -162,6 +162,10 @@ def update_status(self, status=None, update_modified=False): status = "Completed" elif self.per_received > 0 and self.per_received < 100: status = "Partially Received" + for item in self.supplied_items: + if item.returned_qty: + status = "Closed" + break else: total_required_qty = total_supplied_qty = 0 for item in self.supplied_items: @@ -176,7 +180,10 @@ def update_status(self, status=None, update_modified=False): elif self.docstatus == 2: status = "Cancelled" - frappe.db.set_value("Subcontracting Order", self.name, "status", status, update_modified) + if status: + frappe.db.set_value( + "Subcontracting Order", self.name, "status", status, update_modified=update_modified + ) @frappe.whitelist() diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js index 650419cf7458..aab2fc927d03 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js @@ -10,6 +10,7 @@ frappe.listview_settings['Subcontracting Order'] = { "Completed": "green", "Partial Material Transferred": "purple", "Material Transferred": "blue", + "Closed": "red", }; return [__(doc.status), status_colors[doc.status], "status,=," + doc.status]; }, diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 94bb38e98033..098242aed89f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -7,7 +7,10 @@ from frappe.tests.utils import FrappeTestCase from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order -from erpnext.controllers.subcontracting_controller import make_rm_stock_entry +from erpnext.controllers.subcontracting_controller import ( + get_materials_from_supplier, + make_rm_stock_entry, +) from erpnext.controllers.tests.test_subcontracting_controller import ( get_rm_items, get_subcontracting_order, @@ -89,6 +92,16 @@ def test_update_status(self): sco.load_from_db() self.assertEqual(sco.status, "Partially Received") + # Closed + ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) + ste.save() + ste.submit() + sco.load_from_db() + self.assertEqual(sco.status, "Closed") + ste.cancel() + sco.load_from_db() + self.assertEqual(sco.status, "Partially Received") + # Completed scr = make_subcontracting_receipt(sco.name) scr.save() diff --git a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json index a206a21ca63f..8f7128be9a07 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json @@ -150,8 +150,7 @@ "label": "Returned Qty", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "hidden": 1 + "read_only": 1 }, { "fieldname": "total_supplied_qty", @@ -166,7 +165,7 @@ "hide_toolbar": 1, "istable": 1, "links": [], - "modified": "2022-04-07 12:58:28.208847", + "modified": "2022-08-26 16:04:56.125951", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Supplied Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 84e95548e17c..5cd4e637cce0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -369,7 +369,7 @@ "in_standard_filter": 1, "label": "Status", "no_copy": 1, - "options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled", + "options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed", "print_hide": 1, "print_width": "150px", "read_only": 1, @@ -628,7 +628,7 @@ "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2022-08-22 17:30:40.827517", + "modified": "2022-08-26 21:02:26.353870", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From 6c4fcd80c638a0407d9cfeac33ffe2666fb51dfb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 29 Aug 2022 14:18:39 +0530 Subject: [PATCH 076/898] fix: Rounded total for cash and non trade discount invoices (cherry picked from commit 318da16b99232033b592cc6e59f931a360758b1d) --- erpnext/controllers/taxes_and_totals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index bc38d08b809c..9dbcdb04c5b2 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -41,6 +41,7 @@ def calculate(self): if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): self.doc.grand_total -= self.doc.discount_amount self.doc.base_grand_total -= self.doc.base_discount_amount + self.set_rounded_total() self.calculate_shipping_charges() From cee867f94174622662d975d3a128b159c56128b1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 15:50:22 +0530 Subject: [PATCH 077/898] fix: AD not getting copied from SCO while creating a SE (backport #32004) (#32007) fix: AD not getting copied from SCO while creating a SE (#32004) (cherry picked from commit 9dbaaa33f5ec7df8904e71e305c26a5c904e2028) Co-authored-by: Sagar Sharma --- .../stock/doctype/stock_entry/stock_entry.py | 40 ++++-- .../subcontracting_order.js | 128 ++---------------- 2 files changed, 38 insertions(+), 130 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7721efb639f6..d70952282d60 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2568,27 +2568,26 @@ def get_supplied_items( @frappe.whitelist() def get_items_from_subcontracting_order(source_name, target_doc=None): - sco = frappe.get_doc("Subcontracting Order", source_name) + def post_process(source, target): + target.stock_entry_type = target.purpose = "Send to Subcontractor" + target.subcontracting_order = source_name - if sco.docstatus == 1: - if target_doc and isinstance(target_doc, str): - target_doc = frappe.get_doc(json.loads(target_doc)) - - if target_doc.items: - target_doc.items = [] + if target.items: + target.items = [] warehouses = {} - for item in sco.items: + for item in source.items: warehouses[item.name] = item.warehouse - for item in sco.supplied_items: - target_doc.append( + for item in source.supplied_items: + target.append( "items", { "s_warehouse": warehouses.get(item.reference_name), - "t_warehouse": sco.supplier_warehouse, + "t_warehouse": source.supplier_warehouse, + "subcontracted_item": item.main_item_code, "item_code": item.rm_item_code, - "qty": item.required_qty, + "qty": max(item.required_qty - item.total_supplied_qty, 0), "transfer_qty": item.required_qty, "uom": item.stock_uom, "stock_uom": item.stock_uom, @@ -2596,6 +2595,23 @@ def get_items_from_subcontracting_order(source_name, target_doc=None): }, ) + target_doc = get_mapped_doc( + "Subcontracting Order", + source_name, + { + "Subcontracting Order": { + "doctype": "Stock Entry", + "field_no_map": ["purchase_order"], + "validation": { + "docstatus": ["=", 1], + }, + }, + }, + target_doc, + post_process, + ignore_child_tables=True, + ) + return target_doc diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index bbc58fe29b2e..065ef39db3ee 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -164,10 +164,7 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll if (flt(doc.per_received) < 100) { cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create')); if (me.has_unsupplied_items()) { - cur_frm.add_custom_button(__('Material to Supplier'), - () => { - me.make_stock_entry(); - }, __('Transfer')); + cur_frm.add_custom_button(__('Material to Supplier'), this.make_stock_entry, __('Transfer')); } } cur_frm.page.set_inner_btn_group_as_primary(__('Create')); @@ -195,120 +192,6 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse); } - make_stock_entry() { - var items = $.map(cur_frm.doc.items, (d) => d.bom ? d.item_code : false); - var me = this; - - if (items.length >= 1) { - me.raw_material_data = []; - me.show_dialog = 1; - let title = __('Transfer Material to Supplier'); - let fields = [ - { fieldtype: 'Section Break', label: __('Raw Materials') }, - { - fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'), - fields: [ - { - fieldtype: 'Data', - fieldname: 'item_code', - label: __('Item'), - read_only: 1, - in_list_view: 1 - }, - { - fieldtype: 'Data', - fieldname: 'rm_item_code', - label: __('Raw Material'), - read_only: 1, - in_list_view: 1 - }, - { - fieldtype: 'Float', - read_only: 1, - fieldname: 'qty', - label: __('Quantity'), - in_list_view: 1 - }, - { - fieldtype: 'Data', - read_only: 1, - fieldname: 'warehouse', - label: __('Reserve Warehouse'), - in_list_view: 1 - }, - { - fieldtype: 'Float', - read_only: 1, - fieldname: 'rate', - label: __('Rate'), - hidden: 1 - }, - { - fieldtype: 'Float', - read_only: 1, - fieldname: 'amount', - label: __('Amount'), - hidden: 1 - }, - { - fieldtype: 'Link', - read_only: 1, - fieldname: 'uom', - label: __('UOM'), - hidden: 1 - } - ], - data: me.raw_material_data, - get_data: () => me.raw_material_data - } - ]; - - me.dialog = new frappe.ui.Dialog({ - title: title, fields: fields - }); - - if (me.frm.doc['supplied_items']) { - me.frm.doc['supplied_items'].forEach((item) => { - if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) { - me.raw_material_data.push({ - 'name': item.name, - 'item_code': item.main_item_code, - 'rm_item_code': item.rm_item_code, - 'item_name': item.rm_item_code, - 'qty': item.required_qty - item.supplied_qty, - 'warehouse': item.reserve_warehouse, - 'rate': item.rate, - 'amount': item.amount, - 'stock_uom': item.stock_uom - }); - me.dialog.fields_dict.sub_con_rm_items.grid.refresh(); - } - }); - } - - me.dialog.get_field('sub_con_rm_items').check_all_rows(); - - me.dialog.show(); - this.dialog.set_primary_action(__('Transfer'), () => { - me.values = me.dialog.get_values(); - if (me.values) { - me.values.sub_con_rm_items.map((row, i) => { - if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { - let row_id = i + 1; - frappe.throw(__('Item Code, warehouse and quantity are required on row {0}', [row_id])); - } - }); - me.make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()); - me.dialog.hide(); - } - }); - } - - me.dialog.get_close_btn().on('click', () => { - me.dialog.hide(); - }); - } - has_unsupplied_items() { return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty); } @@ -321,6 +204,15 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll }); } + make_stock_entry() { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', + source_name: cur_frm.doc.name, + freeze: true, + freeze_message: __('Creating Stock Entry ...') + }); + } + make_rm_stock_entry(rm_items) { frappe.call({ method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry', From f7898b495470da5e5f787727f67068efd324abdd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 22 Aug 2022 15:46:04 +0530 Subject: [PATCH 078/898] refactor: re-add remarks field to payment ledger and AR/AP report (cherry picked from commit 5782c4469ac98b691564af8f1e38330934584558) --- .../payment_ledger_entry/payment_ledger_entry.json | 10 ++++++++-- .../report/accounts_receivable/accounts_receivable.js | 5 +++++ .../report/accounts_receivable/accounts_receivable.py | 5 +++++ erpnext/accounts/utils.py | 1 + 4 files changed, 19 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 4596b00fc1ae..22842cec0fe7 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -22,7 +22,8 @@ "amount", "account_currency", "amount_in_account_currency", - "delinked" + "delinked", + "remarks" ], "fields": [ { @@ -136,12 +137,17 @@ "fieldtype": "Link", "label": "Finance Book", "options": "Finance Book" + }, + { + "fieldname": "remarks", + "fieldtype": "Text", + "label": "Remarks" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-07-11 09:13:54.379168", + "modified": "2022-08-22 15:32:56.629430", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Ledger Entry", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 0238711a70ed..0b4e577f6cb3 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -178,6 +178,11 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Data", "hidden": 1 }, + { + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check", + }, { "fieldname": "customer_name", "label": __("Customer Name"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e86a2e83b8a4..28b8d2577833 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -119,6 +119,7 @@ def init_voucher_balance(self): party_account=ple.account, posting_date=ple.posting_date, account_currency=ple.account_currency, + remarks=ple.remarks, invoiced=0.0, paid=0.0, credit_note=0.0, @@ -692,6 +693,7 @@ def get_ple_entries(self): ple.account_currency, ple.amount, ple.amount_in_account_currency, + ple.remarks, ) .where(ple.delinked == 0) .where(Criterion.all(self.qb_selection_filter)) @@ -970,6 +972,9 @@ def get_columns(self): options="Supplier Group", ) + if self.filters.show_remarks: + self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200), + def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120): if not fieldname: fieldname = scrub(label) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 018e8f9301a2..f61e8ac960bc 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1424,6 +1424,7 @@ def get_account_type(account): "amount": dr_or_cr, "amount_in_account_currency": dr_or_cr_account_currency, "delinked": True if cancel else False, + "remarks": gle.remarks, } ) From b3b0272ec9208a5fa5e5469e5b70e067950f13cb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 23 Aug 2022 15:10:55 +0530 Subject: [PATCH 079/898] chore: patch for migrating remarks to payment ledger (cherry picked from commit 3a6b095ed45fbc47628e4b189e1d1a89e17ffc25) --- ...grate_remarks_from_gl_to_payment_ledger.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py diff --git a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py new file mode 100644 index 000000000000..062d24b78bdd --- /dev/null +++ b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py @@ -0,0 +1,56 @@ +import frappe +from frappe import qb +from frappe.utils import create_batch + + +def execute(): + if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"): + + gle = qb.DocType("GL Entry") + ple = qb.DocType("Payment Ledger Entry") + + # get ple and their remarks from GL Entry + pl_entries = ( + qb.from_(ple) + .left_join(gle) + .on( + (ple.account == gle.account) + & (ple.party_type == gle.party_type) + & (ple.party == gle.party) + & (ple.voucher_type == gle.voucher_type) + & (ple.voucher_no == gle.voucher_no) + & (ple.company == gle.company) + ) + .select( + ple.company, + ple.account, + ple.party_type, + ple.party, + ple.voucher_type, + ple.voucher_no, + gle.remarks.as_("gle_remarks"), + ) + .where((ple.delinked == 0) & (gle.is_cancelled == 0)) + .run(as_dict=True) + ) + + if pl_entries: + # split into multiple batches, update and commit for each batch + batch_size = 1000 + for batch in create_batch(pl_entries, batch_size): + for entry in batch: + query = ( + qb.update(ple) + .set(ple.remarks, entry.gle_remarks) + .where( + (ple.company == entry.company) + & (ple.account == entry.account) + & (ple.party_type == entry.party_type) + & (ple.party == entry.party) + & (ple.voucher_type == entry.voucher_type) + & (ple.voucher_no == entry.voucher_no) + ) + ) + query.run() + + frappe.db.commit() From 0aaf9c4f053144d6605ed6092b88a75f3f5de6aa Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 29 Aug 2022 15:31:26 +0530 Subject: [PATCH 080/898] chore: add remarks migration to patches.txt (cherry picked from commit d522f13d556af53ba6b6cb9c833e14aefc0226cb) --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 98147dea5a60..4729add16b38 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -312,3 +312,4 @@ erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes +erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger From 7ca1beb15d4fe1fd740ff4d304e394735d4bec37 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 29 Aug 2022 18:26:07 +0200 Subject: [PATCH 081/898] fix: permissions for Task Type (#32016) (cherry picked from commit 73f4d5931d881b1eacc4fde89affd291f88c5ef1) --- .../projects/doctype/task_type/task_type.json | 123 +++++------------- 1 file changed, 33 insertions(+), 90 deletions(-) diff --git a/erpnext/projects/doctype/task_type/task_type.json b/erpnext/projects/doctype/task_type/task_type.json index 3254444a48e9..b04264e9c74e 100644 --- a/erpnext/projects/doctype/task_type/task_type.json +++ b/erpnext/projects/doctype/task_type/task_type.json @@ -1,127 +1,70 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "Prompt", - "beta": 0, "creation": "2019-04-19 15:04:05.317138", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "weight", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "weight", "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": "Weight", - "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 + "label": "Weight" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "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": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Description" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-19 15:31:48.080164", + "links": [], + "modified": "2022-08-29 17:46:41.342979", "modified_by": "Administrator", "module": "Projects", "name": "Task Type", - "name_case": "", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { - "amend": 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 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "states": [], + "track_changes": 1 } \ No newline at end of file From 1b5a1cbaad12e9aa825a7c39046836be0d8f08cd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 22:17:39 +0530 Subject: [PATCH 082/898] fix(patch): update sla doctype directly (backport #32014) (#32015) fix(patch): update sla doctype directly (#32014) fix: update sla doctype directly (cherry picked from commit 2d41704424899a8cc1992a9c379a5d340effbce8) Co-authored-by: Ankush Menat --- erpnext/patches/v13_0/add_doctype_to_sla.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py index 5f5974f65d49..2d3b0de5b5c3 100644 --- a/erpnext/patches/v13_0/add_doctype_to_sla.py +++ b/erpnext/patches/v13_0/add_doctype_to_sla.py @@ -14,7 +14,8 @@ def execute(): for sla in frappe.get_all("Service Level Agreement"): agreement = frappe.get_doc("Service Level Agreement", sla.name) - agreement.document_type = "Issue" + agreement.db_set("document_type", "Issue") + agreement.reload() agreement.apply_sla_for_resolution = 1 agreement.append("sla_fulfilled_on", {"status": "Resolved"}) agreement.append("sla_fulfilled_on", {"status": "Closed"}) From 8717235a34828a7335a6e2469b972d2d341bacce Mon Sep 17 00:00:00 2001 From: MOHAMMED NIYAS <76736615+niyazrazak@users.noreply.github.com> Date: Mon, 29 Aug 2022 14:47:43 +0530 Subject: [PATCH 083/898] fix: lost quotation not to expired (cherry picked from commit 69ffef8c0ee728779dfb76b5eee4f15ef3421b6d) --- erpnext/selling/doctype/quotation/quotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 863fbc4059a0..96092b152381 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -268,7 +268,7 @@ def update_item(obj, target, source_parent): def set_expired_status(): # filter out submitted non expired quotations whose validity has been ended - cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status != 'Expired' and `tabQuotation`.valid_till < %s" + cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status NOT IN ('Expired', 'Lost') and `tabQuotation`.valid_till < %s" # check if those QUO have SO against it so_against_quo = """ SELECT From 32abf67c807495e8da5d08079d2f8d1dc52c7cda Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 30 Aug 2022 09:24:00 +0000 Subject: [PATCH 084/898] chore(release): Bumped to Version 14.0.3 ## [14.0.3](https://github.com/frappe/erpnext/compare/v14.0.2...v14.0.3) (2022-08-30) ### Bug Fixes * AD not getting copied from SCO while creating a SE (backport [#32004](https://github.com/frappe/erpnext/issues/32004)) ([#32007](https://github.com/frappe/erpnext/issues/32007)) ([cee867f](https://github.com/frappe/erpnext/commit/cee867f94174622662d975d3a128b159c56128b1)) * Add docstatus filter for voucher_no in Repost Item Valuation ([309da96](https://github.com/frappe/erpnext/commit/309da9644273118cd4efd6f60b810acd60307394)) * add validation for PO in Stock Entry (backport [#31974](https://github.com/frappe/erpnext/issues/31974)) ([#31975](https://github.com/frappe/erpnext/issues/31975)) ([4d8ced6](https://github.com/frappe/erpnext/commit/4d8ced6c87147d5e7591f97200629a6245cba824)) * default supplier not set in the PP ([4cf9fb0](https://github.com/frappe/erpnext/commit/4cf9fb08e19533f4d2a0674ab09ec045f49ec05b)) * display amount in account currency if party is supplied ([15915d7](https://github.com/frappe/erpnext/commit/15915d7053f9be87b888c5137c820a09cf37599a)) * Explicitly commit "log_error" since its getting called during GET request (backport [#31952](https://github.com/frappe/erpnext/issues/31952)) ([#31953](https://github.com/frappe/erpnext/issues/31953)) ([362976f](https://github.com/frappe/erpnext/commit/362976fa42439eb72dcf1f3efa451142b191a2d9)) * gl entries for asset repair ([f0f7afa](https://github.com/frappe/erpnext/commit/f0f7afa6699bbf61229970923ef0664b242b9fec)) * lost quotation not to expired ([8717235](https://github.com/frappe/erpnext/commit/8717235a34828a7335a6e2469b972d2d341bacce)) * material request connection on work order ([193502c](https://github.com/frappe/erpnext/commit/193502ce03c1fbff0c1b78f77d2c1dd23907375f)) * **patch:** update sla doctype directly (backport [#32014](https://github.com/frappe/erpnext/issues/32014)) ([#32015](https://github.com/frappe/erpnext/issues/32015)) ([1b5a1cb](https://github.com/frappe/erpnext/commit/1b5a1cbaad12e9aa825a7c39046836be0d8f08cd)) * permissions for Task Type ([#32016](https://github.com/frappe/erpnext/issues/32016)) ([7ca1beb](https://github.com/frappe/erpnext/commit/7ca1beb15d4fe1fd740ff4d304e394735d4bec37)) * Purchase Order creation from Sales Order ([39ff0cc](https://github.com/frappe/erpnext/commit/39ff0cc6d885ba31d1361a588fe9430cd009806e)) * Purposes not set ([94ebfa7](https://github.com/frappe/erpnext/commit/94ebfa765c38e8da2e351bb3e2a3154bffd3e655)) * restrict party types to Supplier/Customer for AR/AP report ([c407d1e](https://github.com/frappe/erpnext/commit/c407d1e51ac712dede5a67d4626c8054730b0787)) * Rounded total for cash and non trade discount invoices ([6c4fcd8](https://github.com/frappe/erpnext/commit/6c4fcd80c638a0407d9cfeac33ffe2666fb51dfb)) * Route condition set for stock ledger (backport [#31935](https://github.com/frappe/erpnext/issues/31935)) ([#31945](https://github.com/frappe/erpnext/issues/31945)) ([a7d23ab](https://github.com/frappe/erpnext/commit/a7d23abc2ff7aad33fbac13c8e101fdd3bf4cb00)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2bbbfdade073..e980e96fd334 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.0.2" +__version__ = "14.0.3" def get_default_company(user=None): From 0e9a1fb40e0b5310e69d85f06610f4879f3db378 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 15:46:08 +0530 Subject: [PATCH 085/898] fix: force delete old report docs (backport #32026) (#32027) fix: force delete old report docs (#32026) (cherry picked from commit ffa3071d36e62eb721bc9a3105fb7af4b93cf8fc) Co-authored-by: Ankush Menat --- erpnext/patches/v13_0/delete_old_sales_reports.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index b31c9d17d71b..1b53da755cd9 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -16,18 +16,18 @@ def execute(): delete_auto_email_reports(report) check_and_delete_linked_reports(report) - frappe.delete_doc("Report", report) + frappe.delete_doc("Report", report, force=True) def delete_auto_email_reports(report): """Check for one or multiple Auto Email Reports and delete""" auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: - frappe.delete_doc("Auto Email Report", auto_email_report[0]) + frappe.delete_doc("Auto Email Report", auto_email_report[0], force=True) def delete_links_from_desktop_icons(report): """Check for one or multiple Desktop Icons and delete""" desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"]) for desktop_icon in desktop_icons: - frappe.delete_doc("Desktop Icon", desktop_icon[0]) + frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True) From 1b9082e07b06dadb878933e1cc5cb2f865fb2710 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 30 Aug 2022 19:16:36 +0530 Subject: [PATCH 086/898] fix: Loan Interest accruals for 0 rated loans (cherry picked from commit eefc9b71725d5530e3bd259a299d3c0673385a4a) --- .../loan_interest_accrual/loan_interest_accrual.py | 1 - .../doctype/loan_repayment/loan_repayment.py | 1 + .../process_loan_interest_accrual.py | 13 ++++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 4978f1fcb89b..730d33752ee2 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -233,7 +233,6 @@ def get_term_loans(date, term_loan=None, loan_type=None): AND l.is_term_loan =1 AND rs.payment_date <= %s AND rs.is_accrued=0 {0} - AND rs.interest_amount > 0 AND l.status = 'Disbursed' ORDER BY rs.payment_date""".format( condition diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 07a1d0d85069..9b7603fc3b37 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -732,6 +732,7 @@ def get_amounts(amounts, against_loan, posting_date): ) amounts["pending_accrual_entries"] = pending_accrual_entries amounts["unaccrued_interest"] = flt(unaccrued_interest, precision) + amounts["written_off_amount"] = flt(against_loan_doc.written_off_amount, precision) if final_due_date: amounts["due_date"] = final_due_date diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 81464a36c3dd..25c72d91a7c4 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -57,7 +57,7 @@ def process_loan_interest_accrual_for_demand_loans( def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None): - if not term_loan_accrual_pending(posting_date or nowdate()): + if not term_loan_accrual_pending(posting_date or nowdate(), loan=loan): return loan_process = frappe.new_doc("Process Loan Interest Accrual") @@ -71,9 +71,12 @@ def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=No return loan_process.name -def term_loan_accrual_pending(date): - pending_accrual = frappe.db.get_value( - "Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0} - ) +def term_loan_accrual_pending(date, loan=None): + filters = {"payment_date": ("<=", date), "is_accrued": 0} + + if loan: + filters.update({"parent": loan}) + + pending_accrual = frappe.db.get_value("Repayment Schedule", filters) return pending_accrual From 52fc10d00cf4259cedf7752e2ecf72ea31398bc5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 30 Aug 2022 19:24:57 +0530 Subject: [PATCH 087/898] chore: Add check for principal amount (cherry picked from commit a76d3827ec5355b7eecedc3e66bfd85b119d5211) --- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 730d33752ee2..15e9e3f4c1af 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -233,6 +233,7 @@ def get_term_loans(date, term_loan=None, loan_type=None): AND l.is_term_loan =1 AND rs.payment_date <= %s AND rs.is_accrued=0 {0} + AND rs.principal_amount > 0 AND l.status = 'Disbursed' ORDER BY rs.payment_date""".format( condition From 92f8f0ec748ab732c0edface031f4581357e6863 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 2 Sep 2022 09:10:17 +0530 Subject: [PATCH 088/898] chore: set BOM as default value for Backflush Raw Materials of Subcontract Based On (backport #32048) (#32050) chore: set BOM as default value for Backflush Raw Materials of Subcontract Based On (#32048) chore: set BOM as default value for Backflush Raw Materials of Subcontract Based On in Buying Settings (cherry picked from commit 68907ca7839cc9fcb20bf8b3b47ed9318bfedf90) Co-authored-by: Sagar Sharma --- erpnext/buying/doctype/buying_settings/buying_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 6c18a4650b7d..aad26075f2df 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -76,7 +76,7 @@ "label": "Subcontracting Settings" }, { - "default": "Material Transferred for Subcontract", + "default": "BOM", "fieldname": "backflush_raw_materials_of_subcontract_based_on", "fieldtype": "Select", "label": "Backflush Raw Materials of Subcontract Based On", @@ -148,7 +148,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-05-31 19:40:26.103909", + "modified": "2022-09-01 18:01:34.994657", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", From 4bbd0ec985cf1f6b26f067412a481766ca25511a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 24 Aug 2022 12:24:55 +0530 Subject: [PATCH 089/898] fix: include payment against PO in AR/AP report (cherry picked from commit fdd167cac123a6f2fea11ccdd73f3b12a65f2f8d) --- .../report/accounts_receivable/accounts_receivable.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 28b8d2577833..3f504b13f673 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -179,6 +179,11 @@ def get_voucher_balance(self, ple): key = (ple.against_voucher_type, ple.against_voucher_no, 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)) + return row def update_voucher_balance(self, ple): From d9632e813842c5964e7e449954375e8f1282211b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 24 Aug 2022 13:58:11 +0530 Subject: [PATCH 090/898] test: payments against so/po will show up as outstanding amount 1. Class will use FrappeTestCase fixture 2. setup and teardown methods are introduced 3. test for payments against SO (cherry picked from commit 36f5883ddaf2b2215123d0b06f7b3965b27696c5) --- .../test_accounts_receivable.py | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index edddbbce2194..bac8beed2e2b 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,19 +1,27 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -class TestAccountsReceivable(unittest.TestCase): - def test_accounts_receivable(self): +class TestAccountsReceivable(FrappeTestCase): + def setUp(self): frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'") + frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'") + frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'") + def tearDown(self): + frappe.db.rollback() + + def test_accounts_receivable(self): filters = { "company": "_Test Company 2", "based_on_payment_terms": 1, @@ -66,6 +74,50 @@ def test_accounts_receivable(self): ], ) + def test_payment_againt_po_in_receivable_report(self): + """ + Payments made against Purchase Order will show up as outstanding amount + """ + + so = make_sales_order( + company="_Test Company 2", + customer="_Test Customer 2", + warehouse="Finished Goods - _TC2", + currency="EUR", + debit_to="Debtors - _TC2", + income_account="Sales - _TC2", + expense_account="Cost of Goods Sold - _TC2", + cost_center="Main - _TC2", + ) + + pe = get_payment_entry(so.doctype, so.name) + pe = pe.save().submit() + + filters = { + "company": "_Test Company 2", + "based_on_payment_terms": 0, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + report = execute(filters) + + expected_data_after_payment = [0, 1000, 0, -1000] + + row = report[1][0] + self.assertEqual( + expected_data_after_payment, + [ + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + ], + ) + def make_sales_invoice(): frappe.set_user("Administrator") From 4409f1128233f3dfce7209e6c5272ede21859d7c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 2 Sep 2022 09:50:42 +0530 Subject: [PATCH 091/898] fix: key error on consolidated financial report accounts with same name but different account number will throw key error on consolidated report (cherry picked from commit 6e8395cccdb9fc3eba0a0746450660e89155c9f5) --- .../consolidated_financial_statement.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 98dbbf6c449c..330e442a808b 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -535,7 +535,11 @@ def get_accounts(root_type, companies): ): if account.account_name not in added_accounts: accounts.append(account) - added_accounts.append(account.account_name) + if account.account_number: + account_key = account.account_number + "-" + account.account_name + else: + account_key = account.account_name + added_accounts.append(account_key) return accounts From f55881aef80b0edb806ef4753e997d03bb848de9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 22 Aug 2022 11:54:07 +0530 Subject: [PATCH 092/898] fix: incorrect import parameter for cancel PDA (cherry picked from commit 08f2e4edc365cfd5e8d0154d7aef69ad45439bee) --- .../process_deferred_accounting/process_deferred_accounting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py index 8ec726b36cd9..1f88849b26c3 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py @@ -34,4 +34,4 @@ def on_cancel(self): filters={"against_voucher_type": self.doctype, "against_voucher": self.name}, ) - make_gl_entries(gl_entries=gl_entries, cancel=1) + make_gl_entries(gl_map=gl_entries, cancel=1) From 4698dba402b532b6fda3322f32874fbd89d165e1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 2 Sep 2022 11:22:45 +0530 Subject: [PATCH 093/898] test: pda document submission and cancellation (cherry picked from commit 1c385541fa7d8f28138996447fc38f859a71e224) --- .../test_process_deferred_accounting.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py index 164ba6aa3488..5a0aeb7284ac 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py @@ -57,3 +57,16 @@ def test_creation_of_ledger_entry_on_submit(self): ] check_gl_entries(self, si.name, expected_gle, "2019-01-10") + + def test_pda_submission_and_cancellation(self): + pda = frappe.get_doc( + dict( + doctype="Process Deferred Accounting", + posting_date="2019-01-01", + start_date="2019-01-01", + end_date="2019-01-31", + type="Income", + ) + ) + pda.submit() + pda.cancel() From f871dd4ef6b4a6f7b04353c63ddaff7bec4e9147 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 3 Sep 2022 11:42:04 +0530 Subject: [PATCH 094/898] fix: not able to make variant item (cherry picked from commit 92b0f9cd7e6e3ff7cdc18008e687a1e3a14d6ef9) --- erpnext/stock/doctype/item/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 29b001fdcb35..7e1476d240a6 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -562,7 +562,7 @@ $.extend(erpnext.item, { let selected_attributes = {}; me.multiple_variant_dialog.$wrapper.find('.form-column').each((i, col) => { if(i===0) return; - let attribute_name = $(col).find('label').html().trim(); + let attribute_name = $(col).find('.control-label').html().trim(); selected_attributes[attribute_name] = []; let checked_opts = $(col).find('.checkbox input'); checked_opts.each((i, opt) => { From b96526eefdd3f16fb4e980ee02c39e866ddb6044 Mon Sep 17 00:00:00 2001 From: hamzaali15 Date: Thu, 1 Sep 2022 15:02:21 +0500 Subject: [PATCH 095/898] fix: KSA VAT report multi currency amount issue In KSA VAT report amount is not showing correctly for multi currencies because net_amount field is fetched instead of base_net_amount (cherry picked from commit 56d8962e406a192839a8bf6962d745a9c2f1ea1d) --- erpnext/regional/report/ksa_vat/ksa_vat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 15996d2d1f8c..3571f962667c 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -177,16 +177,16 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): "parent": invoice.name, "item_tax_template": vat_setting.item_tax_template, }, - fields=["item_code", "net_amount"], + fields=["item_code", "base_net_amount"], ) for item in invoice_items: # Summing up total taxable amount if invoice.is_return == 0: - total_taxable_amount += item.net_amount + total_taxable_amount += item.base_net_amount if invoice.is_return == 1: - total_taxable_adjustment_amount += item.net_amount + total_taxable_adjustment_amount += item.base_net_amount # Summing up total tax total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) From 319ee41403083b86325aab514cf84f4d39100340 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 10 Aug 2022 14:17:28 +0530 Subject: [PATCH 096/898] fix(pos): error while consolidating pos invoices (cherry picked from commit 33762dbbac55181371f7bcf052474d213312434b) --- .../accounts/doctype/pos_profile/pos_profile.json | 11 ++++++++++- erpnext/controllers/taxes_and_totals.py | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index d5f7ee4f2174..994b6776e3ce 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -43,6 +43,7 @@ "currency", "write_off_account", "write_off_cost_center", + "write_off_limit", "account_for_change_amount", "disable_rounded_total", "column_break_23", @@ -360,6 +361,14 @@ "fieldtype": "Check", "label": "Validate Stock on Save" }, + { + "default": "1", + "description": "Auto write off precision loss while consolidation", + "fieldname": "write_off_limit", + "fieldtype": "Currency", + "label": "Write Off Limit", + "reqd": 1 + }, { "default": "0", "description": "If enabled, the consolidated invoices will have rounded total disabled", @@ -393,7 +402,7 @@ "link_fieldname": "pos_profile" } ], - "modified": "2022-07-21 11:16:46.911173", + "modified": "2022-08-10 12:57:06.241439", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 9dbcdb04c5b2..cbcccce5f719 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -770,6 +770,18 @@ def calculate_outstanding_amount(self): self.doc.precision("outstanding_amount"), ) + if ( + self.doc.doctype == "Sales Invoice" + and self.doc.get("is_pos") + and self.doc.get("pos_profile") + and self.doc.get("is_consolidated") + ): + write_off_limit = flt( + frappe.db.get_value("POS Profile", self.doc.pos_profile, "write_off_limit") + ) + if write_off_limit and abs(self.doc.outstanding_amount) <= write_off_limit: + self.doc.write_off_outstanding_amount_automatically = 1 + if ( self.doc.doctype == "Sales Invoice" and self.doc.get("is_pos") From 60fa4214098c2d4d81db8f23fd08e4b512836f4d Mon Sep 17 00:00:00 2001 From: hrzzz Date: Mon, 15 Aug 2022 09:14:23 -0300 Subject: [PATCH 097/898] feat: two new filters for gross profit (cherry picked from commit 27891ecb77a74dea90c30133d81d92edebf59cf3) --- .../accounts/report/gross_profit/gross_profit.js | 12 ++++++++++++ .../accounts/report/gross_profit/gross_profit.py | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 21205c31634f..ffbe9ada6ca5 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -38,6 +38,18 @@ frappe.query_reports["Gross Profit"] = { "options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term", "default": "Invoice" }, + { + "fieldname":"item_group", + "label": __("Item Group"), + "fieldtype": "Link", + "options": "Item Group" + }, + { + "fieldname":"sales_person", + "label": __("Sales Person"), + "fieldtype": "Link", + "options": "Sales Person" + }, ], "tree": true, "name_field": "parent", diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 54af22591547..47a72909aa5f 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -8,6 +8,7 @@ from erpnext.controllers.queries import get_match_cond from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition def execute(filters=None): @@ -676,6 +677,17 @@ def load_invoice_items(self): if self.filters.to_date: conditions += " and posting_date <= %(to_date)s" + if self.filters.item_group: + conditions += " and {0}".format(get_item_group_condition(self.filters.item_group)) + + if self.filters.sales_person: + conditions += """ + and exists(select 1 + from `tabSales Team` st + where st.parent = `tabSales Invoice`.name + and st.sales_person = %(sales_person)s) + """ + if self.filters.group_by == "Sales Person": sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives" sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name" @@ -723,6 +735,7 @@ def load_invoice_items(self): from `tabSales Invoice` inner join `tabSales Invoice Item` on `tabSales Invoice Item`.parent = `tabSales Invoice`.name + join `tabItem` item on item.name = `tabSales Invoice Item`.item_code {sales_team_table} {payment_term_table} where From 055556b7f1327cfc62f88b0028c1649798e1aca4 Mon Sep 17 00:00:00 2001 From: hrzzz Date: Mon, 15 Aug 2022 09:23:56 -0300 Subject: [PATCH 098/898] fix: remove spaces and order import (cherry picked from commit 3ef551872a38c059e8ca09927c7bea44635c9474) --- erpnext/accounts/report/gross_profit/gross_profit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 47a72909aa5f..f0106bea4fca 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -7,8 +7,8 @@ from frappe.utils import cint, flt, formatdate from erpnext.controllers.queries import get_match_cond -from erpnext.stock.utils import get_incoming_rate from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition +from erpnext.stock.utils import get_incoming_rate def execute(filters=None): @@ -681,9 +681,9 @@ def load_invoice_items(self): conditions += " and {0}".format(get_item_group_condition(self.filters.item_group)) if self.filters.sales_person: - conditions += """ - and exists(select 1 - from `tabSales Team` st + conditions += """ + and exists(select 1 + from `tabSales Team` st where st.parent = `tabSales Invoice`.name and st.sales_person = %(sales_person)s) """ From 0438433a4ff0143079d49b522bf803b1b4fcb33e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 4 Sep 2022 13:15:59 +0530 Subject: [PATCH 099/898] chore: Linting Issues (cherry picked from commit ad8d0efa2962a5bc5bd1286ed8f5af7183dd3644) --- .../accounts/report/gross_profit/gross_profit.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index ffbe9ada6ca5..615804ef623b 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -4,7 +4,7 @@ frappe.query_reports["Gross Profit"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -12,40 +12,40 @@ frappe.query_reports["Gross Profit"] = { "reqd": 1 }, { - "fieldname":"from_date", + "fieldname": "from_date", "label": __("From Date"), "fieldtype": "Date", "default": frappe.defaults.get_user_default("year_start_date"), "reqd": 1 }, { - "fieldname":"to_date", + "fieldname": "to_date", "label": __("To Date"), "fieldtype": "Date", "default": frappe.defaults.get_user_default("year_end_date"), "reqd": 1 }, { - "fieldname":"sales_invoice", + "fieldname": "sales_invoice", "label": __("Sales Invoice"), "fieldtype": "Link", "options": "Sales Invoice" }, { - "fieldname":"group_by", + "fieldname": "group_by", "label": __("Group By"), "fieldtype": "Select", "options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term", "default": "Invoice" }, { - "fieldname":"item_group", + "fieldname": "item_group", "label": __("Item Group"), "fieldtype": "Link", "options": "Item Group" }, { - "fieldname":"sales_person", + "fieldname": "sales_person", "label": __("Sales Person"), "fieldtype": "Link", "options": "Sales Person" From 5520c6b2f3421aacdd1b0f0f42df9a6cbb3442b8 Mon Sep 17 00:00:00 2001 From: Solufyin <34390782+Solufyin@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:49:13 +0530 Subject: [PATCH 100/898] fix: Naming series in Journal Entry Template (cherry picked from commit 2085626390f705d5cbd4abbf664efecbc96aa91f) --- .../doctype/journal_entry_template/journal_entry_template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js index 1c19c1d2255a..cf5fbe12afe7 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on("Journal Entry Template", { - setup: function(frm) { + refresh: function(frm) { frappe.model.set_default_values(frm.doc); frm.set_query("account" ,"accounts", function(){ From 784fb471976f41e50476922e293c861fe6773684 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Sun, 4 Sep 2022 12:55:03 +0200 Subject: [PATCH 101/898] feat: better Item Price list view (#31954) * feat: better Item Price list view (cherry picked from commit 86395c6adb4c577d175ed08c7615439ab9fbb224) --- .../stock/doctype/item_price/item_price.json | 113 +++++------------- .../doctype/item_price/item_price_list.js | 3 + 2 files changed, 34 insertions(+), 82 deletions(-) create mode 100644 erpnext/stock/doctype/item_price/item_price_list.js diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index 83177b372adf..8c6f6d85a461 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -48,41 +48,31 @@ "oldfieldtype": "Select", "options": "Item", "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "uom", "fieldtype": "Link", "label": "UOM", - "options": "UOM", - "show_days": 1, - "show_seconds": 1 + "options": "UOM" }, { "default": "0", "description": "Quantity that must be bought or sold per UOM", "fieldname": "packing_unit", "fieldtype": "Int", - "label": "Packing Unit", - "show_days": 1, - "show_seconds": 1 + "label": "Packing Unit" }, { "fieldname": "column_break_17", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "item_name", "fieldtype": "Data", "in_list_view": 1, "label": "Item Name", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "item_code.brand", @@ -90,36 +80,29 @@ "fieldtype": "Read Only", "in_list_view": 1, "label": "Brand", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "item_description", "fieldtype": "Text", "label": "Item Description", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "price_list_details", "fieldtype": "Section Break", "label": "Price List", - "options": "fa fa-tags", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-tags" }, { "fieldname": "price_list", "fieldtype": "Link", "in_global_search": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Price List", "options": "Price List", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "bold": 1, @@ -127,49 +110,37 @@ "fieldname": "customer", "fieldtype": "Link", "label": "Customer", - "options": "Customer", - "show_days": 1, - "show_seconds": 1 + "options": "Customer" }, { "depends_on": "eval:doc.buying == 1", "fieldname": "supplier", "fieldtype": "Link", "label": "Supplier", - "options": "Supplier", - "show_days": 1, - "show_seconds": 1 + "options": "Supplier" }, { "fieldname": "column_break_3", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0", "fieldname": "buying", "fieldtype": "Check", "label": "Buying", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "fieldname": "selling", "fieldtype": "Check", "label": "Selling", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "item_details", "fieldtype": "Section Break", - "options": "fa fa-tag", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-tag" }, { "bold": 1, @@ -177,15 +148,11 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_br_1", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "price_list_rate", @@ -197,80 +164,61 @@ "oldfieldname": "ref_rate", "oldfieldtype": "Currency", "options": "currency", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "section_break_15", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "default": "Today", "fieldname": "valid_from", "fieldtype": "Date", - "label": "Valid From", - "show_days": 1, - "show_seconds": 1 + "label": "Valid From" }, { "default": "0", "fieldname": "lead_time_days", "fieldtype": "Int", - "label": "Lead Time in days", - "show_days": 1, - "show_seconds": 1 + "label": "Lead Time in days" }, { "fieldname": "column_break_18", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "valid_upto", "fieldtype": "Date", - "label": "Valid Upto", - "show_days": 1, - "show_seconds": 1 + "label": "Valid Upto" }, { "fieldname": "section_break_24", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "note", "fieldtype": "Text", - "label": "Note", - "show_days": 1, - "show_seconds": 1 + "label": "Note" }, { "fieldname": "reference", "fieldtype": "Data", "in_list_view": 1, - "label": "Reference", - "show_days": 1, - "show_seconds": 1 + "in_standard_filter": 1, + "label": "Reference" }, { "fieldname": "batch_no", "fieldtype": "Link", "label": "Batch No", - "options": "Batch", - "show_days": 1, - "show_seconds": 1 + "options": "Batch" } ], "icon": "fa fa-flag", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-08 18:12:15.395772", + "modified": "2022-09-02 16:33:55.612992", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", @@ -307,6 +255,7 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "title_field": "item_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/item_price/item_price_list.js b/erpnext/stock/doctype/item_price/item_price_list.js new file mode 100644 index 000000000000..48158393f67d --- /dev/null +++ b/erpnext/stock/doctype/item_price/item_price_list.js @@ -0,0 +1,3 @@ +frappe.listview_settings['Item Price'] = { + hide_name_column: true, +}; From 4cb685a326f05af6652b441eeaec2daa9fb496cd Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 25 Aug 2022 20:45:35 +0200 Subject: [PATCH 102/898] fix: upgrade process to version-14 when currency opportunity wass not set (cherry picked from commit 9d02fbadb43d729141422321996084daf31543d1) --- .../update_opportunity_currency_fields.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py index 076de526195d..3a6f1b3a5de5 100644 --- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -4,6 +4,8 @@ import erpnext from erpnext.setup.utils import get_exchange_rate +import click + def execute(): frappe.reload_doctype("Opportunity") @@ -16,6 +18,19 @@ def execute(): for opportunity in opportunities: company_currency = erpnext.get_company_currency(opportunity.company) + if opportunity.currency is None or opportunity.currency == '': + opportunity.currency = company_currency + frappe.db.set_value( + "Opportunity", + opportunity.name, + {"currency": opportunity.currency}, + update_modified=False, + ) + click.secho( + f" Opportunity `{opportunity.name}` has no currency set. We for it to company currency : `{opportunity.currency}`\"\n", + fg="yellow", + ) + # base total and total will be 0 only since item table did not have amount field earlier if opportunity.currency != company_currency: conversion_rate = get_exchange_rate(opportunity.currency, company_currency) @@ -24,6 +39,10 @@ def execute(): conversion_rate = 1 base_opportunity_amount = flt(opportunity.opportunity_amount) + if conversion_rate is None: + print(opportunity.name,conversion_rate,opportunity.currency) + + frappe.db.set_value( "Opportunity", opportunity.name, From aedd0397b846193c91cf207a8c9285596198cc1a Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 25 Aug 2022 22:35:08 +0200 Subject: [PATCH 103/898] chore: remove debug (cherry picked from commit ac66538651f3b66c300678a89d7e53642af46e97) --- .../v14_0/update_opportunity_currency_fields.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py index 3a6f1b3a5de5..7f093ce8c672 100644 --- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -1,11 +1,10 @@ +import click import frappe from frappe.utils import flt import erpnext from erpnext.setup.utils import get_exchange_rate -import click - def execute(): frappe.reload_doctype("Opportunity") @@ -18,7 +17,7 @@ def execute(): for opportunity in opportunities: company_currency = erpnext.get_company_currency(opportunity.company) - if opportunity.currency is None or opportunity.currency == '': + if opportunity.currency is None or opportunity.currency == "": opportunity.currency = company_currency frappe.db.set_value( "Opportunity", @@ -27,7 +26,7 @@ def execute(): update_modified=False, ) click.secho( - f" Opportunity `{opportunity.name}` has no currency set. We for it to company currency : `{opportunity.currency}`\"\n", + f' Opportunity `{opportunity.name}` has no currency set. We for it to company currency : `{opportunity.currency}`"\n', fg="yellow", ) @@ -39,10 +38,6 @@ def execute(): conversion_rate = 1 base_opportunity_amount = flt(opportunity.opportunity_amount) - if conversion_rate is None: - print(opportunity.name,conversion_rate,opportunity.currency) - - frappe.db.set_value( "Opportunity", opportunity.name, From 357f74a5808ec0164960e375422d8225a57d55e0 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 25 Aug 2022 22:35:44 +0200 Subject: [PATCH 104/898] chore: better text (cherry picked from commit d19b664ba950828c375f312f25356b474b53759b) --- erpnext/patches/v14_0/update_opportunity_currency_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py index 7f093ce8c672..17df853fb132 100644 --- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -26,7 +26,7 @@ def execute(): update_modified=False, ) click.secho( - f' Opportunity `{opportunity.name}` has no currency set. We for it to company currency : `{opportunity.currency}`"\n', + f' Opportunity `{opportunity.name}` has no currency set. We force it to company currency : `{opportunity.currency}`"\n', fg="yellow", ) From 6976316ada2e7f7bc9062f5ce6e5d1cea55bbf89 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 4 Sep 2022 19:03:16 +0530 Subject: [PATCH 105/898] chore: fix message (cherry picked from commit 118b0c0f86fd90ae5ec27ab42cd9bfdf839f5096) --- erpnext/patches/v14_0/update_opportunity_currency_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py index 17df853fb132..b803e9fa2dd2 100644 --- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -26,7 +26,7 @@ def execute(): update_modified=False, ) click.secho( - f' Opportunity `{opportunity.name}` has no currency set. We force it to company currency : `{opportunity.currency}`"\n', + f' Opportunity `{opportunity.name}` has no currency set. Setting it to company currency as default: `{opportunity.currency}`"\n', fg="yellow", ) From 00a73c7a571d916b55ba3699afb9eeb02ded5d0b Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 2 Sep 2022 17:58:40 +0530 Subject: [PATCH 106/898] fix(Appointment): create lead notes as child table (cherry picked from commit 58e553151e8ee29a6c908e9bb9021da90f3180a1) --- erpnext/crm/doctype/appointment/appointment.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 5f5923dc89b8..6e7ba1fd5bcc 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import get_url, getdate +from frappe.utils import get_url, getdate, now from frappe.utils.verified_command import get_signed_params @@ -104,16 +104,28 @@ def create_lead_and_link(self): # Return if already linked if self.party: return + lead = frappe.get_doc( { "doctype": "Lead", "lead_name": self.customer_name, "email_id": self.customer_email, - "notes": self.customer_details, "phone": self.customer_phone_number, } ) + + if self.customer_details: + lead.append( + "notes", + { + "note": self.customer_details, + "added_by": frappe.session.user, + "added_on": now(), + }, + ) + lead.insert(ignore_permissions=True) + # Link lead self.party = lead.name From b7e8fbe43fe2c4112b65751820f073ebe0c3485c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 2 Sep 2022 18:30:39 +0530 Subject: [PATCH 107/898] test: dont create lead manually, add coverage for notes (cherry picked from commit 875ff151099af34708aefc094549d1b6988261ed) --- .../doctype/appointment/test_appointment.py | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 776e60433315..178b9d2de537 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -6,29 +6,20 @@ import frappe - -def create_test_lead(): - test_lead = frappe.db.get_value("Lead", {"email_id": "test@example.com"}) - if test_lead: - return frappe.get_doc("Lead", test_lead) - test_lead = frappe.get_doc( - {"doctype": "Lead", "lead_name": "Test Lead", "email_id": "test@example.com"} - ) - test_lead.insert(ignore_permissions=True) - return test_lead +LEAD_EMAIL = "test_appointment_lead@example.com" -def create_test_appointments(): +def create_test_appointment(): test_appointment = frappe.get_doc( { "doctype": "Appointment", - "email": "test@example.com", "status": "Open", "customer_name": "Test Lead", "customer_phone_number": "666", "customer_skype": "test", - "customer_email": "test@example.com", + "customer_email": LEAD_EMAIL, "scheduled_time": datetime.datetime.now(), + "customer_details": "Hello, Friend!", } ) test_appointment.insert() @@ -36,16 +27,16 @@ def create_test_appointments(): class TestAppointment(unittest.TestCase): - test_appointment = test_lead = None + def setUpClass(): + frappe.db.delete("Lead", {"email_id": LEAD_EMAIL}) def setUp(self): - self.test_lead = create_test_lead() - self.test_appointment = create_test_appointments() + self.test_appointment = create_test_appointment() + self.test_appointment.set_verified(self.test_appointment.customer_email) def test_calendar_event_created(self): cal_event = frappe.get_doc("Event", self.test_appointment.calendar_event) self.assertEqual(cal_event.starts_on, self.test_appointment.scheduled_time) def test_lead_linked(self): - lead = frappe.get_doc("Lead", self.test_lead.name) - self.assertIsNotNone(lead) + self.assertTrue(self.test_appointment.party) From d14a9a1e89901f6d2d6568c57e6b39f653ab22fe Mon Sep 17 00:00:00 2001 From: hamzaali15 Date: Mon, 5 Sep 2022 11:15:43 +0500 Subject: [PATCH 108/898] fix: QR Code multi currency issue When try to scan qr code on app it is showing correct values for multi currencies because it is not getting base amount (cherry picked from commit b10a2b87b65f3c29ed0aae7f30099e9c8ad75377) --- erpnext/regional/saudi_arabia/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index b47adc95f723..cac5ec113e83 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -84,7 +84,7 @@ def create_qr_code(doc, method=None): tlv_array.append("".join([tag, length, value])) # Invoice Amount - invoice_amount = str(doc.grand_total) + invoice_amount = str(doc.base_grand_total) tag = bytes([4]).hex() length = bytes([len(invoice_amount)]).hex() value = invoice_amount.encode("utf-8").hex() @@ -144,7 +144,7 @@ def get_vat_amount(doc): for tax in doc.get("taxes"): if tax.account_head in vat_accounts: - vat_amount += tax.tax_amount + vat_amount += tax.base_tax_amount return vat_amount From 26536da74b9a67d46c0164320d481f21a18afc0b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sun, 4 Sep 2022 22:41:48 +0530 Subject: [PATCH 109/898] fix: validate available qty for consumption in SCR (cherry picked from commit 4a7add2169942caa8f21013695ae326c91f087c3) --- .../subcontracting_receipt.py | 44 +++++++++++++++++ .../test_subcontracting_receipt.py | 49 +++++++++++++++++++ .../subcontracting_receipt_supplied_item.json | 21 +++++--- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 021d9aa85473..1da73405e8de 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -75,6 +75,7 @@ def validate(self): self.get_current_stock() def on_submit(self): + self.validate_available_qty_for_consumption() self.update_status_updater_args() self.update_prevdoc_status() self.set_subcontracting_order_status() @@ -107,10 +108,42 @@ def set_missing_values(self): self.set_missing_values_in_supplied_items() self.set_missing_values_in_items() + def set_available_qty_for_consumption(self): + supplied_items_details = {} + + sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item") + for item in self.get("items"): + supplied_items = ( + frappe.qb.from_(sco_supplied_item) + .select( + sco_supplied_item.rm_item_code, + sco_supplied_item.reference_name, + (sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"), + ) + .where( + (sco_supplied_item.parent == item.subcontracting_order) + & (sco_supplied_item.main_item_code == item.item_code) + & (sco_supplied_item.reference_name == item.subcontracting_order_item) + ) + ).run(as_dict=True) + + if supplied_items: + supplied_items_details[item.name] = {} + + for supplied_item in supplied_items: + supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty + else: + for item in self.get("supplied_items"): + item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get( + item.rm_item_code, 0 + ) + def set_missing_values_in_supplied_items(self): for item in self.get("supplied_items") or []: item.amount = item.rate * item.consumed_qty + self.set_available_qty_for_consumption() + def set_missing_values_in_items(self): rm_supp_cost = {} for item in self.get("supplied_items") or []: @@ -147,6 +180,17 @@ def validate_rejected_warehouse(self): _("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code) ) + def validate_available_qty_for_consumption(self): + for item in self.get("supplied_items"): + if ( + item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty + ): + frappe.throw( + _( + "Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table." + ).format(item.idx) + ) + def set_items_cost_center(self): if self.company: cost_center = frappe.get_cached_value("Company", self.company, "cost_center") diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 763e76882e09..a47af52b337f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -70,6 +70,55 @@ def test_subcontracting(self): rm_supp_cost = sum(item.amount for item in scr.get("supplied_items")) self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost)) + def test_available_qty_for_consumption(self): + make_stock_entry( + item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100 + ) + make_stock_entry( + item_code="_Test Item Home Desktop 100", + qty=100, + target="_Test Warehouse 1 - _TC", + basic_rate=100, + ) + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": "_Test FG Item", + "fg_item_qty": 10, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = [ + { + "main_item_code": "_Test FG Item", + "item_code": "_Test Item", + "qty": 5.0, + "rate": 100.0, + "stock_uom": "_Test UOM", + "warehouse": "_Test Warehouse - _TC", + }, + { + "main_item_code": "_Test FG Item", + "item_code": "_Test Item Home Desktop 100", + "qty": 10.0, + "rate": 100.0, + "stock_uom": "_Test UOM", + "warehouse": "_Test Warehouse - _TC", + }, + ] + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + self.assertRaises(frappe.ValidationError, scr.submit) + def test_subcontracting_gle_fg_item_rate_zero(self): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json index 100a8060e8c4..ddbb80661adc 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json @@ -19,6 +19,7 @@ "col_break2", "amount", "secbreak_2", + "available_qty_for_consumption", "required_qty", "col_break3", "consumed_qty", @@ -75,8 +76,7 @@ { "fieldname": "required_qty", "fieldtype": "Float", - "in_list_view": 1, - "label": "Available Qty For Consumption", + "label": "Required Qty", "print_hide": 1, "read_only": 1 }, @@ -85,7 +85,7 @@ "fieldname": "consumed_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty to be Consumed", + "label": "Consumed Qty", "reqd": 1 }, { @@ -179,12 +179,21 @@ "options": "Subcontracting Order", "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "available_qty_for_consumption", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Available Qty For Consumption", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-04-18 10:45:16.538479", + "modified": "2022-09-02 22:28:53.392381", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Supplied Item", @@ -193,6 +202,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file From 121ec83562fc53400cd9aab383ba04783654b58b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 5 Sep 2022 13:55:55 +0530 Subject: [PATCH 110/898] refactor(test): test_update_reserved_qty_for_subcontracting (cherry picked from commit a349b58306d5f0723f4f13181a4c11bcc01af42e) --- .../test_subcontracting_order.py | 112 ++++++++---------- 1 file changed, 49 insertions(+), 63 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 098242aed89f..f4a943f88dc0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -187,22 +187,13 @@ def test_make_rm_stock_entry_for_batch_items(self): self.assertEqual(len(ste.items), len(rm_items)) def test_update_reserved_qty_for_subcontracting(self): - # Make stock available for raw materials - make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) + # Create RM Material Receipt + make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100) make_stock_entry( target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100 ) - make_stock_entry( - target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100 - ) - make_stock_entry( - target="_Test Warehouse 1 - _TC", - item_code="_Test Item Home Desktop 100", - qty=30, - basic_rate=100, - ) - bin1 = frappe.db.get_value( + bin_before_sco = frappe.db.get_value( "Bin", filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], @@ -222,102 +213,97 @@ def test_update_reserved_qty_for_subcontracting(self): ] sco = get_subcontracting_order(service_items=service_items) - bin2 = frappe.db.get_value( + bin_after_sco = frappe.db.get_value( "Bin", filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1, ) - self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) - self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10) - self.assertNotEqual(bin1.modified, bin2.modified) + # reserved_qty_for_sub_contract should be increased by 10 + self.assertEqual( + bin_after_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract + 10 + ) + + # projected_qty should be decreased by 10 + self.assertEqual(bin_after_sco.projected_qty, bin_before_sco.projected_qty - 10) + + self.assertNotEqual(bin_before_sco.modified, bin_after_sco.modified) - # Create stock transfer + # Create Stock Entry(Send to Subcontractor) rm_items = [ { "item_code": "_Test FG Item", "rm_item_code": "_Test Item", "item_name": "_Test Item", - "qty": 6, + "qty": 10, + "warehouse": "_Test Warehouse - _TC", + "rate": 100, + "amount": 1000, + "stock_uom": "Nos", + }, + { + "item_code": "_Test FG Item", + "rm_item_code": "_Test Item Home Desktop 100", + "item_name": "_Test Item Home Desktop 100", + "qty": 20, "warehouse": "_Test Warehouse - _TC", "rate": 100, - "amount": 600, + "amount": 2000, "stock_uom": "Nos", - } + }, ] ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items)) ste.to_warehouse = "_Test Warehouse 1 - _TC" ste.save() ste.submit() - bin3 = frappe.db.get_value( + bin_after_rm_transfer = frappe.db.get_value( "Bin", filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1, ) - self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) - - make_stock_entry( - target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100 + # reserved_qty_for_sub_contract should be decreased by 10 + self.assertEqual( + bin_after_rm_transfer.reserved_qty_for_sub_contract, + bin_after_sco.reserved_qty_for_sub_contract - 10, ) - make_stock_entry( - target="_Test Warehouse 1 - _TC", - item_code="_Test Item Home Desktop 100", - qty=40, - basic_rate=100, - ) - - # Make SCR against the SCO - scr = make_subcontracting_receipt(sco.name) - scr.save() - scr.submit() - - bin4 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) - # Cancel SCR - scr.reload() - scr.cancel() - bin5 = frappe.db.get_value( - "Bin", - filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, - fieldname="reserved_qty_for_sub_contract", - as_dict=1, - ) - - self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) - - # Cancel Stock Entry + # Cancel Stock Entry(Send to Subcontractor) ste.cancel() - bin6 = frappe.db.get_value( + bin_after_cancel_ste = frappe.db.get_value( "Bin", filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1, ) - self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + # reserved_qty_for_sub_contract should be increased by 10 + self.assertEqual( + bin_after_cancel_ste.reserved_qty_for_sub_contract, + bin_after_rm_transfer.reserved_qty_for_sub_contract + 10, + ) - # Cancel PO + # Cancel SCO sco.reload() sco.cancel() - bin7 = frappe.db.get_value( + bin_after_cancel_sco = frappe.db.get_value( "Bin", filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1, ) - self.assertEqual(bin7.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + # reserved_qty_for_sub_contract should be decreased by 10 + self.assertEqual( + bin_after_cancel_sco.reserved_qty_for_sub_contract, + bin_after_cancel_ste.reserved_qty_for_sub_contract - 10, + ) + self.assertEqual( + bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract + ) def test_exploded_items(self): item_code = "_Test Subcontracted FG Item 11" From 3c688dfa6d21dec32b2f8578a841844dd7f509f9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 6 Sep 2022 11:04:13 +0530 Subject: [PATCH 111/898] feat: tabbed view for Employee form (#31940) (#32095) --- erpnext/setup/doctype/employee/employee.json | 226 +++++++++++++------ 1 file changed, 151 insertions(+), 75 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 7a806d5906fc..39e0acd02aa0 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -10,79 +10,89 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "basic_details_tab", "basic_information", "employee", "naming_series", "first_name", "middle_name", "last_name", - "salutation", "employee_name", - "image", - "column_break1", - "company", - "status", + "column_break_9", "gender", "date_of_birth", + "salutation", + "column_break1", "date_of_joining", - "employee_number", - "emergency_contact_details", - "person_to_be_contacted", - "relation", - "column_break_19", - "emergency_phone_number", + "image", + "status", "erpnext_user", "user_id", "create_user", "create_user_permission", + "company_details_section", + "company", + "department", + "employee_number", + "column_break_25", + "designation", + "reports_to", + "column_break_18", + "branch", "employment_details", "scheduled_confirmation_date", + "column_break_32", "final_confirmation_date", - "col_break_22", "contract_end_date", + "col_break_22", "notice_number_of_days", "date_of_retirement", - "job_profile", - "department", - "designation", - "reports_to", - "column_break_31", - "branch", + "contact_details", + "cell_number", + "column_break_40", + "personal_email", + "company_email", + "column_break4", + "prefered_contact_email", + "prefered_email", + "unsubscribed", + "address_section", + "current_address", + "current_accommodation_type", + "column_break_46", + "permanent_address", + "permanent_accommodation_type", + "emergency_contact_details", + "person_to_be_contacted", + "column_break_55", + "emergency_phone_number", + "column_break_19", + "relation", "attendance_and_leave_details", "attendance_device_id", "column_break_44", "holiday_list", "salary_information", - "salary_currency", "ctc", - "payroll_cost_center", - "column_break_52", + "salary_currency", + "salary_mode", + "bank_details_section", "bank_name", "bank_ac_no", - "contact_details", - "cell_number", - "prefered_email", - "personal_email", - "unsubscribed", - "permanent_accommodation_type", - "permanent_address", - "column_break4", - "prefered_contact_email", - "company_email", - "current_accommodation_type", - "current_address", - "sb53", - "bio", "personal_details", - "passport_number", - "date_of_issue", - "valid_upto", - "place_of_issue", "marital_status", - "blood_group", - "column_break6", "family_background", + "column_break6", + "blood_group", "health_details", + "passport_details_section", + "passport_number", + "valid_upto", + "column_break_73", + "date_of_issue", + "place_of_issue", + "profile_tab", + "bio", "educational_qualification", "education", "previous_work_experience", @@ -92,16 +102,20 @@ "exit", "resignation_letter_date", "relieving_date", - "reason_for_leaving", - "leave_encashed", - "encashment_date", "exit_interview_details", "held_on", "new_workplace", + "column_break_99", + "leave_encashed", + "encashment_date", + "feedback_section", + "reason_for_leaving", + "column_break_104", "feedback", "lft", "rgt", - "old_parent" + "old_parent", + "connections_tab" ], "fields": [ { @@ -261,7 +275,7 @@ "collapsible": 1, "fieldname": "erpnext_user", "fieldtype": "Section Break", - "label": "ERPNext User" + "label": "User Details" }, { "description": "System User (login) ID. If set, it will become default for all HR forms.", @@ -289,8 +303,8 @@ "allow_in_quick_entry": 1, "collapsible": 1, "fieldname": "employment_details", - "fieldtype": "Section Break", - "label": "Joining Details" + "fieldtype": "Tab Break", + "label": "Joining" }, { "fieldname": "scheduled_confirmation_date", @@ -331,12 +345,6 @@ "oldfieldname": "date_of_retirement", "oldfieldtype": "Date" }, - { - "collapsible": 1, - "fieldname": "job_profile", - "fieldtype": "Section Break", - "label": "Department" - }, { "fieldname": "department", "fieldtype": "Link", @@ -366,10 +374,6 @@ "oldfieldtype": "Link", "options": "Employee" }, - { - "fieldname": "column_break_31", - "fieldtype": "Column Break" - }, { "fieldname": "branch", "fieldtype": "Link", @@ -391,7 +395,7 @@ { "collapsible": 1, "fieldname": "salary_information", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Salary Details", "oldfieldtype": "Section Break", "width": "50%" @@ -423,8 +427,8 @@ { "collapsible": 1, "fieldname": "contact_details", - "fieldtype": "Section Break", - "label": "Contact Details" + "fieldtype": "Tab Break", + "label": "Contact" }, { "fieldname": "cell_number", @@ -493,12 +497,6 @@ "fieldtype": "Small Text", "label": "Current Address" }, - { - "collapsible": 1, - "fieldname": "sb53", - "fieldtype": "Section Break", - "label": "Personal Bio" - }, { "description": "Short biography for website and other publications.", "fieldname": "bio", @@ -508,7 +506,7 @@ { "collapsible": 1, "fieldname": "personal_details", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Personal Details" }, { @@ -601,7 +599,7 @@ { "collapsible": 1, "fieldname": "exit", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Exit", "oldfieldtype": "Section Break" }, @@ -702,7 +700,7 @@ { "collapsible": 1, "fieldname": "attendance_and_leave_details", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Attendance and Leave Details" }, { @@ -713,10 +711,6 @@ "fieldname": "column_break_19", "fieldtype": "Column Break" }, - { - "fieldname": "column_break_52", - "fieldtype": "Column Break" - }, { "fieldname": "salary_currency", "fieldtype": "Link", @@ -728,13 +722,95 @@ "fieldtype": "Currency", "label": "Cost to Company (CTC)", "options": "salary_currency" + }, + { + "fieldname": "basic_details_tab", + "fieldtype": "Tab Break", + "label": "Basic Details" + }, + { + "fieldname": "company_details_section", + "fieldtype": "Section Break", + "label": "Company Details" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "address_section", + "fieldtype": "Section Break", + "label": "Address" + }, + { + "fieldname": "column_break_46", + "fieldtype": "Column Break" + }, + { + "fieldname": "profile_tab", + "fieldtype": "Tab Break", + "label": "Profile" + }, + { + "fieldname": "passport_details_section", + "fieldtype": "Section Break", + "label": "Passport Details" + }, + { + "fieldname": "column_break_73", + "fieldtype": "Column Break" + }, + { + "fieldname": "bank_details_section", + "fieldtype": "Section Break", + "label": "Bank Details" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_32", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_40", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_55", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_99", + "fieldtype": "Column Break" + }, + { + "fieldname": "feedback_section", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "fieldname": "column_break_104", + "fieldtype": "Column Break" } ], "icon": "fa fa-user", "idx": 24, "image_field": "image", "links": [], - "modified": "2022-06-27 01:29:32.952091", + "modified": "2022-08-23 13:47:46.944993", "modified_by": "Administrator", "module": "Setup", "name": "Employee", From 76ae4d87ca3f664b9d0a422aa3f63c335018181d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 2 Sep 2022 18:43:55 +0530 Subject: [PATCH 112/898] fix: fetch from parent not working for custom field --- erpnext/controllers/selling_controller.py | 2 ++ erpnext/controllers/stock_controller.py | 12 ++++++++ .../inventory_dimension.js | 28 +++++++++++++++++++ .../inventory_dimension.json | 5 ++-- .../inventory_dimension.py | 27 ++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index a3d41ab29afc..5e9c069b1d5b 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -311,6 +311,7 @@ def get_item_list(self): "sales_invoice_item": d.get("sales_invoice_item"), "dn_detail": d.get("dn_detail"), "incoming_rate": p.get("incoming_rate"), + "item_row": p, } ) ) @@ -334,6 +335,7 @@ def get_item_list(self): "sales_invoice_item": d.get("sales_invoice_item"), "dn_detail": d.get("dn_detail"), "incoming_rate": d.get("incoming_rate"), + "item_row": d, } ) ) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 36bed36484ec..4efe25a7a3e2 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -390,6 +390,10 @@ def get_sl_entries(self, d, args): return sl_dict def update_inventory_dimensions(self, row, sl_dict) -> None: + # To handle delivery note and sales invoice + if row.get("item_row"): + row = row.get("item_row") + dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self) for dimension in dimensions: if not dimension: @@ -407,9 +411,17 @@ def update_inventory_dimensions(self, row, sl_dict) -> None: "DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" ) + if not fieldname: + fieldname = frappe.get_cached_value( + "Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" + ) + if fieldname and self.get(fieldname): sl_dict[dimension.target_fieldname] = self.get(fieldname) + if sl_dict[dimension.target_fieldname]: + row.set(dimension.source_fieldname, sl_dict[dimension.target_fieldname]) + def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): from erpnext.stock.stock_ledger import make_sl_entries diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js index 07cb73b1d56b..034f14e2a42f 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js @@ -52,6 +52,34 @@ frappe.ui.form.on('Inventory Dimension', { } }, + onload(frm) { + frm.trigger("set_parent_fields"); + }, + + document_type(frm) { + frm.trigger("set_parent_fields"); + }, + + set_parent_fields(frm) { + if (frm.doc.apply_to_all_doctypes) { + frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document); + } else if (frm.doc.document_type && frm.doc.istable) { + frappe.call({ + method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields', + args: { + child_doctype: frm.doc.document_type, + dimension_name: frm.doc.reference_document + }, + callback: (r) => { + if (r.message && r.message.length) { + frm.set_df_property("fetch_from_parent", "options", + [""].concat(r.message)); + } + } + }) + } + }, + delete_dimension(frm) { let msg = (` Custom fields related to this dimension will be deleted on deletion of dimension. diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index 03e7fda84117..09f4f63031c1 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -144,16 +144,15 @@ "fieldtype": "Column Break" }, { - "depends_on": "istable", "description": "Set fieldname or DocType name like Supplier, Customer etc.", "fieldname": "fetch_from_parent", - "fieldtype": "Data", + "fieldtype": "Select", "label": "Fetch Value From Parent Form" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-08-17 11:43:24.722441", + "modified": "2022-09-02 13:29:04.098469", "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 4ff8f33b4095..9e8c10b394db 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -236,3 +236,30 @@ def get_inventory_dimensions(): def delete_dimension(dimension): doc = frappe.get_doc("Inventory Dimension", dimension) doc.delete() + + +@frappe.whitelist() +def get_parent_fields(child_doctype, dimension_name): + parent_doctypes = frappe.get_all( + "DocField", fields=["parent"], filters={"options": child_doctype} + ) + + fields = [] + + fields.extend( + frappe.get_all( + "DocField", + fields=["fieldname as value", "label"], + filters={"options": dimension_name, "parent": ("in", [d.parent for d in parent_doctypes])}, + ) + ) + + fields.extend( + frappe.get_all( + "Custom Field", + fields=["fieldname as value", "label"], + filters={"options": dimension_name, "dt": ("in", [d.parent for d in parent_doctypes])}, + ) + ) + + return fields From 6ab0637b0bde4f26293b9e9387eb78ff2477d66c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 3 Sep 2022 17:09:24 +0530 Subject: [PATCH 113/898] test: test cases for PI and DN --- erpnext/controllers/stock_controller.py | 4 +- .../inventory_dimension.js | 7 +- .../test_inventory_dimension.py | 77 +++++++++++++++++++ 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4efe25a7a3e2..d4f9aba41d2b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -419,8 +419,8 @@ def update_inventory_dimensions(self, row, sl_dict) -> None: if fieldname and self.get(fieldname): sl_dict[dimension.target_fieldname] = self.get(fieldname) - if sl_dict[dimension.target_fieldname]: - row.set(dimension.source_fieldname, sl_dict[dimension.target_fieldname]) + if sl_dict[dimension.target_fieldname] and self.docstatus == 1: + row.db_set(dimension.source_fieldname, sl_dict[dimension.target_fieldname]) def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): from erpnext.stock.stock_ledger import make_sl_entries diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js index 034f14e2a42f..79e7895f6d0b 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js @@ -30,6 +30,7 @@ frappe.ui.form.on('Inventory Dimension', { onload(frm) { frm.trigger('render_traget_field'); + frm.trigger("set_parent_fields"); }, refresh(frm) { @@ -52,10 +53,6 @@ frappe.ui.form.on('Inventory Dimension', { } }, - onload(frm) { - frm.trigger("set_parent_fields"); - }, - document_type(frm) { frm.trigger("set_parent_fields"); }, @@ -76,7 +73,7 @@ frappe.ui.form.on('Inventory Dimension', { [""].concat(r.message)); } } - }) + }); } }, diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index cc90b74ee85f..19ddc449f0e0 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -2,14 +2,17 @@ # See license.txt import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.tests.utils import FrappeTestCase +from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.inventory_dimension.inventory_dimension import ( CanNotBeChildDoc, CanNotBeDefaultDimension, DoNotChangeError, delete_dimension, ) +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -136,6 +139,58 @@ def test_inventory_dimension(self): self.assertTrue(inv_dim1.has_stock_ledger()) self.assertRaises(DoNotChangeError, inv_dim1.save) + def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self): + create_inventory_dimension( + reference_document="Rack", + type_of_transaction="Both", + dimension_name="Rack", + apply_to_all_doctypes=1, + fetch_from_parent="Rack", + ) + + create_custom_field( + "Purchase Receipt", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack") + ) + + create_custom_field( + "Delivery Note", dict(fieldname="rack", label="Rack", fieldtype="Link", options="Rack") + ) + + frappe.reload_doc("stock", "doctype", "purchase_receipt_item") + frappe.reload_doc("stock", "doctype", "delivery_note_item") + + pr_doc = make_purchase_receipt(qty=2, do_not_submit=True) + pr_doc.rack = "Rack 1" + pr_doc.save() + pr_doc.submit() + + pr_doc.load_from_db() + + self.assertEqual(pr_doc.items[0].rack, "Rack 1") + sle_rack = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_detail_no": pr_doc.items[0].name, "voucher_type": pr_doc.doctype}, + "rack", + ) + + self.assertEqual(sle_rack, "Rack 1") + + dn_doc = create_delivery_note(qty=2, do_not_submit=True) + dn_doc.rack = "Rack 1" + dn_doc.save() + dn_doc.submit() + + dn_doc.load_from_db() + + self.assertEqual(dn_doc.items[0].rack, "Rack 1") + sle_rack = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_detail_no": dn_doc.items[0].name, "voucher_type": dn_doc.doctype}, + "rack", + ) + + self.assertEqual(sle_rack, "Rack 1") + def prepare_test_data(): if not frappe.db.exists("DocType", "Shelf"): @@ -160,6 +215,28 @@ def prepare_test_data(): create_warehouse("Shelf Warehouse") + if not frappe.db.exists("DocType", "Rack"): + frappe.get_doc( + { + "doctype": "DocType", + "name": "Rack", + "module": "Stock", + "custom": 1, + "naming_rule": "By fieldname", + "autoname": "field:rack_name", + "fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}], + "permissions": [ + {"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1} + ], + } + ).insert(ignore_permissions=True) + + for rack in ["Rack 1"]: + if not frappe.db.exists("Rack", rack): + frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True) + + create_warehouse("Rack Warehouse") + def create_inventory_dimension(**args): args = frappe._dict(args) From 7235c3f88fb26b3bd65c115bf463df52804c85db Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 6 Sep 2022 16:04:45 +0000 Subject: [PATCH 114/898] chore(release): Bumped to Version 14.1.0 # [14.1.0](https://github.com/frappe/erpnext/compare/v14.0.3...v14.1.0) (2022-09-06) ### Bug Fixes * **Appointment:** create lead notes as child table ([00a73c7](https://github.com/frappe/erpnext/commit/00a73c7a571d916b55ba3699afb9eeb02ded5d0b)) * fetch from parent not working for custom field ([76ae4d8](https://github.com/frappe/erpnext/commit/76ae4d87ca3f664b9d0a422aa3f63c335018181d)) * force delete old report docs (backport [#32026](https://github.com/frappe/erpnext/issues/32026)) ([#32027](https://github.com/frappe/erpnext/issues/32027)) ([0e9a1fb](https://github.com/frappe/erpnext/commit/0e9a1fb40e0b5310e69d85f06610f4879f3db378)) * include payment against PO in AR/AP report ([4bbd0ec](https://github.com/frappe/erpnext/commit/4bbd0ec985cf1f6b26f067412a481766ca25511a)) * incorrect import parameter for cancel PDA ([f55881a](https://github.com/frappe/erpnext/commit/f55881aef80b0edb806ef4753e997d03bb848de9)) * key error on consolidated financial report ([4409f11](https://github.com/frappe/erpnext/commit/4409f1128233f3dfce7209e6c5272ede21859d7c)) * KSA VAT report multi currency amount issue ([b96526e](https://github.com/frappe/erpnext/commit/b96526eefdd3f16fb4e980ee02c39e866ddb6044)) * Loan Interest accruals for 0 rated loans ([1b9082e](https://github.com/frappe/erpnext/commit/1b9082e07b06dadb878933e1cc5cb2f865fb2710)) * Naming series in Journal Entry Template ([5520c6b](https://github.com/frappe/erpnext/commit/5520c6b2f3421aacdd1b0f0f42df9a6cbb3442b8)) * not able to make variant item ([f871dd4](https://github.com/frappe/erpnext/commit/f871dd4ef6b4a6f7b04353c63ddaff7bec4e9147)) * **pos:** error while consolidating pos invoices ([319ee41](https://github.com/frappe/erpnext/commit/319ee41403083b86325aab514cf84f4d39100340)) * remove spaces and order import ([055556b](https://github.com/frappe/erpnext/commit/055556b7f1327cfc62f88b0028c1649798e1aca4)) * upgrade process to version-14 when currency opportunity wass not set ([4cb685a](https://github.com/frappe/erpnext/commit/4cb685a326f05af6652b441eeaec2daa9fb496cd)) * validate available qty for consumption in SCR ([26536da](https://github.com/frappe/erpnext/commit/26536da74b9a67d46c0164320d481f21a18afc0b)) ### Features * better Item Price list view ([#31954](https://github.com/frappe/erpnext/issues/31954)) ([784fb47](https://github.com/frappe/erpnext/commit/784fb471976f41e50476922e293c861fe6773684)) * tabbed view for Employee form ([#31940](https://github.com/frappe/erpnext/issues/31940)) ([#32095](https://github.com/frappe/erpnext/issues/32095)) ([3c688df](https://github.com/frappe/erpnext/commit/3c688dfa6d21dec32b2f8578a841844dd7f509f9)) * two new filters for gross profit ([60fa421](https://github.com/frappe/erpnext/commit/60fa4214098c2d4d81db8f23fd08e4b512836f4d)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index e980e96fd334..2b9c6c8c2413 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.0.3" +__version__ = "14.1.0" def get_default_company(user=None): From 09d8fa4b5dbb821b2cad78ef4ea0e6b01ee755c9 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 7 Sep 2022 12:42:12 +0530 Subject: [PATCH 115/898] fix: AD not getting copied from SCO while creating a Material Transfer (#32109) fix: AD not getting copied from SCO while creating a Material Transfer (#32106) --- erpnext/controllers/subcontracting_controller.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 1372c89d470b..21504774f645 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -7,6 +7,7 @@ import frappe from frappe import _ +from frappe.model.mapper import get_mapped_doc from frappe.utils import cint, cstr, flt, get_link_to_form from erpnext.controllers.stock_controller import StockController @@ -870,7 +871,17 @@ def add_items_in_ste( def make_return_stock_entry_for_subcontract( available_materials, order_doc, rm_details, order_doctype="Subcontracting Order" ): - ste_doc = frappe.new_doc("Stock Entry") + ste_doc = get_mapped_doc( + order_doctype, + order_doc.name, + { + order_doctype: { + "doctype": "Stock Entry", + }, + }, + ignore_child_tables=True, + ) + ste_doc.purpose = "Material Transfer" if order_doctype == "Purchase Order": From 404668fcc9778017eefc6a5470e6fad7d045f1b8 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 7 Sep 2022 14:24:12 +0530 Subject: [PATCH 116/898] fix: require barcode item barcode (#32111) fix: require barcode item barcode. (#31957) * fix: require barcode item barcode. * fix: make supplier mandatory in Item Supplier DocType Co-authored-by: Sagar Sharma Co-authored-by: Devin Slauenwhite --- .../doctype/item_barcode/item_barcode.json | 3 +- .../doctype/item_supplier/item_supplier.json | 124 +++++------------- 2 files changed, 38 insertions(+), 89 deletions(-) diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.json b/erpnext/stock/doctype/item_barcode/item_barcode.json index 56832f32d30b..bda1218817ca 100644 --- a/erpnext/stock/doctype/item_barcode/item_barcode.json +++ b/erpnext/stock/doctype/item_barcode/item_barcode.json @@ -17,6 +17,7 @@ "in_list_view": 1, "label": "Barcode", "no_copy": 1, + "reqd": 1, "unique": 1 }, { @@ -36,7 +37,7 @@ ], "istable": 1, "links": [], - "modified": "2022-06-01 06:24:33.969534", + "modified": "2022-08-24 19:59:47.871677", "modified_by": "Administrator", "module": "Stock", "name": "Item Barcode", diff --git a/erpnext/stock/doctype/item_supplier/item_supplier.json b/erpnext/stock/doctype/item_supplier/item_supplier.json index 6cff8e0892e4..84649a67d001 100644 --- a/erpnext/stock/doctype/item_supplier/item_supplier.json +++ b/erpnext/stock/doctype/item_supplier/item_supplier.json @@ -1,95 +1,43 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:28:01", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2013-02-22 01:28:01", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "supplier", + "supplier_part_no" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier", - "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": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "supplier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier", + "options": "Supplier", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier_part_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Supplier Part Number", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "supplier_part_no", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Supplier Part Number", + "print_width": "200px", "width": "200px" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-02-20 13:29:32.569715", - "modified_by": "Administrator", - "module": "Stock", - "name": "Item Supplier", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2022-09-07 12:33:55.780062", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item Supplier", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file From 4bac0daf9bc33487e6c0bab91a8d18bdb9843686 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 6 Sep 2022 16:22:00 +0530 Subject: [PATCH 117/898] fix: internal transfer flow --- .../doctype/sales_invoice/sales_invoice.py | 38 ++++++++++---- .../sales_invoice_item.json | 32 +++++++++++- .../purchase_order/purchase_order.json | 10 +++- .../purchase_order_dashboard.py | 1 + .../purchase_order/test_purchase_order.py | 52 +++++++++++++++++++ .../purchase_order_item.json | 32 +++++++++--- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/status_updater.py | 14 +++++ .../doctype/sales_order/sales_order.js | 29 +++++++++++ .../sales_order_item/sales_order_item.json | 32 +++++++++++- .../doctype/delivery_note/delivery_note.py | 3 ++ .../delivery_note_item.json | 32 +++++++++++- erpnext/utilities/transaction_base.py | 3 ++ 13 files changed, 257 insertions(+), 23 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4008863e9ba7..1f5879d7bffb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -710,6 +710,7 @@ def validate_with_previous_doc(self): if ( cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")) and not self.is_return + and not self.is_internal_customer ): self.validate_rate_with_reference_doc( [["Sales Order", "sales_order", "so_detail"], ["Delivery Note", "delivery_note", "dn_detail"]] @@ -2161,6 +2162,17 @@ def update_details(source_doc, target_doc, source_parent): def update_item(source, target, source_parent): target.qty = flt(source.qty) - received_items.get(source.name, 0.0) + if source.doctype == "Purchase Order Item" and target.doctype == "Sales Order Item": + target.purchase_order = source.parent + target.purchase_order_item = source.name + + if ( + source.get("purchase_order") + and source.get("purchase_order_item") + and target.doctype == "Purchase Invoice Item" + ): + target.purchase_order = source.purchase_order + target.po_detail = source.purchase_order_item item_field_map = { "doctype": target_doctype + " Item", @@ -2187,6 +2199,12 @@ def update_item(source, target, source_parent): "serial_no": "serial_no", } ) + elif target_doctype == "Sales Order": + item_field_map["field_map"].update( + { + source_document_warehouse_field: "warehouse", + } + ) doclist = get_mapped_doc( doctype, @@ -2231,6 +2249,7 @@ def get_received_items(reference_name, doctype, reference_fieldname): def set_purchase_references(doc): # add internal PO or PR links if any + if doc.is_internal_transfer(): if doc.doctype == "Purchase Receipt": so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference) @@ -2260,15 +2279,6 @@ def set_purchase_references(doc): warehouse_map, ) - if list(so_item_map.values()): - pd_item_map, parent_child_map, warehouse_map = get_pd_details( - "Purchase Order Item", so_item_map, "sales_order_item" - ) - - update_pi_items( - doc, "po_detail", "purchase_order", so_item_map, pd_item_map, parent_child_map, warehouse_map - ) - def update_pi_items( doc, @@ -2284,13 +2294,19 @@ def update_pi_items( item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item))) if doc.update_stock: item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item)) + if not item.warehouse and item.get("purchase_order") and item.get("purchase_order_item"): + item.warehouse = frappe.db.get_value( + "Purchase Order Item", item.purchase_order_item, "warehouse" + ) def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map): for item in doc.get("items"): - item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item)) item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item)) - item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item)) + if not item.warehouse and item.get("purchase_order") and item.get("purchase_order_item"): + item.warehouse = frappe.db.get_value( + "Purchase Order Item", item.purchase_order_item, "warehouse" + ) def get_delivery_note_details(internal_reference): 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 7cddf123e244..4f97b63789a8 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -96,6 +96,10 @@ "delivery_note", "dn_detail", "delivered_qty", + "internal_transfer_section", + "purchase_order", + "column_break_92", + "purchase_order_item", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -840,12 +844,38 @@ "fieldtype": "Check", "label": "Grant Commission", "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:parent.is_internal_customer == 1", + "fieldname": "internal_transfer_section", + "fieldtype": "Section Break", + "label": "Internal Transfer" + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_92", + "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_order_item", + "fieldtype": "Data", + "label": "Purchase Order Item", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-08-26 12:06:31.205417", + "modified": "2022-09-06 14:17:43.394309", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index aa50487d78e6..acca380672d3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -60,6 +60,7 @@ "section_break_45", "before_items_section", "scan_barcode", + "set_from_warehouse", "items_col_break", "set_warehouse", "items_section", @@ -1166,13 +1167,20 @@ "hidden": 1, "label": "Is Old Subcontracting Flow", "read_only": 1 + }, + { + "depends_on": "is_internal_supplier", + "fieldname": "set_from_warehouse", + "fieldtype": "Link", + "label": "Set From Warehouse", + "options": "Warehouse" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-06-15 15:40:58.527065", + "modified": "2022-09-07 11:06:46.035093", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py index 01b55c00d6b4..05b5a8e7b8c2 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py @@ -23,5 +23,6 @@ def get_data(): "items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"], }, {"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]}, + {"label": _("Internal"), "items": ["Sales Order"]}, ], } diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index bd7e4e8d8653..6029a68824e3 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -9,6 +9,7 @@ from frappe.utils import add_days, flt, getdate, nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order from erpnext.buying.doctype.purchase_order.purchase_order import ( make_purchase_invoice as make_pi_from_po, ) @@ -796,6 +797,56 @@ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): automatically_fetch_payment_terms(enable=0) + def test_internal_transfer_flow(self): + from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + + prepare_data_for_internal_transfer() + supplier = "_Test Internal Supplier 2" + customer = "_Test Internal Customer 2" + + po = create_purchase_order( + company="_Test Company with perpetual inventory", + supplier=supplier, + warehouse="Stores - TCP1", + from_warehouse="_Test Internal Warehouse New 1 - TCP1", + qty=2, + rate=1, + ) + + so = make_inter_company_sales_order(po.name) + so.submit() + + dn = make_delivery_note(so.name) + + +def prepare_data_for_internal_transfer(): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + create_internal_customer( + "_Test Internal Customer 2", + "_Test Company with perpetual inventory", + "_Test Company with perpetual inventory", + ) + + create_internal_supplier( + "_Test Internal Supplier 2", + "_Test Company with perpetual inventory", + "_Test Company with perpetual inventory", + ) + + warehouse = create_warehouse( + "_Test Internal Warehouse New 1", company="_Test Company with perpetual inventory" + ) + + make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse=warehouse, + qty=2, + ) + def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) @@ -847,6 +898,7 @@ def create_purchase_order(**args): { "item_code": args.item or args.item_code or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", + "from_warehouse": args.from_warehouse, "qty": args.qty or 10, "rate": args.rate or 500, "schedule_date": add_days(nowdate(), 1), 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 1a9845396ff2..82e92e87bc9c 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -10,12 +10,14 @@ "item_code", "supplier_part_no", "item_name", + "brand", "product_bundle", "fg_item", "fg_item_qty", "column_break_4", "schedule_date", "expected_delivery_date", + "item_group", "section_break_5", "description", "col_break1", @@ -58,9 +60,12 @@ "base_net_rate", "base_net_amount", "warehouse_and_reference", + "from_warehouse", "warehouse", + "column_break_54", "actual_qty", "company_total_stock", + "references_section", "material_request", "material_request_item", "sales_order", @@ -73,8 +78,6 @@ "against_blanket_order", "blanket_order", "blanket_order_rate", - "item_group", - "brand", "section_break_56", "received_qty", "returned_qty", @@ -442,13 +445,13 @@ { "fieldname": "warehouse_and_reference", "fieldtype": "Section Break", - "label": "Warehouse and Reference" + "label": "Warehouse Settings" }, { "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, - "label": "Warehouse", + "label": "Target Warehouse", "oldfieldname": "warehouse", "oldfieldtype": "Link", "options": "Warehouse", @@ -760,7 +763,7 @@ "allow_on_submit": 1, "fieldname": "actual_qty", "fieldtype": "Float", - "label": "Available Qty at Warehouse", + "label": "Available Qty at Target Warehouse", "print_hide": 1, "read_only": 1 }, @@ -868,13 +871,30 @@ "fieldtype": "Float", "label": "Finished Good Item Qty", "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow" + }, + { + "depends_on": "eval:parent.is_internal_supplier", + "fieldname": "from_warehouse", + "fieldtype": "Link", + "label": "From Warehouse", + "options": "Warehouse" + }, + { + "collapsible": 1, + "fieldname": "references_section", + "fieldtype": "Section Break", + "label": "References" + }, + { + "fieldname": "column_break_54", + "fieldtype": "Column Break" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-06-17 05:29:40.602349", + "modified": "2022-09-07 11:12:38.634976", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e689d567a17a..6f321f476612 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -373,7 +373,7 @@ def validate_party_accounts(self): ) def validate_inter_company_reference(self): - if self.doctype not in ("Purchase Invoice", "Purchase Receipt", "Purchase Order"): + if self.doctype not in ("Purchase Invoice", "Purchase Receipt"): return if self.is_internal_transfer(): diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 197d2ba2dc8a..6e7d2b33c28a 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -307,6 +307,20 @@ def check_overflow_with_allowance(self, item, args): def limits_crossed_error(self, args, item, qty_or_amount): """Raise exception for limits crossed""" + if ( + self.doctype in ["Sales Invoice", "Delivery Note"] + and qty_or_amount == "amount" + and self.is_internal_customer + ): + return + + elif ( + self.doctype in ["Purchase Invoice", "Purchase Receipt"] + and qty_or_amount == "amount" + and self.is_internal_supplier + ): + return + if qty_or_amount == "qty": action_msg = _( 'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.' diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 6b6ea89b6387..386c12b6386b 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -59,7 +59,36 @@ frappe.ui.form.on("Sales Order", { }) }); } + + if (frm.doc.docstatus === 0 && frm.doc.is_internal_customer) { + frm.events.get_items_from_internal_purchase_order(frm); + } }, + + get_items_from_internal_purchase_order(frm) { + frm.add_custom_button(__('Purchase Order'), () => { + erpnext.utils.map_current_doc({ + method: 'erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order', + source_doctype: 'Purchase Order', + target: frm, + setters: [ + { + label: 'Supplier', + fieldname: 'supplier', + fieldtype: 'Link', + options: 'Supplier' + } + ], + get_query_filters: { + company: frm.doc.company, + is_internal_supplier: 1, + docstatus: 1, + status: ['!=', 'Completed'] + } + }); + }, __('Get Items From')); + }, + onload: function(frm) { if (!frm.doc.transaction_date){ frm.set_value('transaction_date', frappe.datetime.get_today()) 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 318799907edd..2cf836f9fcc7 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -92,7 +92,11 @@ "section_break_63", "page_break", "item_tax_rate", - "transaction_date" + "transaction_date", + "inter_transfer_reference_section", + "purchase_order", + "column_break_89", + "purchase_order_item" ], "fields": [ { @@ -809,12 +813,36 @@ "label": "Picked Qty (in Stock UOM)", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "inter_transfer_reference_section", + "fieldtype": "Section Break", + "label": "Inter Transfer Reference" + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_89", + "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_order_item", + "fieldtype": "Data", + "label": "Purchase Order Item", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-06-17 05:27:41.603006", + "modified": "2022-09-06 13:24:18.065312", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 0e68e8580655..36d5a6ce0e18 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -178,6 +178,7 @@ def validate_with_previous_doc(self): if ( cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")) and not self.is_return + and not self.is_internal_customer ): self.validate_rate_with_reference_doc( [ @@ -896,6 +897,8 @@ def update_details(source_doc, target_doc, source_parent): "name": "delivery_note_item", "batch_no": "batch_no", "serial_no": "serial_no", + "purchase_order": "purchase_order", + "purchase_order_item": "purchase_order_item", }, "field_no_map": ["warehouse"], }, 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 2de4842ebea4..0911cdb476ce 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -86,6 +86,10 @@ "expense_account", "allow_zero_valuation_rate", "column_break_71", + "internal_transfer_section", + "purchase_order", + "column_break_82", + "purchase_order_item", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -777,13 +781,39 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:parent.is_internal_customer == 1", + "fieldname": "internal_transfer_section", + "fieldtype": "Section Break", + "label": "Internal Transfer" + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_82", + "fieldtype": "Column Break" + }, + { + "fieldname": "purchase_order_item", + "fieldtype": "Data", + "label": "Purchase Order Item", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-06-17 05:25:47.711177", + "modified": "2022-09-06 14:19:42.876357", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index cd1bf9f321b5..21a0a551b627 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -71,6 +71,9 @@ def compare_values(self, ref_doc, fields, doc=None): self.validate_value(field, condition, prevdoc_values[field], doc) def validate_rate_with_reference_doc(self, ref_details): + if self.get("is_internal_supplier"): + return + buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"] if self.doctype in buying_doctypes: From a00bc2967e70aae4fd6985bc9e0865a6822d3138 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 7 Sep 2022 13:28:10 +0530 Subject: [PATCH 118/898] test: added test case for internal transfer --- .../purchase_order/test_purchase_order.py | 84 +++++++++++++++---- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 6029a68824e3..6c1bcc7dd490 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -7,6 +7,7 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, flt, getdate, nowdate +from frappe.utils.data import today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order @@ -798,11 +799,20 @@ def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): automatically_fetch_payment_terms(enable=0) def test_internal_transfer_flow(self): - from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note + from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + make_inter_company_purchase_invoice, + ) + from erpnext.selling.doctype.sales_order.sales_order import ( + make_delivery_note, + make_sales_invoice, + ) + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + + frappe.db.set_value("Selling Settings", None, "maintain_same_sales_rate", 1) + frappe.db.set_value("Buying Settings", None, "maintain_same_rate", 1) prepare_data_for_internal_transfer() supplier = "_Test Internal Supplier 2" - customer = "_Test Internal Customer 2" po = create_purchase_order( company="_Test Company with perpetual inventory", @@ -814,9 +824,41 @@ def test_internal_transfer_flow(self): ) so = make_inter_company_sales_order(po.name) + so.items[0].delivery_date = today() + self.assertEqual(so.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") + self.assertTrue(so.items[0].purchase_order) + self.assertTrue(so.items[0].purchase_order_item) so.submit() dn = make_delivery_note(so.name) + dn.items[0].target_warehouse = "_Test Internal Warehouse GIT - TCP1" + self.assertEqual(dn.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") + self.assertTrue(dn.items[0].purchase_order) + self.assertTrue(dn.items[0].purchase_order_item) + + self.assertEqual(po.items[0].name, dn.items[0].purchase_order_item) + dn.submit() + + pr = make_inter_company_purchase_receipt(dn.name) + self.assertEqual(pr.items[0].warehouse, "Stores - TCP1") + self.assertTrue(pr.items[0].purchase_order) + self.assertTrue(pr.items[0].purchase_order_item) + self.assertEqual(po.items[0].name, pr.items[0].purchase_order_item) + pr.submit() + + si = make_sales_invoice(so.name) + self.assertEqual(si.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") + self.assertTrue(si.items[0].purchase_order) + self.assertTrue(si.items[0].purchase_order_item) + si.submit() + + pi = make_inter_company_purchase_invoice(si.name) + self.assertTrue(pi.items[0].purchase_order) + self.assertTrue(pi.items[0].po_detail) + pi.submit() + + po.load_from_db() + self.assertEqual(po.status, "Completed") def prepare_data_for_internal_transfer(): @@ -825,27 +867,41 @@ def prepare_data_for_internal_transfer(): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + company = "_Test Company with perpetual inventory" + create_internal_customer( "_Test Internal Customer 2", - "_Test Company with perpetual inventory", - "_Test Company with perpetual inventory", + company, + company, ) create_internal_supplier( "_Test Internal Supplier 2", - "_Test Company with perpetual inventory", - "_Test Company with perpetual inventory", + company, + company, ) - warehouse = create_warehouse( - "_Test Internal Warehouse New 1", company="_Test Company with perpetual inventory" - ) + warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) - make_purchase_receipt( - company="_Test Company with perpetual inventory", - warehouse=warehouse, - qty=2, - ) + create_warehouse("_Test Internal Warehouse GIT", company=company) + + make_purchase_receipt(company=company, warehouse=warehouse, qty=2, rate=100) + + if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): + account = "Unrealized Profit and Loss - TCP1" + if not frappe.db.exists("Account", account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Unrealized Profit and Loss", + "parent_account": "Direct Income - TCP1", + "company": company, + "is_group": 0, + "account_type": "Income Account", + } + ).insert() + + frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account) def make_pr_against_po(po, received_qty=0): From 2d2eef6b2945536546896ae190baf47aa6bec42e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 7 Sep 2022 14:40:18 +0530 Subject: [PATCH 119/898] fix: inventory dimension filter's label not showing in the reort --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index f2bea5829c2a..6d64625270bf 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -226,7 +226,7 @@ $.extend(erpnext.utils, { if (!found) { filters.splice(index, 0, { "fieldname": dimension["fieldname"], - "label": __(dimension["label"]), + "label": __(dimension["doctype"]), "fieldtype": "MultiSelectList", get_data: function(txt) { return frappe.db.get_link_options(dimension["doctype"], txt); From 64831952d5c827089be95fba5bad074674ccaeea Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 7 Sep 2022 16:38:17 +0530 Subject: [PATCH 120/898] fix: option to start reposting from repost item valuation --- .../repost_item_valuation.js | 15 +++++++++++++++ .../repost_item_valuation.py | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index eae73050b23e..d595a80b20a3 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -58,6 +58,21 @@ frappe.ui.form.on('Repost Item Valuation', { } frm.trigger('show_reposting_progress'); + + if (frm.doc.status === 'Queued' && frm.doc.docstatus === 1) { + frm.trigger('execute_reposting'); + } + }, + + execute_reposting(frm) { + frm.add_custom_button(__("Start Reposting"), () => { + frappe.call({ + method: 'erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.execute_repost_item_valuation', + callback: function() { + frappe.msgprint(__('Reposting has been started in the background.')); + } + }); + }); }, show_reposting_progress: function(frm) { diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 7c57ecd73e65..c4705246b3cc 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -307,3 +307,9 @@ def in_configured_timeslot(repost_settings=None, current_time=None): return end_time >= now_time >= start_time else: return now_time >= start_time or now_time <= end_time + + +@frappe.whitelist() +def execute_repost_item_valuation(): + """Execute repost item valuation via scheduler.""" + frappe.get_doc("Scheduled Job Type", "repost_item_valuation.repost_entries").enqueue(force=True) From f2ab220ce31cef507f8f0f4eb3eac3fb1aa6684d Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 8 Sep 2022 11:18:00 +0530 Subject: [PATCH 121/898] fix: Subcontracting Receipt GL Entries (backport #31918) (#32124) fix: Subcontracting Receipt GL Entries --- erpnext/controllers/stock_controller.py | 41 ++++++ .../tests/test_subcontracting_controller.py | 7 +- erpnext/patches.txt | 1 + .../fix_subcontracting_receipt_gl_entries.py | 30 ++++ .../purchase_receipt/purchase_receipt.py | 41 ------ .../test_subcontracting_order.py | 2 +- .../subcontracting_receipt.py | 133 ++++++++++++++++++ .../test_subcontracting_receipt.py | 102 +++++++++++++- 8 files changed, 311 insertions(+), 46 deletions(-) create mode 100644 erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d4f9aba41d2b..9149b4d8570c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -700,6 +700,47 @@ def repost_future_sle_and_gle(self): else: create_repost_item_valuation_entry(args) + def add_gl_entry( + self, + gl_entries, + account, + cost_center, + debit, + credit, + remarks, + against_account, + debit_in_account_currency=None, + credit_in_account_currency=None, + account_currency=None, + project=None, + voucher_detail_no=None, + item=None, + posting_date=None, + ): + + gl_entry = { + "account": account, + "cost_center": cost_center, + "debit": debit, + "credit": credit, + "against": against_account, + "remarks": remarks, + } + + if voucher_detail_no: + gl_entry.update({"voucher_detail_no": voucher_detail_no}) + + if debit_in_account_currency: + gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) + + if credit_in_account_currency: + gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) + + if posting_date: + gl_entry.update({"posting_date": posting_date}) + + gl_entries.append(self.get_gl_dict(gl_entry, item=item)) + def repost_required_for_queue(doc: StockController) -> bool: """check if stock document contains repeated item-warehouse with queue based valuation. diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index bc503f5440a1..8490d145286f 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -897,7 +897,7 @@ def make_stock_transfer_entry(**args): "item_name": row.item_code, "rate": row.rate or 100, "stock_uom": row.stock_uom or "Nos", - "warehouse": row.warehuose or "_Test Warehouse - _TC", + "warehouse": row.warehouse or "_Test Warehouse - _TC", } item_details = args.itemwise_details.get(row.item_code) @@ -1031,9 +1031,9 @@ def get_subcontracting_order(**args): if not args.service_items: service_items = [ { - "warehouse": "_Test Warehouse - _TC", + "warehouse": args.warehouse or "_Test Warehouse - _TC", "item_code": "Subcontracted Service Item 7", - "qty": 5, + "qty": 10, "rate": 100, "fg_item": "Subcontracted Item SA7", "fg_item_qty": 10, @@ -1046,6 +1046,7 @@ def get_subcontracting_order(**args): rm_items=service_items, is_subcontracted=1, supplier_warehouse=args.supplier_warehouse or "_Test Warehouse 1 - _TC", + company=args.company, ) return create_subcontracting_order(po_name=po.name, **args) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4729add16b38..d780213209c8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -312,4 +312,5 @@ erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes +erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py b/erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py new file mode 100644 index 000000000000..159c6dc82d93 --- /dev/null +++ b/erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py @@ -0,0 +1,30 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + +from erpnext.stock.report.stock_and_account_value_comparison.stock_and_account_value_comparison import ( + get_data, +) + + +def execute(): + data = [] + + for company in frappe.db.get_list("Company", pluck="name"): + data += get_data( + frappe._dict( + { + "company": company, + } + ) + ) + + if data: + for d in data: + if d and d.get("voucher_type") == "Subcontracting Receipt": + doc = frappe.new_doc("Repost Item Valuation") + doc.voucher_type = d.get("voucher_type") + doc.voucher_no = d.get("voucher_no") + doc.save() + doc.submit() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 84da3cc41d5e..51d914dc62c7 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -631,47 +631,6 @@ def make_tax_gl_entries(self, gl_entries): i += 1 - def add_gl_entry( - self, - gl_entries, - account, - cost_center, - debit, - credit, - remarks, - against_account, - debit_in_account_currency=None, - credit_in_account_currency=None, - account_currency=None, - project=None, - voucher_detail_no=None, - item=None, - posting_date=None, - ): - - gl_entry = { - "account": account, - "cost_center": cost_center, - "debit": debit, - "credit": credit, - "against": against_account, - "remarks": remarks, - } - - if voucher_detail_no: - gl_entry.update({"voucher_detail_no": voucher_detail_no}) - - if debit_in_account_currency: - gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) - - if credit_in_account_currency: - gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) - - if posting_date: - gl_entry.update({"posting_date": posting_date}) - - gl_entries.append(self.get_gl_dict(gl_entry, item=item)) - def get_asset_gl_entry(self, gl_entries): for item in self.get("items"): if item.is_fixed_asset: diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index f4a943f88dc0..9385568c2b36 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -510,7 +510,7 @@ def create_subcontracting_order(**args): for item in sco.items: item.include_exploded_items = args.get("include_exploded_items", 1) - if args.get("warehouse"): + if args.warehouse: for item in sco.items: item.warehouse = args.warehouse else: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 1da73405e8de..cd05b745e6d7 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -5,6 +5,8 @@ from frappe import _ from frappe.utils import cint, flt, getdate, nowdate +import erpnext +from erpnext.accounts.utils import get_account_currency from erpnext.controllers.subcontracting_controller import SubcontractingController @@ -225,6 +227,137 @@ def update_status(self, status=None, update_modified=False): if status: frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified) + def get_gl_entries(self, warehouse_account=None): + from erpnext.accounts.general_ledger import process_gl_map + + gl_entries = [] + self.make_item_gl_entries(gl_entries, warehouse_account) + + return process_gl_map(gl_entries) + + def make_item_gl_entries(self, gl_entries, warehouse_account=None): + if erpnext.is_perpetual_inventory_enabled(self.company): + 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 = [] + + for item in self.items: + if flt(item.rate) and flt(item.qty): + if warehouse_account.get(item.warehouse): + stock_value_diff = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Subcontracting Receipt", + "voucher_no": self.name, + "voucher_detail_no": item.name, + "warehouse": item.warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + + warehouse_account_name = warehouse_account[item.warehouse]["account"] + warehouse_account_currency = warehouse_account[item.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") + + # FG Warehouse Account (Debit) + self.add_gl_entry( + gl_entries=gl_entries, + account=warehouse_account_name, + cost_center=item.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_rbnb, + account_currency=warehouse_account_currency, + item=item, + ) + + # Supplier Warehouse Account (Credit) + 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=warehouse_account_name, + account_currency=supplier_warehouse_account_currency, + item=item, + ) + + # Expense Account (Credit) + if flt(item.service_cost_per_qty): + self.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.service_cost_per_qty) * flt(item.qty), + remarks=remarks, + against_account=warehouse_account_name, + account_currency=get_account_currency(item.expense_account), + item=item, + ) + + # Loss Account (Credit) + 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 + + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=item.cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + account_currency=get_account_currency(loss_account), + project=item.project, + item=item, + ) + elif ( + item.warehouse not in warehouse_with_no_account + or item.rejected_warehouse not in warehouse_with_no_account + ): + warehouse_with_no_account.append(item.warehouse) + + # Additional Costs Expense Accounts (Credit) + for row in self.additional_costs: + credit_amount = ( + flt(row.base_amount) + if (row.base_amount or row.account_currency != self.company_currency) + else flt(row.amount) + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=row.expense_account, + cost_center=self.cost_center or self.get_company_default("cost_center"), + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=None, + ) + + if warehouse_with_no_account: + frappe.msgprint( + _("No accounting entries for the following warehouses") + + ": \n" + + "\n".join(warehouse_with_no_account) + ) + @frappe.whitelist() def make_subcontract_return(source_name, target_doc=None): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index a47af52b337f..090f1457d95a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -6,8 +6,10 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import flt +from frappe.utils import cint, flt +import erpnext +from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.controllers.tests.test_subcontracting_controller import ( get_rm_items, @@ -22,6 +24,7 @@ set_backflush_based_on, ) from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, @@ -366,6 +369,103 @@ def test_subcontracting_order_over_return(self): args = frappe._dict(scr_name=scr1.name, qty=-15) self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args) + def test_subcontracting_receipt_no_gl_entry(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + scr.append( + "additional_costs", + { + "expense_account": "Expenses Included In Valuation - _TC", + "description": "Test Additional Costs", + "amount": 100, + }, + ) + scr.save() + scr.submit() + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Subcontracting Receipt", + "voucher_no": scr.name, + "item_code": "Subcontracted Item SA7", + "warehouse": "_Test Warehouse - _TC", + }, + "stock_value_difference", + ) + + # Service Cost(100 * 10) + Raw Materials Cost(50 * 10) + Additional Costs(100) = 1600 + self.assertEqual(stock_value_difference, 1600) + self.assertFalse(get_gl_entries("Subcontracting Receipt", scr.name)) + + def test_subcontracting_receipt_gl_entry(self): + sco = get_subcontracting_order( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + ) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + additional_costs_expense_account = "Expenses Included In Valuation - TCP1" + scr.append( + "additional_costs", + { + "expense_account": additional_costs_expense_account, + "description": "Test Additional Costs", + "amount": 100, + "base_amount": 100, + }, + ) + scr.save() + scr.submit() + + self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(scr.company)), 1) + + gl_entries = get_gl_entries("Subcontracting Receipt", scr.name) + + self.assertTrue(gl_entries) + + fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse) + supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse) + expense_account = scr.items[0].expense_account + + if fg_warehouse_ac == supplier_warehouse_ac: + expected_values = { + fg_warehouse_ac: [2100.0, 1000.0], # FG Amount (D), RM Cost (C) + expense_account: [0.0, 1000.0], # Service Cost (C) + additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C) + } + else: + expected_values = { + fg_warehouse_ac: [2100.0, 0.0], # FG Amount (D) + supplier_warehouse_ac: [0.0, 1000.0], # RM Cost (C) + expense_account: [0.0, 1000.0], # Service Cost (C) + additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C) + } + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account][0], gle.debit) + self.assertEqual(expected_values[gle.account][1], gle.credit) + + scr.reload() + scr.cancel() + self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name)) + def make_return_subcontracting_receipt(**args): args = frappe._dict(args) From 7840fed7a50a08d9b90b27bae8096651a87e5a23 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 8 Sep 2022 18:25:27 +0530 Subject: [PATCH 122/898] fix: hide "Return of Components" button in closed SCO (backport #32130) (#32131) fix: hide "Return of Components" button in closed SCO (#32130) (cherry picked from commit 3585daab9549e8f3741d4f102fade1d42f693101) Co-authored-by: Sagar Sharma --- .../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 065ef39db3ee..40963f863730 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.supplied_items && frm.doc.per_received > 0) { + if (frm.doc.status != "Closed" && frm.doc.supplied_items && frm.doc.per_received > 0) { 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 4568648d8ce4bdc4bb74076d90abacf5edf16fa6 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 8 Sep 2022 16:04:08 +0530 Subject: [PATCH 123/898] fix: SCO Supplied Items returned-qty (cherry picked from commit ccb2889cac27c95475dbe421fa32711dd719c3bf) --- erpnext/controllers/subcontracting_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 21504774f645..bbd950ed37ad 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -877,6 +877,7 @@ def make_return_stock_entry_for_subcontract( { order_doctype: { "doctype": "Stock Entry", + "field_no_map": ["purchase_order", "subcontracting_order"], }, }, ignore_child_tables=True, From 62741bf7448796d4d58a372139dbe9cf529c7198 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 8 Sep 2022 17:02:37 +0530 Subject: [PATCH 124/898] test: add test case for returned-qty (cherry picked from commit aea71883040360b688de729cf20e9fc1b48a1800) --- .../test_subcontracting_order.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 9385568c2b36..d054ce0f9d4f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -502,6 +502,35 @@ def test_supplied_qty(self): set_backflush_based_on("BOM") + def test_get_materials_from_supplier(self): + # Create SCO + sco = get_subcontracting_order() + + # Transfer RM + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + # Create SCR (Partial) + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty -= 5 + scr.save() + scr.submit() + + # Get RM from Supplier + ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) + ste.save() + ste.submit() + + sco.load_from_db() + + self.assertEqual(sco.status, "Closed") + self.assertEqual(sco.supplied_items[0].returned_qty, 5) + def create_subcontracting_order(**args): args = frappe._dict(args) From 7ce672910015afe2e7d0383d4da2813f7901f826 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Sep 2022 19:16:00 +0530 Subject: [PATCH 125/898] fix: reposting not working for internal transferred purchase receipt (cherry picked from commit a03b4ce21335e0f1f724d89bba8eebc06d206cb5) # Conflicts: # erpnext/stock/stock_ledger.py --- .../purchase_receipt/purchase_receipt.py | 10 +- .../purchase_receipt/test_purchase_receipt.py | 120 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 32 ++++- 3 files changed, 159 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 51d914dc62c7..f85c478a72f2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -362,6 +362,12 @@ def make_item_gl_entries(self, gl_entries, warehouse_account=None): 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_supplier and d.valuation_rate: + outgoing_amount = d.valuation_rate * d.stock_qty + credit_amount = outgoing_amount + if credit_amount: account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb @@ -369,7 +375,7 @@ def make_item_gl_entries(self, gl_entries, warehouse_account=None): gl_entries=gl_entries, account=account, cost_center=d.cost_center, - debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")), + debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")), credit=0.0, remarks=remarks, against_account=warehouse_account_name, @@ -456,7 +462,7 @@ def make_item_gl_entries(self, gl_entries, warehouse_account=None): # divisional loss adjustment valuation_amount_as_per_doc = ( - flt(d.base_net_amount, d.precision("base_net_amount")) + flt(outgoing_amount, d.precision("base_net_amount")) + flt(d.landed_cost_voucher_amount) + flt(d.rm_supp_cost) + flt(d.item_tax_amount) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d0d115d96a11..b77c3a513487 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -5,6 +5,7 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, today +from pypika import functions as fn import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account @@ -1156,6 +1157,125 @@ def test_neg_to_positive(self): if gle.account == account: self.assertEqual(gle.credit, 50) + def test_backdated_transaction_for_internal_transfer(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" + + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + item_doc = create_item("Test Internal Transfer Item") + + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -1), + warehouse=from_warehouse, + qty=1, + rate=100, + ) + + dn1 = create_delivery_note( + item_code=item_doc.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=1, + rate=500, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + self.assertEqual(dn1.items[0].rate, 100) + + pr1 = make_inter_company_purchase_receipt(dn1.name) + pr1.items[0].warehouse = to_warehouse + self.assertEqual(pr1.items[0].rate, 100) + pr1.submit() + + # Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1 + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -2), + warehouse=from_warehouse, + qty=1, + rate=200, + ) + + dn_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(dn_value), 200.00) + + pr_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(pr_value), 200.00) + pr1.load_from_db() + + self.assertEqual(pr1.items[0].valuation_rate, 200) + self.assertEqual(pr1.items[0].rate, 100) + + Gl = frappe.qb.DocType("GL Entry") + + query = ( + frappe.qb.from_(Gl) + .select( + (fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"), + ) + .where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name)) + ).run(as_dict=True) + + self.assertEqual(query[0].value, 0) + + +def prepare_data_for_internal_transfer(): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + + company = "_Test Company with perpetual inventory" + + create_internal_customer( + "_Test Internal Customer 2", + company, + company, + ) + + create_internal_supplier( + "_Test Internal Supplier 2", + company, + company, + ) + + if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): + account = "Unrealized Profit and Loss - TCP1" + if not frappe.db.exists("Account", account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Unrealized Profit and Loss", + "parent_account": "Direct Income - TCP1", + "company": company, + "is_group": 0, + "account_type": "Income Account", + } + ).insert() + + frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql( diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index fd1aece7b153..9e55aa389575 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -646,6 +646,31 @@ def get_incoming_outgoing_rate_from_transaction(self, sle): voucher_detail_no=sle.voucher_detail_no, sle=sle, ) +<<<<<<< HEAD +======= + + elif ( + sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] + and sle.voucher_detail_no + and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier") + ): + field = ( + "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item" + ) + doctype = ( + "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item" + ) + refernce_name = frappe.get_cached_value( + sle.voucher_type + " Item", sle.voucher_detail_no, field + ) + + if refernce_name: + rate = frappe.get_cached_value( + doctype, + refernce_name, + "incoming_rate", + ) +>>>>>>> a03b4ce213 (fix: reposting not working for internal transferred purchase receipt) else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" @@ -727,7 +752,12 @@ def update_rate_on_delivery_and_sales_return(self, sle, outgoing_rate): def update_rate_on_purchase_receipt(self, sle, outgoing_rate): if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): frappe.db.set_value( - sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate + sle.voucher_type + " Item", + sle.voucher_detail_no, + { + "base_net_rate": outgoing_rate, + "valuation_rate": outgoing_rate, + }, ) else: frappe.db.set_value( From 5af006278ede5ae20c84132e6179f3b21dc4ec84 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 6 Sep 2022 16:42:15 +0530 Subject: [PATCH 126/898] fix: consider Stock Entry purpose while getting total supplied qty (cherry picked from commit 2f004138645bc1078acc41e4b8d2d57e8f1d9c91) --- .../stock/doctype/stock_entry/stock_entry.py | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d70952282d60..211401a56ab1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -875,25 +875,24 @@ def validate_subcontract_order(self): ) ) - parent = frappe.qb.DocType("Stock Entry") - child = frappe.qb.DocType("Stock Entry Detail") - - conditions = ( - (parent.docstatus == 1) - & (child.item_code == se_item.item_code) - & ( - (parent.purchase_order == self.purchase_order) - if self.subcontract_data.order_doctype == "Purchase Order" - else (parent.subcontracting_order == self.subcontracting_order) - ) - ) + se = frappe.qb.DocType("Stock Entry") + se_detail = frappe.qb.DocType("Stock Entry Detail") total_supplied = ( - frappe.qb.from_(parent) - .inner_join(child) - .on(parent.name == child.parent) - .select(Sum(child.transfer_qty)) - .where(conditions) + frappe.qb.from_(se) + .inner_join(se_detail) + .on(se.name == se_detail.parent) + .select(Sum(se_detail.transfer_qty)) + .where( + (se.purpose == "Send to Subcontractor") + & (se.docstatus == 1) + & (se_detail.item_code == se_item.item_code) + & ( + (se.purchase_order == self.purchase_order) + if self.subcontract_data.order_doctype == "Purchase Order" + else (se.subcontracting_order == self.subcontracting_order) + ) + ) ).run()[0][0] if flt(total_supplied, precision) > flt(total_allowed, precision): From 844f120a56822a3f87d2af09869445197515efe8 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 9 Sep 2022 13:45:31 +0530 Subject: [PATCH 127/898] fix: status filter for Subcontracting Order in Stock Entry (cherry picked from commit 30909a9b79442adf4fdc09461388c5fdf2897033) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 1bbe57080714..a952a93ac725 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -815,7 +815,8 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle return { "filters": { "docstatus": 1, - "company": me.frm.doc.company + "company": me.frm.doc.company, + "status": ["not in", ["Completed", "Closed"]] } }; }); From d3cc9d4fa6369d636a0eb34678ec86e68a7fcb49 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 9 Sep 2022 13:46:50 +0530 Subject: [PATCH 128/898] fix: validate Subcontracting Order in Stock Entry (cherry picked from commit 9a3dcb9ad163e9023df5b6417683a49b517f87b7) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 211401a56ab1..76bba8af6464 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -117,6 +117,7 @@ def validate(self): self.validate_work_order() self.validate_bom() self.validate_purchase_order() + self.validate_subcontracting_order() if self.purpose in ("Manufacture", "Repack"): self.mark_finished_and_scrap_items() @@ -959,6 +960,20 @@ def validate_purchase_order(self): ) ) + def validate_subcontracting_order(self): + if self.get("subcontracting_order") and self.purpose in [ + "Send to Subcontractor", + "Material Transfer", + ]: + sco_status = frappe.db.get_value("Subcontracting Order", self.subcontracting_order, "status") + + if sco_status == "Closed": + frappe.throw( + _("Cannot create Stock Entry against a closed Subcontracting Order {0}.").format( + self.subcontracting_order + ) + ) + def mark_finished_and_scrap_items(self): if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]): return From 467ee9793857b95352f860097aeef7696114e6e8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 7 Sep 2022 11:18:20 +0530 Subject: [PATCH 129/898] fix: import error on bank statement import Name collision between frameworks file importer's internal log and doctype fieldname - import_log. Frameworks internal log overrode, doctypes field which caused failure in basic fieldtype validation. (cherry picked from commit a6fbb80b941b40dc021824d3accec8e1cf852c22) --- .../bank_statement_import.js | 6 +++--- .../bank_statement_import.json | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 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 a964965c26fc..f74562086efa 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -141,7 +141,7 @@ frappe.ui.form.on("Bank Statement Import", { }, show_import_status(frm) { - let import_log = JSON.parse(frm.doc.import_log || "[]"); + let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); let successful_records = import_log.filter((log) => log.success); let failed_records = import_log.filter((log) => !log.success); if (successful_records.length === 0) return; @@ -309,7 +309,7 @@ frappe.ui.form.on("Bank Statement Import", { // method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template', show_import_preview(frm, preview_data) { - let import_log = JSON.parse(frm.doc.import_log || "[]"); + let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); if ( frm.import_preview && @@ -439,7 +439,7 @@ frappe.ui.form.on("Bank Statement Import", { }, show_import_log(frm) { - let import_log = JSON.parse(frm.doc.import_log || "[]"); + let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); let logs = import_log; frm.toggle_display("import_log", false); frm.toggle_display("import_log_section", logs.length > 0); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index 7ffff02850c0..eede3bdc6d12 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -24,7 +24,7 @@ "section_import_preview", "import_preview", "import_log_section", - "import_log", + "statement_import_log", "show_failed_logs", "import_log_preview", "reference_doctype", @@ -90,12 +90,6 @@ "options": "JSON", "read_only": 1 }, - { - "fieldname": "import_log", - "fieldtype": "Code", - "label": "Import Log", - "options": "JSON" - }, { "fieldname": "import_log_section", "fieldtype": "Section Break", @@ -198,11 +192,17 @@ { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "fieldname": "statement_import_log", + "fieldtype": "Code", + "label": "Statement Import Log", + "options": "JSON" } ], "hide_toolbar": 1, "links": [], - "modified": "2021-05-12 14:17:37.777246", + "modified": "2022-09-07 11:11:40.293317", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", From a40872298340a6fb2e92b3104b5e74a3b037d25b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 9 Sep 2022 22:44:10 +0530 Subject: [PATCH 130/898] fix: conflict --- erpnext/stock/stock_ledger.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9e55aa389575..50309647de02 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -646,8 +646,6 @@ def get_incoming_outgoing_rate_from_transaction(self, sle): voucher_detail_no=sle.voucher_detail_no, sle=sle, ) -<<<<<<< HEAD -======= elif ( sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] @@ -670,7 +668,6 @@ def get_incoming_outgoing_rate_from_transaction(self, sle): refernce_name, "incoming_rate", ) ->>>>>>> a03b4ce213 (fix: reposting not working for internal transferred purchase receipt) else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" From 92032176784e0699215ae013627be8a34b60caac Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sat, 10 Sep 2022 16:15:35 +0530 Subject: [PATCH 131/898] refactor: rewrite Work Order Stock Report queries in QB (cherry picked from commit d4c4dddfc3fcf609165b8d25ba426cf728735f66) --- .../work_order_stock_report.py | 129 +++++++++--------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py index 063ebba05977..998b0e4bccd8 100644 --- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py +++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull from frappe.utils import cint @@ -17,70 +18,70 @@ def execute(filters=None): def get_item_list(wo_list, filters): out = [] - # Add a row for each item/qty - for wo_details in wo_list: - desc = frappe.db.get_value("BOM", wo_details.bom_no, "description") - - for wo_item_details in frappe.db.get_values( - "Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1 - ): - - item_list = frappe.db.sql( - """SELECT - bom_item.item_code as item_code, - ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty - FROM - `tabBOM` as bom, `tabBOM Item` AS bom_item - LEFT JOIN `tabBin` AS ledger - ON bom_item.item_code = ledger.item_code - AND ledger.warehouse = ifnull(%(warehouse)s,%(filterhouse)s) - WHERE - bom.name = bom_item.parent - and bom_item.item_code = %(item_code)s - and bom.name = %(bom)s - GROUP BY - bom_item.item_code""", - { - "bom": wo_details.bom_no, - "warehouse": wo_item_details.source_warehouse, - "filterhouse": filters.warehouse, - "item_code": wo_item_details.item_code, - }, - as_dict=1, - ) - - stock_qty = 0 - count = 0 - buildable_qty = wo_details.qty - for item in item_list: - count = count + 1 - if item.build_qty >= (wo_details.qty - wo_details.produced_qty): - stock_qty = stock_qty + 1 - elif buildable_qty >= item.build_qty: - buildable_qty = item.build_qty - - if count == stock_qty: - build = "Y" - else: - build = "N" - - row = frappe._dict( - { - "work_order": wo_details.name, - "status": wo_details.status, - "req_items": cint(count), - "instock": stock_qty, - "description": desc, - "source_warehouse": wo_item_details.source_warehouse, - "item_code": wo_item_details.item_code, - "bom_no": wo_details.bom_no, - "qty": wo_details.qty, - "buildable_qty": buildable_qty, - "ready_to_build": build, - } - ) - - out.append(row) + if wo_list: + bin = frappe.qb.DocType("Bin") + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType("BOM Item") + + # Add a row for each item/qty + for wo_details in wo_list: + desc = frappe.db.get_value("BOM", wo_details.bom_no, "description") + + for wo_item_details in frappe.db.get_values( + "Work Order Item", {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1 + ): + item_list = ( + frappe.qb.from_(bom) + .from_(bom_item) + .left_join(bin) + .on( + (bom_item.item_code == bin.item_code) + & (bin.warehouse == IfNull(wo_item_details.source_warehouse, filters.warehouse)) + ) + .select( + bom_item.item_code.as_("item_code"), + IfNull(bin.actual_qty * bom.quantity / bom_item.stock_qty, 0).as_("build_qty"), + ) + .where( + (bom.name == bom_item.parent) + & (bom_item.item_code == wo_item_details.item_code) + & (bom.name == wo_details.bom_no) + ) + .groupby(bom_item.item_code) + ).run(as_dict=1) + + stock_qty = 0 + count = 0 + buildable_qty = wo_details.qty + for item in item_list: + count = count + 1 + if item.build_qty >= (wo_details.qty - wo_details.produced_qty): + stock_qty = stock_qty + 1 + elif buildable_qty >= item.build_qty: + buildable_qty = item.build_qty + + if count == stock_qty: + build = "Y" + else: + build = "N" + + row = frappe._dict( + { + "work_order": wo_details.name, + "status": wo_details.status, + "req_items": cint(count), + "instock": stock_qty, + "description": desc, + "source_warehouse": wo_item_details.source_warehouse, + "item_code": wo_item_details.item_code, + "bom_no": wo_details.bom_no, + "qty": wo_details.qty, + "buildable_qty": buildable_qty, + "ready_to_build": build, + } + ) + + out.append(row) return out From ccd0449006183b43a617c1acc736730b7ac32bc6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 8 Sep 2022 18:53:30 +0530 Subject: [PATCH 132/898] fix: delete linked payment ledger entries no source doc deletion (cherry picked from commit 70313df53178772624e2fa3525b7caff82e911c0) --- erpnext/controllers/accounts_controller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6f321f476612..9244844e584d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -205,6 +205,10 @@ def before_cancel(self): def on_trash(self): # delete sl and gl entries on deletion of transaction if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): + ple = frappe.qb.DocType("Payment Ledger Entry") + frappe.qb.from_(ple).delete().where( + (ple.voucher_type == self.doctype) & (ple.voucher_no == self.name) + ).run() frappe.db.sql( "delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name) ) From a2285d5e5e84df8bc50c87aecd1fcadb11908c76 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 11 Sep 2022 11:52:17 +0530 Subject: [PATCH 133/898] fix: remove multiple call to ple creation (cherry picked from commit 4adc372f9a28cc69c7fe161584a5e339b7070ae5) --- erpnext/accounts/general_ledger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 16072f331f11..f4a50a5f915f 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -489,7 +489,6 @@ def make_reverse_gl_entries( ).run(as_dict=1) if gl_entries: - create_payment_ledger_entry(gl_entries, cancel=1) create_payment_ledger_entry( gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding ) From 86e5f362e367eb4e2d2f0f704b91cc9415cbf5c7 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sun, 11 Sep 2022 18:35:40 +0530 Subject: [PATCH 134/898] refactor: rewrite Process Loss Report queries in QB (cherry picked from commit 52459286482744410f5127a4f4bba6b0fb71f5ed) --- .../process_loss_report.py | 87 ++++++++----------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index b10e6434223b..ce8f4f35a3f2 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -5,6 +5,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Sum Filters = frappe._dict Row = frappe._dict @@ -14,15 +15,50 @@ def execute(filters: Filters) -> Tuple[Columns, Data]: + filters = frappe._dict(filters or {}) columns = get_columns() data = get_data(filters) return columns, data def get_data(filters: Filters) -> Data: - query_args = get_query_args(filters) - data = run_query(query_args) + wo = frappe.qb.DocType("Work Order") + se = frappe.qb.DocType("Stock Entry") + + query = ( + frappe.qb.from_(wo) + .inner_join(se) + .on(wo.name == se.work_order) + .select( + wo.name, + wo.status, + wo.production_item, + wo.qty, + wo.produced_qty, + wo.process_loss_qty, + (wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"), + Sum(se.total_incoming_value).as_("total_fg_value"), + Sum(se.total_outgoing_value).as_("total_rm_value"), + ) + .where( + (wo.process_loss_qty > 0) + & (wo.company == filters.company) + & (se.docstatus == 1) + & (se.posting_date.between(filters.from_date, filters.to_date)) + ) + .groupby(se.work_order) + ) + + if "item" in filters: + query.where(wo.production_item == filters.item) + + if "work_order" in filters: + query.where(wo.name == filters.work_order) + + data = query.run(as_dict=True) + update_data_with_total_pl_value(data) + return data @@ -67,54 +103,7 @@ def get_columns() -> Columns: ] -def get_query_args(filters: Filters) -> QueryArgs: - query_args = {} - query_args.update(filters) - query_args.update(get_filter_conditions(filters)) - return query_args - - -def run_query(query_args: QueryArgs) -> Data: - return frappe.db.sql( - """ - SELECT - wo.name, wo.status, wo.production_item, wo.qty, - wo.produced_qty, wo.process_loss_qty, - (wo.produced_qty - wo.process_loss_qty) as actual_produced_qty, - sum(se.total_incoming_value) as total_fg_value, - sum(se.total_outgoing_value) as total_rm_value - FROM - `tabWork Order` wo INNER JOIN `tabStock Entry` se - ON wo.name=se.work_order - WHERE - process_loss_qty > 0 - AND wo.company = %(company)s - AND se.docstatus = 1 - AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s - {item_filter} - {work_order_filter} - GROUP BY - se.work_order - """.format( - **query_args - ), - query_args, - as_dict=1, - ) - - def update_data_with_total_pl_value(data: Data) -> None: for row in data: value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"] row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg - - -def get_filter_conditions(filters: Filters) -> QueryArgs: - filter_conditions = dict(item_filter="", work_order_filter="") - if "item" in filters: - production_item = filters.get("item") - filter_conditions.update({"item_filter": f"AND wo.production_item='{production_item}'"}) - if "work_order" in filters: - work_order_name = filters.get("work_order") - filter_conditions.update({"work_order_filter": f"AND wo.name='{work_order_name}'"}) - return filter_conditions From c06e241fbc1b555300fcc2a76a79bc5250202956 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Sep 2022 12:40:57 +0530 Subject: [PATCH 135/898] fix: Rate for internal PI have non stock UOM items (cherry picked from commit 0f655e4430108ddb8d2bb8b2d35c26ab6fe376dc) # Conflicts: # erpnext/controllers/buying_controller.py --- erpnext/controllers/buying_controller.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index c0f37455a082..7baefd3775b1 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -308,7 +308,16 @@ def set_incoming_rate(self): rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) else: +<<<<<<< HEAD rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate") +======= + field = "incoming_rate" if self.get("is_internal_supplier") else "rate" + rate = flt( + frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field) + * (d.conversion_factor or 1), + d.precision("rate"), + ) +>>>>>>> 0f655e4430 (fix: Rate for internal PI have non stock UOM items) if self.is_internal_transfer(): if rate != d.rate: From 3083b4c218b770a7362532ea375c6ab6bc404fd2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 13:57:49 +0530 Subject: [PATCH 136/898] chore: correct license text for GPLv3 (backport #32170) (#32171) chore: correct license text for GPLv3 (#32170) [skip ci] (cherry picked from commit a30f38481de3df4350ca67e6a1529a5203b26bec) Co-authored-by: Ankush Menat --- README.md | 2 + license.txt | 749 ++++++++++++++++++++++++++-------------------------- 2 files changed, 376 insertions(+), 375 deletions(-) diff --git a/README.md b/README.md index cea3472447f9..0708266a4705 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,8 @@ GNU/General Public License (see [license.txt](license.txt)) The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors. +By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3). + ## Logo and Trademark Policy Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md). diff --git a/license.txt b/license.txt index a238a97b060d..f288702d2fa1 100644 --- a/license.txt +++ b/license.txt @@ -1,205 +1,200 @@ -### GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Version 3, 29 June 2007 + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Copyright (C) 2007 Free Software Foundation, Inc. - + Preamble -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -### Preamble - -The GNU General Public License is a free, copyleft license for + The GNU General Public License is a free, copyleft license for software and other kinds of works. -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom -to share and change all versions of a program--to make sure it remains -free software for all its users. We, the Free Software Foundation, use -the GNU General Public License for most of our software; it applies -also to any other work released this way by its authors. You can apply -it to your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you -have certain responsibilities if you distribute copies of the -software, or if you modify it: responsibilities to respect the freedom -of others. + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. -For example, if you distribute copies of such a program, whether + For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they know their rights. -Developers that use the GNU GPL protect your rights with two steps: + Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the -manufacturer can do so. This is fundamentally incompatible with the -aim of protecting users' freedom to change the software. The -systematic pattern of such abuse occurs in the area of products for -individuals to use, which is precisely where it is most unacceptable. -Therefore, we have designed this version of the GPL to prohibit the -practice for those products. If such problems arise substantially in -other domains, we stand ready to extend this provision to those -domains in future versions of the GPL, as needed to protect the -freedom of users. - -Finally, every program is threatened constantly by software patents. + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish -to avoid the special danger that patents applied to a free program -could make it effectively proprietary. To prevent this, the GPL -assures that patents cannot be used to render the program non-free. +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. -The precise terms and conditions for copying, distribution and + The precise terms and conditions for copying, distribution and modification follow. -### TERMS AND CONDITIONS + TERMS AND CONDITIONS -#### 0. Definitions. + 0. Definitions. -"This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU General Public License. -"Copyright" also means copyright-like laws that apply to other kinds -of works, such as semiconductor masks. + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of -an exact copy. The resulting work is called a "modified version" of -the earlier work or a work "based on" the earlier work. + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. -A "covered work" means either the unmodified Program or a work based + A "covered work" means either the unmodified Program or a work based on the Program. -To "propagate" a work means to do anything with it that, without + To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, +computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user -through a computer network, with no transfer of a copy, is not -conveying. + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. -An interactive user interface displays "Appropriate Legal Notices" to -the extent that it includes a convenient and prominently visible + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If +work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. -#### 1. Source Code. + 1. Source Code. -The "source code" for a work means the preferred form of the work for -making modifications to it. "Object code" means any non-source form of -a work. + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. -A "Standard Interface" means an interface that either is an official + A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. -The "System Libraries" of an executable work include anything, other + The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A +implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. -The "Corresponding Source" for a work in object code form means all + The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's +control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source +which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. -The Corresponding Source need not include anything that users can -regenerate automatically from other parts of the Corresponding Source. + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. -The Corresponding Source for a work in source code form is that same -work. + The Corresponding Source for a work in source code form is that +same work. -#### 2. Basic Permissions. + 2. Basic Permissions. -All rights granted under this License are granted for the term of + All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your +content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. -You may make, run and propagate covered works that you do not convey, -without conditions so long as your license otherwise remains in force. -You may convey covered works to others for the sole purpose of having -them make modifications exclusively for you, or provide you with -facilities for running those works, provided that you comply with the -terms of this License in conveying all material for which you do not -control copyright. Those thus making or running the covered works for -you must do so exclusively on your behalf, under your direction and -control, on terms that prohibit them from making any copies of your -copyrighted material outside their relationship with you. + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. -Conveying under any other circumstances is permitted solely under the -conditions stated below. Sublicensing is not allowed; section 10 makes -it unnecessary. + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. -#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. -No covered work shall be deemed part of an effective technological + No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such -circumvention is effected by exercising rights under this License with -respect to the covered work, and you disclaim any intention to limit -operation or modification of the work as a means of enforcing, against -the work's users, your or third parties' legal rights to forbid -circumvention of technological measures. + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. -#### 4. Conveying Verbatim Copies. + 4. Conveying Verbatim Copies. -You may convey verbatim copies of the Program's source code as you + You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any @@ -207,56 +202,59 @@ non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. -You may charge any price or no price for each copy that you convey, + You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. -#### 5. Conveying Modified Source Versions. + 5. Conveying Modified Source Versions. -You may convey a work based on the Program, or the modifications to + You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these -conditions: +terms of section 4, provided that you also meet all of these conditions: -- a) The work must carry prominent notices stating that you modified + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. -- b) The work must carry prominent notices stating that it is - released under this License and any conditions added under - section 7. This requirement modifies the requirement in section 4 - to "keep intact all notices". -- c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no + regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. -- d) If the work has interactive user interfaces, each must display + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. -A compilation of a covered work with other separate and independent + A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work +beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. -#### 6. Conveying Non-Source Forms. + 6. Conveying Non-Source Forms. -You may convey a covered work in object code form under the terms of -sections 4 and 5, provided that you also convey the machine-readable -Corresponding Source under the terms of this License, in one of these -ways: + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: -- a) Convey the object code in, or embodied in, a physical product + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. -- b) Convey the object code in, or embodied in, a physical product + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product @@ -265,190 +263,196 @@ ways: product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the Corresponding - Source from a network server at no charge. -- c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. -- d) Convey the object code by offering access from a designated + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the + Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. -- e) Convey the object code using peer-to-peer transmission, - provided you inform other peers where the object code and - Corresponding Source of the work are being offered to the general - public at no charge under subsection 6d. -A separable portion of the object code, whose source code is excluded + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, -family, or household purposes, or (2) anything designed or sold for -incorporation into a dwelling. In determining whether a product is a -consumer product, doubtful cases shall be resolved in favor of -coverage. For a particular product received by a particular user, -"normally used" refers to a typical or common use of that class of -product, regardless of the status of the particular user or of the way -in which the particular user actually uses, or expects or is expected -to use, the product. A product is a consumer product regardless of -whether the product has substantial commercial, industrial or -non-consumer uses, unless such uses represent the only significant -mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to -install and execute modified versions of a covered work in that User -Product from a modified version of its Corresponding Source. The -information must suffice to ensure that the continued functioning of -the modified object code is in no case prevented or interfered with -solely because modification has been made. - -If you convey an object code work under this section in, or with, or + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply +by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or -updates for a work that has been modified or installed by the -recipient, or for the User Product in which it has been modified or -installed. Access to a network may be denied when the modification -itself materially and adversely affects the operation of the network -or violates the rules and protocols for communication across the -network. + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. -Corresponding Source conveyed, and Installation Information provided, + Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. -#### 7. Additional Terms. + 7. Additional Terms. -"Additional permissions" are terms that supplement the terms of this + "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions +that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. -When you convey a copy of a covered work, you may at your option + When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders -of that material) supplement the terms of this License with terms: + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: -- a) Disclaiming warranty or limiting liability differently from the + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or -- b) Requiring preservation of specified reasonable legal notices or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or -- c) Prohibiting misrepresentation of the origin of that material, - or requiring that modified versions of such material be marked in + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in reasonable ways as different from the original version; or -- d) Limiting the use for publicity purposes of names of licensors - or authors of the material; or -- e) Declining to grant rights under trademark law for use of some + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or -- f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions - of it) with contractual assumptions of liability to the recipient, - for any liability that these contractual assumptions directly - impose on those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains +restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. -If you add terms to a covered work in accord with this section, you + If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; the -above requirements apply either way. + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. -#### 8. Termination. + 8. Termination. -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). -However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally -terminates your license, and (b) permanently, if the copyright holder -fails to notify you of the violation by some reasonable means prior to -60 days after the cessation. + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. -Moreover, your license from a particular copyright holder is + Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. -Termination of your rights under this section does not terminate the + Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently +this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. -#### 9. Acceptance Not Required for Having Copies. + 9. Acceptance Not Required for Having Copies. -You are not required to accept this License in order to receive or run -a copy of the Program. Ancillary propagation of a covered work + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, +to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. -#### 10. Automatic Licensing of Downstream Recipients. + 10. Automatic Licensing of Downstream Recipients. -Each time you convey a covered work, the recipient automatically + Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible +propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. -An "entity transaction" is a transaction transferring control of an + An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered +organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could @@ -456,43 +460,43 @@ give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. -#### 11. Patents. + 11. Patents. -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". -A contributor's "essential patent claims" are all patent claims owned -or controlled by the contributor, whether already acquired or + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For +consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. -Each contributor grants you a non-exclusive, worldwide, royalty-free + Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. -In the following three paragraphs, a "patent license" is any express + In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a +sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. -If you convey a covered work, knowingly relying on a patent license, + If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, @@ -500,13 +504,13 @@ then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. -If, pursuant to or in connection with a single transaction or + If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify @@ -514,162 +518,157 @@ or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. -A patent license is "discriminatory" if it does not include within the -scope of its coverage, prohibits the exercise of, or is conditioned on -the non-exercise of one or more of the rights that are specifically -granted under this License. You may not convey a covered work if you -are a party to an arrangement with a third party that is in the -business of distributing software, under which you make payment to the -third party based on the extent of your activity of conveying the -work, and under which the third party grants, to any of the parties -who would receive the covered work from you, a discriminatory patent -license (a) in connection with copies of the covered work conveyed by -you (or copies made from those copies), or (b) primarily for and in -connection with specific products or compilations that contain the -covered work, unless you entered into that arrangement, or that patent -license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. -#### 12. No Surrender of Others' Freedom. + 12. No Surrender of Others' Freedom. -If conditions are imposed on you (whether by court order, agreement or + If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under -this License and any other pertinent obligations, then as a -consequence you may not convey it at all. For example, if you agree to -terms that obligate you to collect a royalty for further conveying -from those to whom you convey the Program, the only way you could -satisfy both those terms and this License would be to refrain entirely -from conveying the Program. - -#### 13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this +combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. -#### 14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions -of the GNU General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in -detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies that a certain numbered version of the GNU General Public -License "or any later version" applies to it, you have the option of -following the terms and conditions either of that numbered version or -of any later version published by the Free Software Foundation. If the -Program does not specify a version number of the GNU General Public -License, you may choose any version ever published by the Free -Software Foundation. - -If the Program specifies that a proxy can decide which future versions -of the GNU General Public License can be used, that proxy's public -statement of acceptance of a version permanently authorizes you to -choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. -#### 15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT -WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND -PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE -DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR -CORRECTION. - -#### 16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR -CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT -NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR -LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM -TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER -PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -#### 17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -### How to Apply These Terms to Your New Programs + How to Apply These Terms to Your New Programs -If you develop a new program, and you want it to be of the greatest + If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these -terms. +free software which everyone can redistribute and change under these terms. -To do so, attach the following notices to the program. It is safest to -attach them to the start of each source file to most effectively state -the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + + Copyright (C) - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU General Public License + along with this program. If not, see . -Also add information on how to contact you by electronic and paper -mail. +Also add information on how to contact you by electronic and paper mail. -If the program does terminal interaction, make it output a short + If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands \`show w' and \`show c' should show the -appropriate parts of the General Public License. Of course, your -program's commands might be different; for a GUI interface, you would -use an "about box". - -You should also get your employer (if you work as a programmer) or -institute, if any, to sign a "copyright disclaimer" for the program, if -necessary. For more information on this, and how to apply and follow -the GNU GPL, see . - -The GNU General Public License does not permit incorporating your -program into proprietary programs. If your program is a subroutine -library, you may consider it more useful to permit linking proprietary -applications with the library. If this is what you want to do, use the -GNU Lesser General Public License instead of this License. But first, -please read . \ No newline at end of file + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 174c7503c97b5ddf0ed264fe540d2fab28853074 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" Date: Mon, 12 Sep 2022 14:09:42 +0530 Subject: [PATCH 137/898] fix: Set filter condition and spell in AR (cherry picked from commit e2b4ae13fa557962ebb15f201a0ff56084219229) --- .../report/accounts_receivable/accounts_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 3f504b13f673..8557c03bd5e5 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -784,7 +784,7 @@ def add_common_filters(self, party_type_field): def add_customer_filters( self, ): - self.customter = qb.DocType("Customer") + self.customer = qb.DocType("Customer") if self.filters.get("customer_group"): self.get_hierarchical_filters("Customer Group", "customer_group") @@ -838,7 +838,7 @@ def get_hierarchical_filters(self, doctype, key): customer = self.customer groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt)) customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups)) - self.qb_selection_filter.append(ple.isin(ple.party.isin(customers))) + self.qb_selection_filter.append(ple.party.isin(customers)) def add_accounting_dimensions_filters(self): accounting_dimensions = get_accounting_dimensions(as_list=False) From 93e510023a9498e744c37daf6f2e45fb356b116c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 12 Sep 2022 16:48:53 +0530 Subject: [PATCH 138/898] fix: customer code max characters limit issue (#32177) (cherry picked from commit 8f787c08a22b3e1117fe46491562d6c602b9e01d) --- erpnext/stock/doctype/item/item.json | 4 ++-- erpnext/stock/doctype/item/test_item.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 76cb31dc42e0..247140b657eb 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -795,7 +795,7 @@ }, { "fieldname": "customer_code", - "fieldtype": "Data", + "fieldtype": "Small Text", "hidden": 1, "label": "Customer Code", "no_copy": 1, @@ -910,7 +910,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2022-06-15 09:02:06.177691", + "modified": "2022-09-12 15:00:10.130340", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 3366c737cbd9..89da72195fc3 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -778,6 +778,14 @@ def test_item_type_field_change(self): item.has_batch_no = 1 item.save() + def test_customer_codes_length(self): + """Check if item code with special characters are allowed.""" + item = make_item(properties={"item_code": "Test Item Code With Special Characters"}) + for row in range(3): + item.append("customer_items", {"ref_code": frappe.generate_hash("", 120)}) + item.save() + self.assertTrue(len(item.customer_code) > 140) + def set_item_variant_settings(fields): doc = frappe.get_doc("Item Variant Settings") From 702c16eac4ebdc16f7c7cbae1b82263cbb41a62c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 12 Sep 2022 16:53:52 +0530 Subject: [PATCH 139/898] fix: promotional scheme min and max amount configuration (cherry picked from commit a8fd92ddc1202830cc38e73f4e2fe5a670e63f49) --- .../promotional_scheme/promotional_scheme.py | 10 +++++++--- .../test_promotional_scheme.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index fac9be7bdb13..4d28d1066045 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -34,8 +34,8 @@ other_fields = [ "min_qty", "max_qty", - "min_amt", - "max_amt", + "min_amount", + "max_amount", "priority", "warehouse", "threshold_percentage", @@ -246,7 +246,11 @@ def prepare_pricing_rule( def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields): pr.update(args) for field in other_fields + discount_fields: - pr.set(field, child_doc_fields.get(field)) + target_field = field + if target_field in ["min_amount", "max_amount"]: + target_field = "min_amt" if field == "min_amount" else "max_amt" + + pr.set(target_field, child_doc_fields.get(field)) pr.promotional_scheme_id = child_doc_fields.name pr.promotional_scheme = doc.name diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index b3b9d7b20865..9e576fb8775f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -90,6 +90,23 @@ def test_change_applicable_for_in_promotional_scheme(self): price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name}) self.assertEqual(price_rules, []) + def test_min_max_amount_configuration(self): + ps = make_promotional_scheme() + ps.price_discount_slabs[0].min_amount = 10 + ps.price_discount_slabs[0].max_amount = 1000 + ps.save() + + price_rules_data = frappe.db.get_value( + "Pricing Rule", {"promotional_scheme": ps.name}, ["min_amt", "max_amt"], as_dict=1 + ) + + self.assertEqual(price_rules_data.min_amt, 10) + self.assertEqual(price_rules_data.max_amt, 1000) + + frappe.delete_doc("Promotional Scheme", ps.name) + price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name}) + self.assertEqual(price_rules, []) + def make_promotional_scheme(**args): args = frappe._dict(args) From 08f5f81fa86356df5c35361e87645190ba051c62 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 9 Sep 2022 18:31:12 +0530 Subject: [PATCH 140/898] refactor: BOM Stock Calculated report (cherry picked from commit 723fa9eebc604075c1f8f7bf12f243590b787fe0) --- .../bom_stock_calculated.py | 174 +++++++++++------- 1 file changed, 110 insertions(+), 64 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index 933be3e01404..48fbf1cf03d0 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -4,29 +4,31 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Floor, IfNull, Sum from frappe.utils.data import comma_and +from pypika.terms import ExistsCriterion def execute(filters=None): - # if not filters: filters = {} columns = get_columns() - summ_data = [] + data = [] - data = get_bom_stock(filters) + bom_data = get_bom_data(filters) qty_to_make = filters.get("qty_to_make") - manufacture_details = get_manufacturer_records() - for row in data: - reqd_qty = qty_to_make * row.actual_qty - last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") - summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details)) - return columns, summ_data + for row in bom_data: + required_qty = qty_to_make * row.actual_qty + last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") + + data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details)) + return columns, data -def get_report_data(last_pur_price, reqd_qty, row, manufacture_details): + +def get_report_data(last_purchase_rate, required_qty, row, manufacture_details): to_build = row.to_build if row.to_build > 0 else 0 - diff_qty = to_build - reqd_qty + difference_qty = to_build - required_qty return [ row.item_code, row.description, @@ -36,83 +38,127 @@ def get_report_data(last_pur_price, reqd_qty, row, manufacture_details): ), row.actual_qty, str(to_build), - reqd_qty, - diff_qty, - last_pur_price, + required_qty, + difference_qty, + last_purchase_rate, ] def get_columns(): - """return columns""" - columns = [ - _("Item") + ":Link/Item:100", - _("Description") + "::150", - _("Manufacturer") + "::250", - _("Manufacturer Part Number") + "::250", - _("Qty") + ":Float:50", - _("Stock Qty") + ":Float:100", - _("Reqd Qty") + ":Float:100", - _("Diff Qty") + ":Float:100", - _("Last Purchase Price") + ":Float:100", + return [ + { + "fieldname": "item", + "label": _("Item"), + "fieldtype": "Link", + "options": "Item", + "width": 120, + }, + { + "fieldname": "description", + "label": _("Description"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "manufacturer", + "label": _("Manufacturer"), + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "manufacturer_part_number", + "label": _("Manufacturer Part Number"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "available_qty", + "label": _("Available Qty"), + "fieldtype": "Float", + "width": 120, + }, + { + "fieldname": "qty_per_unit", + "label": _("Qty Per Unit"), + "fieldtype": "Float", + "width": 110, + }, + { + "fieldname": "required_qty", + "label": _("Required Qty"), + "fieldtype": "Float", + "width": 120, + }, + { + "fieldname": "difference_qty", + "label": _("Difference Qty"), + "fieldtype": "Float", + "width": 130, + }, + { + "fieldname": "last_purchase_rate", + "label": _("Last Purchase Rate"), + "fieldtype": "Float", + "width": 160, + }, ] - return columns -def get_bom_stock(filters): - conditions = "" - bom = filters.get("bom") - - table = "`tabBOM Item`" - qty_field = "qty" - +def get_bom_data(filters): if filters.get("show_exploded_view"): - table = "`tabBOM Explosion Item`" qty_field = "stock_qty" + bom_item_table = "BOM Explosion Item" + else: + qty_field = "qty" + bom_item_table = "BOM Item" + + bom_item = frappe.qb.DocType(bom_item_table) + bin = frappe.qb.DocType("Bin") + + query = ( + frappe.qb.from_(bom_item) + .left_join(bin) + .on(bom_item.item_code == bin.item_code) + .select( + bom_item.item_code, + bom_item.description, + bom_item[qty_field], + IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"), + IfNull(Sum(Floor(bin.actual_qty / bom_item[qty_field])), 0).as_("to_build"), + ) + .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) + .groupby(bom_item.item_code) + ) if filters.get("warehouse"): warehouse_details = frappe.db.get_value( "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 ) + if warehouse_details: - conditions += ( - " and exists (select name from `tabWarehouse` wh \ - where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" - % (warehouse_details.lft, warehouse_details.rgt) + wh = frappe.qb.DocType("Warehouse") + query = query.where( + ExistsCriterion( + frappe.qb.from_(wh) + .select(wh.name) + .where( + (wh.lft >= warehouse_details.lft) + & (wh.rgt <= warehouse_details.rgt) + & (bin.warehouse == wh.name) + ) + ) ) else: - conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse")) + query = query.where(bin.warehouse == frappe.db.escape(filters.get("warehouse"))) - else: - conditions += "" - - return frappe.db.sql( - """ - SELECT - bom_item.item_code, - bom_item.description, - bom_item.{qty_field}, - ifnull(sum(ledger.actual_qty), 0) as actual_qty, - ifnull(sum(FLOOR(ledger.actual_qty / bom_item.{qty_field})), 0) as to_build - FROM - {table} AS bom_item - LEFT JOIN `tabBin` AS ledger - ON bom_item.item_code = ledger.item_code - {conditions} - - WHERE - bom_item.parent = '{bom}' and bom_item.parenttype='BOM' - - GROUP BY bom_item.item_code""".format( - qty_field=qty_field, table=table, conditions=conditions, bom=bom - ), - as_dict=1, - ) + return query.run(as_dict=True) def get_manufacturer_records(): details = frappe.get_all( "Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"] ) + manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get("item_code"), {}) From af883be065a9362f5a12cea8d41f818ac1e9dc8d Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 9 Sep 2022 18:57:49 +0530 Subject: [PATCH 141/898] fix: required_qty in BOM Stock Calculated report (cherry picked from commit 56192daabfd374765c2703cb9c50ddf6e24f2c13) --- .../bom_stock_calculated.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index 48fbf1cf03d0..ec4b25c859ff 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.query_builder.functions import Floor, IfNull, Sum +from frappe.query_builder.functions import IfNull, Sum from frappe.utils.data import comma_and from pypika.terms import ExistsCriterion @@ -18,7 +18,7 @@ def execute(filters=None): manufacture_details = get_manufacturer_records() for row in bom_data: - required_qty = qty_to_make * row.actual_qty + required_qty = qty_to_make * row.qty_per_unit last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate") data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details)) @@ -27,8 +27,8 @@ def execute(filters=None): def get_report_data(last_purchase_rate, required_qty, row, manufacture_details): - to_build = row.to_build if row.to_build > 0 else 0 - difference_qty = to_build - required_qty + qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0 + difference_qty = row.actual_qty - required_qty return [ row.item_code, row.description, @@ -36,8 +36,8 @@ def get_report_data(last_purchase_rate, required_qty, row, manufacture_details): comma_and( manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False ), + qty_per_unit, row.actual_qty, - str(to_build), required_qty, difference_qty, last_purchase_rate, @@ -71,18 +71,18 @@ def get_columns(): "fieldtype": "Data", "width": 150, }, - { - "fieldname": "available_qty", - "label": _("Available Qty"), - "fieldtype": "Float", - "width": 120, - }, { "fieldname": "qty_per_unit", "label": _("Qty Per Unit"), "fieldtype": "Float", "width": 110, }, + { + "fieldname": "available_qty", + "label": _("Available Qty"), + "fieldtype": "Float", + "width": 120, + }, { "fieldname": "required_qty", "label": _("Required Qty"), @@ -106,10 +106,8 @@ def get_columns(): def get_bom_data(filters): if filters.get("show_exploded_view"): - qty_field = "stock_qty" bom_item_table = "BOM Explosion Item" else: - qty_field = "qty" bom_item_table = "BOM Item" bom_item = frappe.qb.DocType(bom_item_table) @@ -122,9 +120,8 @@ def get_bom_data(filters): .select( bom_item.item_code, bom_item.description, - bom_item[qty_field], + bom_item.qty_consumed_per_unit.as_("qty_per_unit"), IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"), - IfNull(Sum(Floor(bin.actual_qty / bom_item[qty_field])), 0).as_("to_build"), ) .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) .groupby(bom_item.item_code) From 4e09203ddcf6ad344a2dbc6632a207aeaf5071c9 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 9 Sep 2022 19:08:13 +0530 Subject: [PATCH 142/898] fix: add missing warehouse filter in BOM Stock Calculated report (cherry picked from commit 7a968a5f0dc23dc6e815ef590a70794a75a1cd15) --- .../bom_stock_calculated.js | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js index 0d5bfcbaf402..a0fd91e866f2 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.js @@ -11,17 +11,24 @@ frappe.query_reports["BOM Stock Calculated"] = { "options": "BOM", "reqd": 1 }, - { - "fieldname": "qty_to_make", - "label": __("Quantity to Make"), - "fieldtype": "Int", - "default": "1" - }, - - { + { + "fieldname": "warehouse", + "label": __("Warehouse"), + "fieldtype": "Link", + "options": "Warehouse", + }, + { + "fieldname": "qty_to_make", + "label": __("Quantity to Make"), + "fieldtype": "Float", + "default": "1.0", + "reqd": 1 + }, + { "fieldname": "show_exploded_view", "label": __("Show exploded view"), - "fieldtype": "Check" + "fieldtype": "Check", + "default": false, } ] } From 581c5cbc389bab32987937e672f96443cdfc7388 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 12 Sep 2022 15:50:30 +0530 Subject: [PATCH 143/898] test: add test cases for BOM Stock Calculated report (cherry picked from commit e1a98c1ff7f130afe1eb8adc1ba86dfd84778db3) --- .../test_bom_stock_calculated.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py diff --git a/erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py new file mode 100644 index 000000000000..8ad980fa19af --- /dev/null +++ b/erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py @@ -0,0 +1,115 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from frappe.tests.utils import FrappeTestCase + +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.manufacturing.report.bom_stock_calculated.bom_stock_calculated import ( + execute as bom_stock_calculated_report, +) +from erpnext.stock.doctype.item.test_item import make_item + + +class TestBOMStockCalculated(FrappeTestCase): + def setUp(self): + self.fg_item, self.rm_items = create_items() + self.boms = create_boms(self.fg_item, self.rm_items) + + def test_bom_stock_calculated(self): + qty_to_make = 10 + + # Case 1: When Item(s) Qty and Stock Qty are equal. + data = bom_stock_calculated_report( + filters={ + "qty_to_make": qty_to_make, + "bom": self.boms[0].name, + } + )[1] + expected_data = get_expected_data(self.boms[0], qty_to_make) + self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + # Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1. + data = bom_stock_calculated_report( + filters={ + "qty_to_make": qty_to_make, + "bom": self.boms[1].name, + } + )[1] + expected_data = get_expected_data(self.boms[1], qty_to_make) + self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + # Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1. + data = bom_stock_calculated_report( + filters={ + "qty_to_make": qty_to_make, + "bom": self.boms[2].name, + } + )[1] + expected_data = get_expected_data(self.boms[2], qty_to_make) + self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data)) + + +def create_items(): + fg_item = make_item(properties={"is_stock_item": 1}).name + rm_item1 = make_item( + properties={ + "is_stock_item": 1, + "standard_rate": 100, + "opening_stock": 100, + "last_purchase_rate": 100, + } + ).name + rm_item2 = make_item( + properties={ + "is_stock_item": 1, + "standard_rate": 200, + "opening_stock": 200, + "last_purchase_rate": 200, + } + ).name + + return fg_item, [rm_item1, rm_item2] + + +def create_boms(fg_item, rm_items): + def update_bom_items(bom, uom, conversion_factor): + for item in bom.items: + item.uom = uom + item.conversion_factor = conversion_factor + + return bom + + bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10) + + bom2 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10, do_not_submit=True) + bom2 = update_bom_items(bom2, "Box", 10) + bom2.save() + bom2.submit() + + bom3 = make_bom(item=fg_item, quantity=2, raw_materials=rm_items, rm_qty=10, do_not_submit=True) + bom3 = update_bom_items(bom3, "Box", 10) + bom3.save() + bom3.submit() + + return [bom1, bom2, bom3] + + +def get_expected_data(bom, qty_to_make): + expected_data = [] + + for idx in range(len(bom.items)): + expected_data.append( + [ + bom.items[idx].item_code, + bom.items[idx].item_code, + "", + "", + float(bom.items[idx].stock_qty / bom.quantity), + float(100 * (idx + 1)), + float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)), + float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))), + float(100 * (idx + 1)), + ] + ) + + return expected_data From 3c3ab897d58a169ef2b1564b759cba9ed51ffc48 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 12 Sep 2022 14:23:44 +0500 Subject: [PATCH 144/898] fix: pick_list - picked qty getting set to 1 (cherry picked from commit 3256e2b8b7cee0e7b95ca58a6f22f96090e2ffaa) --- erpnext/stock/doctype/pick_list/pick_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index d31d695c803b..adddb4138245 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -183,7 +183,7 @@ def aggregate_item_qty(self): frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx)) item_code = item.item_code reference = item.sales_order_item or item.material_request_item - key = (item_code, item.uom, reference) + key = (item_code, item.uom, item.warehouse, reference) item.idx = None item.name = None From 944b8a0ed79d8081632eca2ebb4877e2a7cb3817 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 09:50:36 +0530 Subject: [PATCH 145/898] fix: remove EmployeeBoardingController (backport #32139) (#32191) Co-authored-by: Rucha Mahabal Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../employee_boarding_controller.py | 193 ------------------ erpnext/projects/doctype/project/project.py | 3 - 2 files changed, 196 deletions(-) delete mode 100644 erpnext/controllers/employee_boarding_controller.py diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py deleted file mode 100644 index c06fb5930dcb..000000000000 --- a/erpnext/controllers/employee_boarding_controller.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe import _ -from frappe.desk.form import assign_to -from frappe.model.document import Document -from frappe.utils import add_days, flt, unique - -from erpnext.setup.doctype.employee.employee import get_holiday_list_for_employee -from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday - - -class EmployeeBoardingController(Document): - """ - Create the project and the task for the boarding process - Assign to the concerned person and roles as per the onboarding/separation template - """ - - def validate(self): - # remove the task if linked before submitting the form - if self.amended_from: - for activity in self.activities: - activity.task = "" - - def on_submit(self): - # create the project for the given employee onboarding - project_name = _(self.doctype) + " : " - if self.doctype == "Employee Onboarding": - project_name += self.job_applicant - else: - project_name += self.employee - - project = frappe.get_doc( - { - "doctype": "Project", - "project_name": project_name, - "expected_start_date": self.date_of_joining - if self.doctype == "Employee Onboarding" - else self.resignation_letter_date, - "department": self.department, - "company": self.company, - } - ).insert(ignore_permissions=True, ignore_mandatory=True) - - self.db_set("project", project.name) - self.db_set("boarding_status", "Pending") - self.reload() - self.create_task_and_notify_user() - - def create_task_and_notify_user(self): - # create the task for the given project and assign to the concerned person - holiday_list = self.get_holiday_list() - - for activity in self.activities: - if activity.task: - continue - - dates = self.get_task_dates(activity, holiday_list) - - task = frappe.get_doc( - { - "doctype": "Task", - "project": self.project, - "subject": activity.activity_name + " : " + self.employee_name, - "description": activity.description, - "department": self.department, - "company": self.company, - "task_weight": activity.task_weight, - "exp_start_date": dates[0], - "exp_end_date": dates[1], - } - ).insert(ignore_permissions=True) - activity.db_set("task", task.name) - - users = [activity.user] if activity.user else [] - if activity.role: - user_list = frappe.db.sql_list( - """ - SELECT - DISTINCT(has_role.parent) - FROM - `tabHas Role` has_role - LEFT JOIN `tabUser` user - ON has_role.parent = user.name - WHERE - has_role.parenttype = 'User' - AND user.enabled = 1 - AND has_role.role = %s - """, - activity.role, - ) - users = unique(users + user_list) - - if "Administrator" in users: - users.remove("Administrator") - - # assign the task the users - if users: - self.assign_task_to_users(task, users) - - def get_holiday_list(self): - if self.doctype == "Employee Separation": - return get_holiday_list_for_employee(self.employee) - else: - if self.employee: - return get_holiday_list_for_employee(self.employee) - else: - if not self.holiday_list: - frappe.throw(_("Please set the Holiday List."), frappe.MandatoryError) - else: - return self.holiday_list - - def get_task_dates(self, activity, holiday_list): - start_date = end_date = None - - if activity.begin_on is not None: - start_date = add_days(self.boarding_begins_on, activity.begin_on) - start_date = self.update_if_holiday(start_date, holiday_list) - - if activity.duration is not None: - end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration) - end_date = self.update_if_holiday(end_date, holiday_list) - - return [start_date, end_date] - - def update_if_holiday(self, date, holiday_list): - while is_holiday(holiday_list, date): - date = add_days(date, 1) - return date - - def assign_task_to_users(self, task, users): - for user in users: - args = { - "assign_to": [user], - "doctype": task.doctype, - "name": task.name, - "description": task.description or task.subject, - "notify": self.notify_users_by_email, - } - assign_to.add(args) - - def on_cancel(self): - # delete task project - project = self.project - for task in frappe.get_all("Task", filters={"project": project}): - frappe.delete_doc("Task", task.name, force=1) - frappe.delete_doc("Project", project, force=1) - self.db_set("project", "") - for activity in self.activities: - activity.db_set("task", "") - - frappe.msgprint( - _("Linked Project {} and Tasks deleted.").format(project), alert=True, indicator="blue" - ) - - -@frappe.whitelist() -def get_onboarding_details(parent, parenttype): - return frappe.get_all( - "Employee Boarding Activity", - fields=[ - "activity_name", - "role", - "user", - "required_for_employee_creation", - "description", - "task_weight", - "begin_on", - "duration", - ], - filters={"parent": parent, "parenttype": parenttype}, - order_by="idx", - ) - - -def update_employee_boarding_status(project): - employee_onboarding = frappe.db.exists("Employee Onboarding", {"project": project.name}) - employee_separation = frappe.db.exists("Employee Separation", {"project": project.name}) - - if not (employee_onboarding or employee_separation): - return - - status = "Pending" - if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0: - status = "In Process" - elif flt(project.percent_complete) == 100.0: - status = "Completed" - - if employee_onboarding: - frappe.db.set_value("Employee Onboarding", employee_onboarding, "boarding_status", status) - elif employee_separation: - frappe.db.set_value("Employee Separation", employee_separation, "boarding_status", status) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index a2be9367688b..d80133c988a3 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -10,7 +10,6 @@ from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today from erpnext import get_default_company -from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status from erpnext.controllers.queries import get_filters_cond from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday @@ -43,7 +42,6 @@ def validate(self): self.send_welcome_email() self.update_costing() self.update_percent_complete() - update_employee_boarding_status(self) def copy_from_template(self): """ @@ -145,7 +143,6 @@ def is_row_updated(self, row, existing_task_data, fields): def update_project(self): """Called externally by Task""" self.update_percent_complete() - update_employee_boarding_status(self) self.update_costing() self.db_update() From 027e66f637dcaf5dcddb797b139632f3f363ca1b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 10:36:12 +0530 Subject: [PATCH 146/898] fix(Employee): shorter tab titles (backport #32192) (#32193) Co-authored-by: Rucha Mahabal --- erpnext/setup/doctype/employee/employee.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index 39e0acd02aa0..99693d909189 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -396,7 +396,7 @@ "collapsible": 1, "fieldname": "salary_information", "fieldtype": "Tab Break", - "label": "Salary Details", + "label": "Salary", "oldfieldtype": "Section Break", "width": "50%" }, @@ -428,7 +428,7 @@ "collapsible": 1, "fieldname": "contact_details", "fieldtype": "Tab Break", - "label": "Contact" + "label": "Address & Contacts" }, { "fieldname": "cell_number", @@ -507,7 +507,7 @@ "collapsible": 1, "fieldname": "personal_details", "fieldtype": "Tab Break", - "label": "Personal Details" + "label": "Personal" }, { "fieldname": "passport_number", @@ -701,7 +701,7 @@ "collapsible": 1, "fieldname": "attendance_and_leave_details", "fieldtype": "Tab Break", - "label": "Attendance and Leave Details" + "label": "Attendance & Leaves" }, { "fieldname": "column_break_44", @@ -726,7 +726,7 @@ { "fieldname": "basic_details_tab", "fieldtype": "Tab Break", - "label": "Basic Details" + "label": "Overview" }, { "fieldname": "company_details_section", @@ -810,7 +810,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2022-08-23 13:47:46.944993", + "modified": "2022-09-13 10:27:14.579197", "modified_by": "Administrator", "module": "Setup", "name": "Employee", From 28b1d4e2a31daafaa7192bba8a0e9ad0159c57bc Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 5 Sep 2022 15:32:48 +0530 Subject: [PATCH 147/898] fix: Migrate old lead notes as per the new format (cherry picked from commit 3abd00f3bb2b26beaeeea7bee03775f7368ceb08) --- erpnext/patches.txt | 1 + ...isting_lead_notes_as_per_the_new_format.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d780213209c8..2a0ca8c49619 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,6 +307,7 @@ erpnext.patches.v13_0.job_card_status_on_hold erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.crm_ux_cleanup +erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format erpnext.patches.v14_0.remove_india_localisation # 14-07-2022 erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py new file mode 100644 index 000000000000..e97651e55754 --- /dev/null +++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py @@ -0,0 +1,21 @@ +import frappe +from frappe.utils import cstr, strip_html + + +def execute(): + for doctype in ("Lead", "Prospect"): + if not frappe.db.has_column(doctype, "notes"): + continue + + dt = frappe.qb.DocType(doctype) + records = ( + frappe.qb.from_(dt) + .select(dt.name, dt.notes, dt.modified_by, dt.modified) + .where(dt.notes.isnotnull() & dt.notes != "") + ).run() + + for d in records: + if strip_html(cstr(d.notes)).strip(): + doc = frappe.get_doc(doctype, d.name) + doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified}) + doc.save() From 7bb5f6eb2365c114573705753d93358221c2a8d2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 5 Sep 2022 18:16:34 +0530 Subject: [PATCH 148/898] perf: lesser SQL queries and no validation Co-authored-by: Sagar Vora (cherry picked from commit 2a100abef19d4db9566b7b760b1c029e1609f3e1) --- .../v14_0/migrate_existing_lead_notes_as_per_the_new_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py index e97651e55754..6ba5a9ff2143 100644 --- a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py +++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py @@ -18,4 +18,4 @@ def execute(): if strip_html(cstr(d.notes)).strip(): doc = frappe.get_doc(doctype, d.name) doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified}) - doc.save() + doc.update_child_table("notes") From ded6e7f5449d785ee0d862ac61da00b58b5259f7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 5 Sep 2022 18:27:03 +0530 Subject: [PATCH 149/898] fix: drop old notes column from lead and prospect (cherry picked from commit 4b1345202210680e4bcb7bc4f299cc0db3829543) --- .../migrate_existing_lead_notes_as_per_the_new_format.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py index 6ba5a9ff2143..032aeccc23d7 100644 --- a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py +++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py @@ -3,7 +3,7 @@ def execute(): - for doctype in ("Lead", "Prospect"): + for doctype in ("Lead", "Prospect", "Opportunity"): if not frappe.db.has_column(doctype, "notes"): continue @@ -19,3 +19,5 @@ def execute(): doc = frappe.get_doc(doctype, d.name) doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified}) doc.update_child_table("notes") + + frappe.db.sql_ddl(f"alter table `tab{doctype}` drop column `notes`") From dda057d2577d1140cb9f704dfe6571e6056413fb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Sep 2022 13:40:36 +0530 Subject: [PATCH 150/898] chore: Resolve conflicts --- erpnext/controllers/buying_controller.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 7baefd3775b1..584266d53b18 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -308,16 +308,12 @@ def set_incoming_rate(self): rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) else: -<<<<<<< HEAD - rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate") -======= field = "incoming_rate" if self.get("is_internal_supplier") else "rate" rate = flt( frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field) * (d.conversion_factor or 1), d.precision("rate"), ) ->>>>>>> 0f655e4430 (fix: Rate for internal PI have non stock UOM items) if self.is_internal_transfer(): if rate != d.rate: From 2c4867cc981f02095d2f18a01f3f4c7130b8fb51 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 13:41:58 +0530 Subject: [PATCH 151/898] fix(UX): make Item attachments public by default (backport #32196) (#32197) fix(UX): make Item attachments public by default (#32196) (cherry picked from commit fffc245922530d51d7af12ddce142a1f32757cb1) Co-authored-by: Ankush Menat --- erpnext/e_commerce/doctype/website_item/website_item.json | 3 ++- erpnext/stock/doctype/item/item.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index a416aac3a17b..c5775ee9075a 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -345,7 +345,8 @@ "image_field": "website_image", "index_web_pages_for_search": 1, "links": [], - "modified": "2022-06-28 17:10:30.613251", + "make_attachments_public": 1, + "modified": "2022-09-13 04:05:11.614087", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 247140b657eb..d1d228dfdc65 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -910,7 +910,8 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2022-09-12 15:00:10.130340", + "make_attachments_public": 1, + "modified": "2022-09-13 04:08:17.431731", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 53f235b6306ae2c95e23c87ca50cfd6ebe1f3020 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 12 Sep 2022 23:44:56 +0530 Subject: [PATCH 152/898] fix: item wise sales register taxes and charges i have added separate column for other charges. Instead of adding all values to tax_total, it checks if account_type is tax, and then only it adds to total_tax otherwise it adds to the total_other_charges. (cherry picked from commit 62163ab3d3503a3b7f7d5f879b7f4f6ac2b3facc) --- .../item_wise_sales_register.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index ac706666547e..c987231fe17c 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -97,6 +97,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row.update({"rate": d.base_net_rate, "amount": d.base_net_amount}) total_tax = 0 + total_other_charges = 0 for tax in tax_columns: item_tax = itemised_tax.get(d.name, {}).get(tax, {}) row.update( @@ -105,10 +106,18 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0), } ) - total_tax += flt(item_tax.get("tax_amount")) + if item_tax.get("is_other_charges"): + total_other_charges += flt(item_tax.get("tax_amount")) + else: + total_tax += flt(item_tax.get("tax_amount")) row.update( - {"total_tax": total_tax, "total": d.base_net_amount + total_tax, "currency": company_currency} + { + "total_tax": total_tax, + "total_other_charges": total_other_charges, + "total": d.base_net_amount + total_tax, + "currency": company_currency, + } ) if filters.get("group_by"): @@ -477,7 +486,7 @@ def get_tax_accounts( tax_details = frappe.db.sql( """ select - name, parent, description, item_wise_tax_detail, + name, parent, description, item_wise_tax_detail, account_head, charge_type, {add_deduct_tax}, base_tax_amount_after_discount_amount from `tab%s` where @@ -493,11 +502,22 @@ def get_tax_accounts( tuple([doctype] + list(invoice_item_row)), ) + account_doctype = frappe.qb.DocType("Account") + + query = ( + frappe.qb.from_(account_doctype) + .select(account_doctype.name) + .where((account_doctype.account_type == "Tax")) + ) + + tax_accounts = query.run() + for ( name, parent, description, item_wise_tax_detail, + account_head, charge_type, add_deduct_tax, tax_amount, @@ -540,7 +560,11 @@ def get_tax_accounts( ) itemised_tax.setdefault(d.name, {})[description] = frappe._dict( - {"tax_rate": tax_rate, "tax_amount": tax_value} + { + "tax_rate": tax_rate, + "tax_amount": tax_value, + "is_other_charges": 0 if tuple([account_head]) in tax_accounts else 1, + } ) except ValueError: @@ -583,6 +607,13 @@ def get_tax_accounts( "options": "currency", "width": 100, }, + { + "label": _("Total Other Charges"), + "fieldname": "total_other_charges", + "fieldtype": "Currency", + "options": "currency", + "width": 100, + }, { "label": _("Total"), "fieldname": "total", From 38c31077c9ca63ef4da586680cd5bd326f04144a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 13 Sep 2022 14:56:21 +0530 Subject: [PATCH 153/898] feat: Asset Capitalization - manual selection of entry type - GLE cleanup with smaller functions - GLE considering periodical inventory - test cases --- erpnext/assets/doctype/asset/test_asset.py | 10 + .../asset_capitalization.js | 7 +- .../asset_capitalization.json | 42 +- .../asset_capitalization.py | 472 +++++++++++------- .../test_asset_capitalization.py | 341 +++++++++---- .../doctype/asset_repair/test_asset_repair.py | 12 - .../test_stock_reconciliation.py | 7 +- 7 files changed, 570 insertions(+), 321 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 132840e38c2c..e7af9bd5bc25 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1425,6 +1425,16 @@ def create_asset_category(): "depreciation_expense_account": "_Test Depreciations - _TC", }, ) + asset_category.append( + "accounts", + { + "company_name": "_Test Company with perpetual inventory", + "fixed_asset_account": "_Test Fixed Asset - TCP1", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1", + "depreciation_expense_account": "_Test Depreciations - TCP1", + }, + ) + asset_category.insert() diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index d135e60ae7f1..9c7f70b0e57a 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -27,7 +27,11 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s me.setup_warehouse_query(); me.frm.set_query("target_item_code", function() { - return erpnext.queries.item(); + if (me.frm.doc.entry_type == "Capitalization") { + return erpnext.queries.item({"is_stock_item": 0, "is_fixed_asset": 1}); + } else { + return erpnext.queries.item({"is_stock_item": 1, "is_fixed_asset": 0}); + } }); me.frm.set_query("target_asset", function() { @@ -410,5 +414,4 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s } }; -//$.extend(cur_frm.cscript, new erpnext.assets.AssetCapitalization({frm: cur_frm})); cur_frm.cscript = new erpnext.assets.AssetCapitalization({frm: cur_frm}); diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json index d7e6b547162e..d1be5752d611 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.json @@ -8,29 +8,28 @@ "engine": "InnoDB", "field_order": [ "title", + "naming_series", + "entry_type", "target_item_code", "target_item_name", "target_is_fixed_asset", "target_has_batch_no", "target_has_serial_no", - "entry_type", - "finance_book", - "naming_series", "column_break_9", - "company", - "posting_date", - "posting_time", - "set_posting_time", - "amended_from", - "target_item_details_section", "target_asset", "target_asset_name", "target_warehouse", + "target_qty", + "target_stock_uom", "target_batch_no", "target_serial_no", "column_break_5", - "target_qty", - "target_stock_uom", + "company", + "finance_book", + "posting_date", + "posting_time", + "set_posting_time", + "amended_from", "section_break_16", "stock_items", "stock_items_total", @@ -86,16 +85,17 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:!doc.target_item_code || doc.target_is_fixed_asset", + "depends_on": "eval:doc.entry_type=='Capitalization'", "fieldname": "target_asset", "fieldtype": "Link", "in_standard_filter": 1, "label": "Target Asset", + "mandatory_depends_on": "eval:doc.entry_type=='Capitalization'", "no_copy": 1, "options": "Asset" }, { - "depends_on": "target_asset", + "depends_on": "eval:doc.entry_type=='Capitalization'", "fetch_from": "target_asset.asset_name", "fieldname": "target_asset_name", "fieldtype": "Data", @@ -170,15 +170,11 @@ "options": "Asset Capitalization Stock Item" }, { - "fieldname": "target_item_details_section", - "fieldtype": "Section Break", - "label": "Target Item Details" - }, - { - "depends_on": "eval:!doc.target_is_fixed_asset", + "depends_on": "eval:doc.entry_type=='Decapitalization'", "fieldname": "target_warehouse", "fieldtype": "Link", "label": "Target Warehouse", + "mandatory_depends_on": "eval:doc.entry_type=='Decapitalization'", "options": "Warehouse" }, { @@ -240,13 +236,14 @@ "options": "Asset Capitalization Asset Item" }, { + "default": "Capitalization", "fieldname": "entry_type", "fieldtype": "Select", "in_list_view": 1, "in_standard_filter": 1, "label": "Entry Type", - "options": "\nCapitalization\nDecapitalization", - "read_only": 1 + "options": "Capitalization\nDecapitalization", + "reqd": 1 }, { "fieldname": "stock_items_total", @@ -337,7 +334,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-09-15 15:41:27.917458", + "modified": "2022-09-12 15:09:40.771332", "modified_by": "Administrator", "module": "Assets", "name": "Asset Capitalization", @@ -377,6 +374,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title", "track_changes": 1, "track_seen": 1 diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index a8f2d79c270b..2e6f0ad7b020 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -10,9 +10,9 @@ from frappe.utils import cint, flt from six import string_types +import erpnext from erpnext.assets.doctype.asset.depreciation import ( get_gl_entries_on_asset_disposal, - get_gl_entries_on_asset_regain, get_value_after_depreciation_on_disposal_date, ) from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account @@ -32,16 +32,26 @@ from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.utils import get_incoming_rate -force_fields = ['target_item_name', 'target_asset_name', 'item_name', 'asset_name', - 'target_is_fixed_asset', 'target_has_serial_no', 'target_has_batch_no', - 'target_stock_uom', 'stock_uom', 'target_fixed_asset_account', 'fixed_asset_account'] +force_fields = [ + "target_item_name", + "target_asset_name", + "item_name", + "asset_name", + "target_is_fixed_asset", + "target_has_serial_no", + "target_has_batch_no", + "target_stock_uom", + "stock_uom", + "target_fixed_asset_account", + "fixed_asset_account", + "valuation_rate", +] class AssetCapitalization(StockController): def validate(self): self.validate_posting_time() self.set_missing_values(for_validate=True) - self.set_entry_type() self.validate_target_item() self.validate_target_asset() self.validate_consumed_stock_item() @@ -58,14 +68,13 @@ def before_submit(self): def on_submit(self): self.update_stock_ledger() self.make_gl_entries() + self.update_target_asset() def on_cancel(self): - self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') + self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.update_stock_ledger() self.make_gl_entries() - - def set_entry_type(self): - self.entry_type = "Capitalization" if self.target_is_fixed_asset else "Decapitalization" + self.update_target_asset() def set_title(self): self.title = self.target_asset_name or self.target_item_name or self.target_item_code @@ -90,7 +99,7 @@ def set_missing_values(self, for_validate=False): args.update(d.as_dict()) args.doctype = self.doctype args.name = self.name - consumed_stock_item_details = get_consumed_stock_item_details(args, get_valuation_rate=False) + consumed_stock_item_details = get_consumed_stock_item_details(args) for k, v in consumed_stock_item_details.items(): if d.meta.has_field(k) and (not d.get(k) or k in force_fields): d.set(k, v) @@ -100,8 +109,8 @@ def set_missing_values(self, for_validate=False): args.update(d.as_dict()) args.doctype = self.doctype args.name = self.name - args.finance_book = d.get('finance_book') or self.get('finance_book') - consumed_asset_details = get_consumed_asset_details(args, get_asset_value=False) + args.finance_book = d.get("finance_book") or self.get("finance_book") + consumed_asset_details = get_consumed_asset_details(args) for k, v in consumed_asset_details.items(): if d.meta.has_field(k) and (not d.get(k) or k in force_fields): d.set(k, v) @@ -120,8 +129,14 @@ def validate_target_item(self): target_item = frappe.get_cached_doc("Item", self.target_item_code) if not target_item.is_fixed_asset and not target_item.is_stock_item: - frappe.throw(_("Target Item {0} is neither a Fixed Asset nor a Stock Item") - .format(target_item.name)) + frappe.throw( + _("Target Item {0} is neither a Fixed Asset nor a Stock Item").format(target_item.name) + ) + + if self.entry_type == "Capitalization" and not target_item.is_fixed_asset: + frappe.throw(_("Target Item {0} must be a Fixed Asset item").format(target_item.name)) + elif self.entry_type == "Decapitalization" and not target_item.is_stock_item: + frappe.throw(_("Target Item {0} must be a Stock Item").format(target_item.name)) if target_item.is_fixed_asset: self.target_qty = 1 @@ -144,14 +159,13 @@ def validate_target_item(self): self.validate_item(target_item) def validate_target_asset(self): - if self.target_is_fixed_asset and not self.target_asset: - frappe.throw(_("Target Asset is mandatory for Capitalization")) - if self.target_asset: target_asset = self.get_asset_for_validation(self.target_asset) 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)) + frappe.throw( + _("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code) + ) self.validate_asset(target_asset) @@ -172,8 +186,11 @@ def validate_consumed_asset_item(self): for d in self.asset_items: if d.asset: if d.asset == self.target_asset: - frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be the same as the Target Asset") - .format(d.idx, d.asset)) + frappe.throw( + _("Row #{0}: Consumed Asset {1} cannot be the same as the Target Asset").format( + d.idx, d.asset + ) + ) asset = self.get_asset_for_validation(d.asset) self.validate_asset(asset) @@ -198,18 +215,21 @@ def validate_service_item(self): d.cost_center = frappe.get_cached_value("Company", self.company, "cost_center") def validate_source_mandatory(self): - if not self.target_is_fixed_asset and not self.get('asset_items'): + if not self.target_is_fixed_asset and not self.get("asset_items"): frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization")) - if not self.get('stock_items') and not self.get('asset_items'): + if not self.get("stock_items") and not self.get("asset_items"): frappe.throw(_("Consumed Stock Items or Consumed Asset Items is mandatory for Capitalization")) def validate_item(self, item): from erpnext.stock.doctype.item.item import validate_end_of_life + validate_end_of_life(item.name, item.end_of_life, item.disabled) def get_asset_for_validation(self, asset): - return frappe.db.get_value("Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1) + return frappe.db.get_value( + "Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1 + ) def validate_asset(self, asset): if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"): @@ -225,7 +245,7 @@ def validate_asset(self, asset): @frappe.whitelist() def set_warehouse_details(self): - for d in self.get('stock_items'): + for d in self.get("stock_items"): if d.item_code and d.warehouse: args = self.get_args_for_incoming_rate(d) warehouse_details = get_warehouse_details(args) @@ -233,27 +253,30 @@ def set_warehouse_details(self): @frappe.whitelist() def set_asset_values(self): - for d in self.get('asset_items'): + for d in self.get("asset_items"): if d.asset: - finance_book = d.get('finance_book') or self.get('finance_book') + finance_book = d.get("finance_book") or self.get("finance_book") d.current_asset_value = flt(get_current_asset_value(d.asset, finance_book=finance_book)) - d.asset_value = get_value_after_depreciation_on_disposal_date(d.asset, self.posting_date, - finance_book=finance_book) + d.asset_value = get_value_after_depreciation_on_disposal_date( + d.asset, self.posting_date, finance_book=finance_book + ) def get_args_for_incoming_rate(self, item): - return frappe._dict({ - "item_code": item.item_code, - "warehouse": item.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * flt(item.stock_qty), - "serial_no": item.serial_no, - "batch_no": item.batch_no, - "voucher_type": self.doctype, - "voucher_no": self.name, - "company": self.company, - "allow_zero_valuation": cint(item.get('allow_zero_valuation_rate')), - }) + return frappe._dict( + { + "item_code": item.item_code, + "warehouse": item.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * flt(item.stock_qty), + "serial_no": item.serial_no, + "batch_no": item.batch_no, + "voucher_type": self.doctype, + "voucher_no": self.name, + "company": self.company, + "allow_zero_valuation": cint(item.get("allow_zero_valuation_rate")), + } + ) def calculate_totals(self): self.stock_items_total = 0 @@ -261,45 +284,51 @@ def calculate_totals(self): self.service_items_total = 0 for d in self.stock_items: - d.amount = flt(flt(d.stock_qty) * flt(d.valuation_rate), d.precision('amount')) + d.amount = flt(flt(d.stock_qty) * flt(d.valuation_rate), d.precision("amount")) self.stock_items_total += d.amount for d in self.asset_items: - d.asset_value = flt(flt(d.asset_value), d.precision('asset_value')) + d.asset_value = flt(flt(d.asset_value), d.precision("asset_value")) self.asset_items_total += d.asset_value for d in self.service_items: - d.amount = flt(flt(d.qty) * flt(d.rate), d.precision('amount')) + d.amount = flt(flt(d.qty) * flt(d.rate), d.precision("amount")) self.service_items_total += d.amount - self.stock_items_total = flt(self.stock_items_total, self.precision('stock_items_total')) - self.asset_items_total = flt(self.asset_items_total, self.precision('asset_items_total')) - self.service_items_total = flt(self.service_items_total, self.precision('service_items_total')) + self.stock_items_total = flt(self.stock_items_total, self.precision("stock_items_total")) + self.asset_items_total = flt(self.asset_items_total, self.precision("asset_items_total")) + self.service_items_total = flt(self.service_items_total, self.precision("service_items_total")) self.total_value = self.stock_items_total + self.asset_items_total + self.service_items_total - self.total_value = flt(self.total_value, self.precision('total_value')) + self.total_value = flt(self.total_value, self.precision("total_value")) - self.target_qty = flt(self.target_qty, self.precision('target_qty')) + self.target_qty = flt(self.target_qty, self.precision("target_qty")) self.target_incoming_rate = self.total_value / self.target_qty def update_stock_ledger(self): sl_entries = [] for d in self.stock_items: - sle = self.get_sl_entries(d, { - "actual_qty": -flt(d.stock_qty), - }) + sle = self.get_sl_entries( + d, + { + "actual_qty": -flt(d.stock_qty), + }, + ) sl_entries.append(sle) - if not frappe.db.get_value("Item", self.target_item_code, "is_fixed_asset", cache=1): - sle = self.get_sl_entries(self, { - "item_code": self.target_item_code, - "warehouse": self.target_warehouse, - "batch_no": self.target_batch_no, - "serial_no": self.target_serial_no, - "actual_qty": flt(self.target_qty), - "incoming_rate": flt(self.target_incoming_rate) - }) + if self.entry_type == "Decapitalization" and not self.target_is_fixed_asset: + sle = self.get_sl_entries( + self, + { + "item_code": self.target_item_code, + "warehouse": self.target_warehouse, + "batch_no": self.target_batch_no, + "serial_no": self.target_serial_no, + "actual_qty": flt(self.target_qty), + "incoming_rate": flt(self.target_incoming_rate), + }, + ) sl_entries.append(sle) # reverse sl entries if cancel @@ -312,139 +341,183 @@ def update_stock_ledger(self): def make_gl_entries(self, gl_entries=None, from_repost=False): from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries - if not gl_entries: - gl_entries = self.get_gl_entries() - if self.docstatus == 1: + if not gl_entries: + gl_entries = self.get_gl_entries() + if gl_entries: make_gl_entries(gl_entries, from_repost=from_repost) elif self.docstatus == 2: make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): + def get_gl_entries( + self, warehouse_account=None, default_expense_account=None, default_cost_center=None + ): # Stock GL Entries gl_entries = [] - if not warehouse_account: - warehouse_account = get_warehouse_account_map(self.company) + self.warehouse_account = warehouse_account + if not self.warehouse_account: + self.warehouse_account = get_warehouse_account_map(self.company) precision = self.get_debit_field_precision() - sle_map = self.get_stock_ledger_details() + self.sle_map = self.get_stock_ledger_details() + target_account = self.get_target_account() + target_against = set() + + self.get_gl_entries_for_consumed_stock_items( + gl_entries, target_account, target_against, precision + ) + self.get_gl_entries_for_consumed_asset_items( + gl_entries, target_account, target_against, precision + ) + self.get_gl_entries_for_consumed_service_items( + gl_entries, target_account, target_against, precision + ) + + self.get_gl_entries_for_target_item(gl_entries, target_against, precision) + return gl_entries + + def get_target_account(self): if self.target_is_fixed_asset: - target_account = self.target_fixed_asset_account + return self.target_fixed_asset_account else: - target_account = warehouse_account[self.target_warehouse]["account"] - - target_against = set() + return self.warehouse_account[self.target_warehouse]["account"] + def get_gl_entries_for_consumed_stock_items( + self, gl_entries, target_account, target_against, precision + ): # Consumed Stock Items - total_consumed_stock_value = 0 for item_row in self.stock_items: - sle_list = sle_map.get(item_row.name) + sle_list = self.sle_map.get(item_row.name) if sle_list: for sle in sle_list: stock_value_difference = flt(sle.stock_value_difference, precision) - total_consumed_stock_value += -1 * sle.stock_value_difference - - account = warehouse_account[sle.warehouse]["account"] - target_against.add(account) - gl_entries.append(self.get_gl_dict({ - "account": account, - "against": target_account, - "cost_center": item_row.cost_center, - "project": item_row.get('project') or self.get('project'), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "credit": -1 * stock_value_difference, - }, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) + if erpnext.is_perpetual_inventory_enabled(self.company): + account = self.warehouse_account[sle.warehouse]["account"] + else: + account = self.get_company_default("default_expense_account") + target_against.add(account) + gl_entries.append( + self.get_gl_dict( + { + "account": account, + "against": target_account, + "cost_center": item_row.cost_center, + "project": item_row.get("project") or self.get("project"), + "remarks": self.get("remarks") or "Accounting Entry for Stock", + "credit": -1 * stock_value_difference, + }, + self.warehouse_account[sle.warehouse]["account_currency"], + item=item_row, + ) + ) + + def get_gl_entries_for_consumed_asset_items( + self, gl_entries, target_account, target_against, precision + ): # Consumed Assets for item in self.asset_items: asset = self.get_asset(item) - if self.docstatus == 2: - fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset, - item.asset_value, item.get('finance_book') or self.get('finance_book')) - asset.db_set("disposal_date", None) + if asset.calculate_depreciation: + self.depreciate_asset(asset) + asset.reload() - self.set_consumed_asset_status(asset) + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( + asset, item.asset_value, item.get("finance_book") or self.get("finance_book") + ) - if asset.calculate_depreciation: - self.reverse_depreciation_entry_made_after_disposal(asset) - self.reset_depreciation_schedule(asset) - else: - if asset.calculate_depreciation: - self.depreciate_asset(asset) - asset.reload() - - fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset, - item.asset_value, item.get('finance_book') or self.get('finance_book')) - asset.db_set("disposal_date", self.posting_date) + asset.db_set("disposal_date", self.posting_date) - self.set_consumed_asset_status(asset) + self.set_consumed_asset_status(asset) for gle in fixed_asset_gl_entries: gle["against"] = target_account gl_entries.append(self.get_gl_dict(gle, item=item)) + target_against.add(gle["account"]) + def get_gl_entries_for_consumed_service_items( + self, gl_entries, target_account, target_against, precision + ): # Service Expenses - total_service_expenses = 0 for item_row in self.service_items: expense_amount = flt(item_row.amount, precision) - total_service_expenses += expense_amount target_against.add(item_row.expense_account) - gl_entries.append(self.get_gl_dict({ - "account": item_row.expense_account, - "against": target_account, - "cost_center": item_row.cost_center, - "project": item_row.get('project') or self.get('project'), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "credit": expense_amount, - }, item=item_row)) - - target_against = ", ".join(target_against) - total_target_stock_value = 0 - total_target_asset_value = 0 + gl_entries.append( + self.get_gl_dict( + { + "account": item_row.expense_account, + "against": target_account, + "cost_center": item_row.cost_center, + "project": item_row.get("project") or self.get("project"), + "remarks": self.get("remarks") or "Accounting Entry for Stock", + "credit": expense_amount, + }, + item=item_row, + ) + ) + def get_gl_entries_for_target_item(self, gl_entries, target_against, precision): if self.target_is_fixed_asset: - # Target Asset Item - total_target_asset_value = flt(self.total_value, precision) - gl_entries.append(self.get_gl_dict({ - "account": self.target_fixed_asset_account, - "against": target_against, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": total_target_asset_value, - "cost_center": self.get('cost_center') - }, item=self)) - - if self.docstatus == 1: - asset_doc = frappe.get_doc("Asset", self.target_asset) - 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.prepare_depreciation_data() - asset_doc.flags.ignore_validate_update_after_submit = True - asset_doc.save() + # Capitalization + gl_entries.append( + self.get_gl_dict( + { + "account": self.target_fixed_asset_account, + "against": ", ".join(target_against), + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": flt(self.total_value, precision), + "cost_center": self.get("cost_center"), + }, + item=self, + ) + ) else: # Target Stock Item - sle_list = sle_map.get(self.name) + sle_list = self.sle_map.get(self.name) for sle in sle_list: stock_value_difference = flt(sle.stock_value_difference, precision) - total_target_stock_value += sle.stock_value_difference - account = warehouse_account[sle.warehouse]["account"] - - gl_entries.append(self.get_gl_dict({ - "account": account, - "against": target_against, - "cost_center": self.cost_center, - "project": self.get('project'), - "remarks": self.get("remarks") or "Accounting Entry for Stock", - "debit": stock_value_difference, - }, warehouse_account[sle.warehouse]["account_currency"], item=self)) + account = self.warehouse_account[sle.warehouse]["account"] + + gl_entries.append( + self.get_gl_dict( + { + "account": account, + "against": ", ".join(target_against), + "cost_center": self.cost_center, + "project": self.get("project"), + "remarks": self.get("remarks") or "Accounting Entry for Stock", + "debit": stock_value_difference, + }, + self.warehouse_account[sle.warehouse]["account_currency"], + item=self, + ) + ) + + def update_target_asset(self): + total_target_asset_value = flt(self.total_value, self.precision("total_value")) + if self.docstatus == 1 and self.entry_type == "Capitalization": + asset_doc = frappe.get_doc("Asset", self.target_asset) + 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.prepare_depreciation_data() + asset_doc.flags.ignore_validate_update_after_submit = True + asset_doc.save() + elif self.docstatus == 2: + for item in self.asset_items: + asset = self.get_asset(item) + asset.db_set("disposal_date", None) + self.set_consumed_asset_status(asset) - return gl_entries + if asset.calculate_depreciation: + self.reverse_depreciation_entry_made_after_disposal(asset) + self.reset_depreciation_schedule(asset) def get_asset(self, item): asset = frappe.get_doc("Asset", item.asset) @@ -489,16 +562,12 @@ def get_target_item_details(item_code=None, company=None): item_defaults = get_item_defaults(item.name, company) item_group_defaults = get_item_group_defaults(item.name, company) brand_defaults = get_brand_defaults(item.name, company) - out.cost_center = get_default_cost_center(frappe._dict({'item_code': item.name, 'company': company}), - item_defaults, item_group_defaults, brand_defaults) - - # Set Entry Type - if not item_code: - out.entry_type = "" - elif out.target_is_fixed_asset: - out.entry_type = "Capitalization" - else: - out.entry_type = "Decapitalization" + out.cost_center = get_default_cost_center( + frappe._dict({"item_code": item.name, "company": company}), + item_defaults, + item_group_defaults, + brand_defaults, + ) return out @@ -510,7 +579,7 @@ def get_target_asset_details(asset=None, company=None): # Get Asset Details asset_details = frappe._dict() if asset: - asset_details = frappe.db.get_value("Asset", asset, ['asset_name', 'item_code'], as_dict=1) + 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)) @@ -521,8 +590,9 @@ def get_target_asset_details(asset=None, company=None): 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) + 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 @@ -530,7 +600,7 @@ def get_target_asset_details(asset=None, company=None): @frappe.whitelist() -def get_consumed_stock_item_details(args, get_valuation_rate=True): +def get_consumed_stock_item_details(args): if isinstance(args, string_types): args = json.loads(args) @@ -554,24 +624,29 @@ def get_consumed_stock_item_details(args, get_valuation_rate=True): item_defaults = get_item_defaults(item.name, args.company) item_group_defaults = get_item_group_defaults(item.name, args.company) brand_defaults = get_brand_defaults(item.name, args.company) - out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults) - - if get_valuation_rate: - if args.item_code and out.warehouse: - incoming_rate_args = frappe._dict({ - 'item_code': args.item_code, - 'warehouse': out.warehouse, - 'posting_date': args.posting_date, - 'posting_time': args.posting_time, - 'qty': -1 * flt(out.stock_qty), + out.cost_center = get_default_cost_center( + args, item_defaults, item_group_defaults, brand_defaults + ) + + if args.item_code and out.warehouse: + incoming_rate_args = frappe._dict( + { + "item_code": args.item_code, + "warehouse": out.warehouse, + "posting_date": args.posting_date, + "posting_time": args.posting_time, + "qty": -1 * flt(out.stock_qty), "voucher_type": args.doctype, "voucher_no": args.name, "company": args.company, - }) - out.update(get_warehouse_details(incoming_rate_args)) - else: - out.valuation_rate = 0 - out.actual_qty = 0 + "serial_no": args.serial_no, + "batch_no": args.batch_no, + } + ) + out.update(get_warehouse_details(incoming_rate_args)) + else: + out.valuation_rate = 0 + out.actual_qty = 0 return out @@ -587,13 +662,13 @@ def get_warehouse_details(args): if args.warehouse and args.item_code: out = { "actual_qty": get_previous_sle(args).get("qty_after_transaction") or 0, - "valuation_rate": get_incoming_rate(args, raise_error_if_no_rate=False) + "valuation_rate": get_incoming_rate(args, raise_error_if_no_rate=False), } return out @frappe.whitelist() -def get_consumed_asset_details(args, get_asset_value=True): +def get_consumed_asset_details(args): if isinstance(args, string_types): args = json.loads(args) @@ -602,7 +677,9 @@ def get_consumed_asset_details(args, get_asset_value=True): asset_details = frappe._dict() if args.asset: - asset_details = frappe.db.get_value("Asset", args.asset, ['asset_name', 'item_code', 'item_name'], as_dict=1) + asset_details = frappe.db.get_value( + "Asset", args.asset, ["asset_name", "item_code", "item_name"], as_dict=1 + ) if not asset_details: frappe.throw(_("Asset {0} does not exist").format(args.asset)) @@ -610,19 +687,22 @@ def get_consumed_asset_details(args, get_asset_value=True): out.asset_name = asset_details.asset_name out.item_name = asset_details.item_name - if get_asset_value: - if args.asset: - out.current_asset_value = flt(get_current_asset_value(args.asset, finance_book=args.finance_book)) - out.asset_value = get_value_after_depreciation_on_disposal_date(args.asset, args.posting_date, - finance_book=args.finance_book) - else: - out.current_asset_value = 0 - out.asset_value = 0 + if args.asset: + out.current_asset_value = flt( + get_current_asset_value(args.asset, finance_book=args.finance_book) + ) + out.asset_value = get_value_after_depreciation_on_disposal_date( + args.asset, args.posting_date, finance_book=args.finance_book + ) + else: + out.current_asset_value = 0 + out.asset_value = 0 # Account if asset_details.item_code: - out.fixed_asset_account = get_asset_category_account('fixed_asset_account', item=asset_details.item_code, - company=args.company) + out.fixed_asset_account = get_asset_category_account( + "fixed_asset_account", item=asset_details.item_code, company=args.company + ) else: out.fixed_asset_account = None @@ -632,7 +712,9 @@ def get_consumed_asset_details(args, get_asset_value=True): item_defaults = get_item_defaults(item.name, args.company) item_group_defaults = get_item_group_defaults(item.name, args.company) brand_defaults = get_brand_defaults(item.name, args.company) - out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults) + out.cost_center = get_default_cost_center( + args, item_defaults, item_group_defaults, brand_defaults + ) return out @@ -657,7 +739,11 @@ def get_service_item_details(args): item_group_defaults = get_item_group_defaults(item.name, args.company) brand_defaults = get_brand_defaults(item.name, args.company) - out.expense_account = get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) - out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults) + out.expense_account = get_default_expense_account( + args, item_defaults, item_group_defaults, brand_defaults + ) + out.cost_center = get_default_cost_center( + args, item_defaults, item_group_defaults, brand_defaults + ) return out diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 7046de6f837b..86861f0b1658 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -22,9 +22,12 @@ def setUp(self): create_asset_capitalization_data() frappe.db.sql("delete from `tabTax Rule`") - def test_capitalization(self): + def test_capitalization_with_perpetual_inventory(self): + company = "_Test Company with perpetual inventory" + set_depreciation_settings_in_company(company=company) + # Variables - consumed_asset_value = 100_000 + consumed_asset_value = 100000 stock_rate = 1000 stock_qty = 2 @@ -34,23 +37,39 @@ def test_capitalization(self): service_qty = 2 service_amount = 1000 - total_amount = 103_000 + total_amount = 103000 # Create assets - target_asset = create_asset(asset_name='Asset Capitalization Target Asset', submit=1) - consumed_asset = create_asset(asset_name='Asset Capitalization Consumable Asset', asset_value=consumed_asset_value, - submit=1) + target_asset = create_asset( + asset_name="Asset Capitalization Target Asset", + submit=1, + warehouse="Stores - TCP1", + company=company, + ) + consumed_asset = create_asset( + asset_name="Asset Capitalization Consumable Asset", + asset_value=consumed_asset_value, + submit=1, + warehouse="Stores - TCP1", + company=company, + ) # Create and submit Asset Captitalization - asset_capitalization = create_asset_capitalization(target_asset=target_asset.name, - stock_qty=stock_qty, stock_rate=stock_rate, + asset_capitalization = create_asset_capitalization( + entry_type="Capitalization", + target_asset=target_asset.name, + stock_qty=stock_qty, + stock_rate=stock_rate, consumed_asset=consumed_asset.name, - service_qty=service_qty, service_rate=service_rate, - service_expense_account='Expenses Included In Asset Valuation - _TC', - submit=1) + service_qty=service_qty, + service_rate=service_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.entry_type, "Capitalization") self.assertEqual(asset_capitalization.target_qty, 1) self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate) @@ -72,13 +91,13 @@ def test_capitalization(self): self.assertEqual(target_asset.purchase_receipt_amount, total_amount) # Test Consumed Asset values - self.assertEqual(consumed_asset.db_get('status'), 'Capitalized') + self.assertEqual(consumed_asset.db_get("status"), "Capitalized") # Test General Ledger Entries expected_gle = { - '_Test Fixed Asset - _TC': 3000, - 'Expenses Included In Asset Valuation - _TC': -1000, - 'Stock In Hand - _TC' : -2000 + "_Test Fixed Asset - TCP1": 3000, + "Expenses Included In Asset Valuation - TCP1": -1000, + "_Test Warehouse - TCP1": -2000, } actual_gle = get_actual_gle_dict(asset_capitalization.name) @@ -86,25 +105,121 @@ def test_capitalization(self): # Test Stock Ledger Entries expected_sle = { - ('Capitalization Source Stock Item', '_Test Warehouse - _TC'): { - 'actual_qty': -stock_qty, 'stock_value_difference': -stock_amount + ("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.assertEqual(consumed_asset.db_get("status"), "Submitted") + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + + def test_capitalization_with_periodical_inventory(self): + company = "_Test Company" + # Variables + consumed_asset_value = 100000 + + stock_rate = 1000 + stock_qty = 2 + stock_amount = 2000 + + service_rate = 500 + service_qty = 2 + service_amount = 1000 + total_amount = 103000 + + # Create assets + target_asset = create_asset( + asset_name="Asset Capitalization Target Asset", + submit=1, + warehouse="Stores - _TC", + company=company, + ) + consumed_asset = create_asset( + asset_name="Asset Capitalization Consumable Asset", + asset_value=consumed_asset_value, + submit=1, + warehouse="Stores - _TC", + company=company, + ) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization( + entry_type="Capitalization", + target_asset=target_asset.name, + stock_qty=stock_qty, + stock_rate=stock_rate, + consumed_asset=consumed_asset.name, + service_qty=service_qty, + service_rate=service_rate, + service_expense_account="Expenses Included In Asset Valuation - _TC", + company=company, + submit=1, + ) + + # Test Asset Capitalization values + self.assertEqual(asset_capitalization.entry_type, "Capitalization") + 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.asset_items[0].asset_value, consumed_asset_value) + self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value) + + self.assertEqual(asset_capitalization.service_items[0].amount, service_amount) + self.assertEqual(asset_capitalization.service_items_total, service_amount) + + self.assertEqual(asset_capitalization.total_value, total_amount) + self.assertEqual(asset_capitalization.target_incoming_rate, total_amount) + + # Test Target Asset values + target_asset.reload() + self.assertEqual(target_asset.gross_purchase_amount, total_amount) + self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + + # Test Consumed Asset values + self.assertEqual(consumed_asset.db_get("status"), "Capitalized") + + # Test General Ledger Entries + default_expense_account = frappe.db.get_value("Company", company, "default_expense_account") + expected_gle = { + "_Test Fixed Asset - _TC": 3000, + "Expenses Included In Asset Valuation - _TC": -1000, + default_expense_account: -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 - _TC"): { + "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.assertEqual(consumed_asset.db_get('status'), 'Submitted') + self.assertEqual(consumed_asset.db_get("status"), "Submitted") 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' - depreciation_start_date = '2020-12-31' - capitalization_date = '2021-06-30' + purchase_date = "2020-01-01" + depreciation_start_date = "2020-12-31" + capitalization_date = "2021-06-30" total_number_of_depreciations = 3 expected_value_after_useful_life = 10_000 @@ -126,29 +241,38 @@ def test_decapitalization_with_depreciation(self): # Create assets consumed_asset = create_depreciation_asset( - asset_name='Asset Capitalization Consumable Asset', + asset_name="Asset Capitalization Consumable Asset", asset_value=consumed_asset_purchase_value, purchase_date=purchase_date, depreciation_start_date=depreciation_start_date, - depreciation_method='Straight Line', + depreciation_method="Straight Line", total_number_of_depreciations=total_number_of_depreciations, frequency_of_depreciation=12, expected_value_after_useful_life=expected_value_after_useful_life, - submit=1) + company="_Test Company with perpetual inventory", + submit=1, + ) # Create and submit Asset Captitalization asset_capitalization = create_asset_capitalization( + entry_type="Decapitalization", posting_date=capitalization_date, # half a year target_item_code="Capitalization Target Stock Item", target_qty=target_qty, consumed_asset=consumed_asset.name, - submit=1) + company="_Test Company with perpetual inventory", + submit=1, + ) # Test Asset Capitalization values - self.assertEqual(asset_capitalization.entry_type, 'Decapitalization') - - self.assertEqual(asset_capitalization.asset_items[0].current_asset_value, consumed_asset_current_value) - self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value_before_disposal) + self.assertEqual(asset_capitalization.entry_type, "Decapitalization") + + self.assertEqual( + asset_capitalization.asset_items[0].current_asset_value, consumed_asset_current_value + ) + self.assertEqual( + asset_capitalization.asset_items[0].asset_value, consumed_asset_value_before_disposal + ) self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value_before_disposal) self.assertEqual(asset_capitalization.total_value, consumed_asset_value_before_disposal) @@ -156,38 +280,45 @@ def test_decapitalization_with_depreciation(self): # Test Consumed Asset values consumed_asset.reload() - self.assertEqual(consumed_asset.status, 'Decapitalized') - - consumed_depreciation_schedule = [d for d in consumed_asset.schedules - if getdate(d.schedule_date) == getdate(capitalization_date)] - self.assertTrue(consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry) - self.assertEqual(consumed_depreciation_schedule[0].depreciation_amount, depreciation_before_disposal_amount) + self.assertEqual(consumed_asset.status, "Decapitalized") + + consumed_depreciation_schedule = [ + d for d in consumed_asset.schedules if getdate(d.schedule_date) == getdate(capitalization_date) + ] + self.assertTrue( + consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry + ) + self.assertEqual( + consumed_depreciation_schedule[0].depreciation_amount, depreciation_before_disposal_amount + ) # Test General Ledger Entries expected_gle = { - 'Stock In Hand - _TC': consumed_asset_value_before_disposal, - '_Test Accumulated Depreciations - _TC': accumulated_depreciation, - '_Test Fixed Asset - _TC': -consumed_asset_purchase_value, + "_Test Warehouse - TCP1": consumed_asset_value_before_disposal, + "_Test Accumulated Depreciations - TCP1": accumulated_depreciation, + "_Test Fixed Asset - TCP1": -consumed_asset_purchase_value, } actual_gle = get_actual_gle_dict(asset_capitalization.name) - self.assertEqual(actual_gle, expected_gle) # Cancel Asset Capitalization and make test entries and status are reversed asset_capitalization.reload() asset_capitalization.cancel() - self.assertEqual(consumed_asset.db_get('status'), 'Partially Depreciated') + self.assertEqual(consumed_asset.db_get("status"), "Partially Depreciated") self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) def create_asset_capitalization_data(): - create_item("Capitalization Target Stock Item", - is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) - create_item("Capitalization Source Stock Item", - is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) - create_item("Capitalization Source Service Item", - is_stock_item=0, is_fixed_asset=0, is_purchase_item=0) + create_item( + "Capitalization Target Stock Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=0 + ) + create_item( + "Capitalization Source Stock Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=0 + ) + create_item( + "Capitalization Source Service Item", is_stock_item=0, is_fixed_asset=0, is_purchase_item=0 + ) def create_asset_capitalization(**args): @@ -204,43 +335,55 @@ def create_asset_capitalization(**args): source_warehouse = args.source_warehouse or warehouse asset_capitalization = frappe.new_doc("Asset Capitalization") - asset_capitalization.update({ - "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'), - "target_item_code": target_item_code, - "target_asset": target_asset.name, - "target_warehouse": target_warehouse, - "target_qty": flt(args.target_qty) or 1, - "target_batch_no": args.target_batch_no, - "target_serial_no": args.target_serial_no, - "finance_book": args.finance_book - }) + asset_capitalization.update( + { + "entry_type": args.entry_type or "Capitalization", + "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"), + "target_item_code": target_item_code, + "target_asset": target_asset.name, + "target_warehouse": target_warehouse, + "target_qty": flt(args.target_qty) or 1, + "target_batch_no": args.target_batch_no, + "target_serial_no": args.target_serial_no, + "finance_book": args.finance_book, + } + ) if args.posting_date or args.posting_time: asset_capitalization.set_posting_time = 1 if flt(args.stock_rate): - asset_capitalization.append("stock_items", { - "item_code": args.stock_item or "Capitalization Source Stock Item", - "warehouse": source_warehouse, - "stock_qty": flt(args.stock_qty) or 1, - "batch_no": args.stock_batch_no, - "serial_no": args.stock_serial_no, - }) + asset_capitalization.append( + "stock_items", + { + "item_code": args.stock_item or "Capitalization Source Stock Item", + "warehouse": source_warehouse, + "stock_qty": flt(args.stock_qty) or 1, + "batch_no": args.stock_batch_no, + "serial_no": args.stock_serial_no, + }, + ) if args.consumed_asset: - asset_capitalization.append("asset_items", { - "asset": args.consumed_asset, - }) + asset_capitalization.append( + "asset_items", + { + "asset": args.consumed_asset, + }, + ) if flt(args.service_rate): - asset_capitalization.append("service_items", { - "item_code": args.service_item or "Capitalization Source Service Item", - "expense_account": args.service_expense_account, - "qty": flt(args.service_qty) or 1, - "rate": flt(args.service_rate) - }) + asset_capitalization.append( + "service_items", + { + "item_code": args.service_item or "Capitalization Source Service Item", + "expense_account": args.service_expense_account, + "qty": flt(args.service_qty) or 1, + "rate": flt(args.service_rate), + }, + ) if args.submit: create_stock_reconciliation(asset_capitalization, stock_rate=args.stock_rate) @@ -255,17 +398,23 @@ def create_asset_capitalization(**args): def create_stock_reconciliation(asset_capitalization, stock_rate=0): from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + EmptyStockReconciliationItemsError, create_stock_reconciliation, ) - if not asset_capitalization.get('stock_items'): + + if not asset_capitalization.get("stock_items"): return - return create_stock_reconciliation( - item_code=asset_capitalization.stock_items[0].item_code, - warehouse=asset_capitalization.stock_items[0].warehouse, - qty=flt(asset_capitalization.stock_items[0].stock_qty), - rate=flt(stock_rate), - company=asset_capitalization.company) + try: + create_stock_reconciliation( + item_code=asset_capitalization.stock_items[0].item_code, + warehouse=asset_capitalization.stock_items[0].warehouse, + qty=flt(asset_capitalization.stock_items[0].stock_qty), + rate=flt(stock_rate), + company=asset_capitalization.company, + ) + except EmptyStockReconciliationItemsError: + pass def create_depreciation_asset(**args): @@ -281,15 +430,15 @@ def create_depreciation_asset(**args): asset.asset_name = args.asset_name or asset.item_code asset.location = args.location or "Test Location" - asset.purchase_date = args.purchase_date or '2020-01-01' + asset.purchase_date = args.purchase_date or "2020-01-01" asset.available_for_use_date = args.available_for_use_date or asset.purchase_date asset.gross_purchase_amount = args.asset_value or 100000 asset.purchase_receipt_amount = asset.gross_purchase_amount - finance_book = asset.append('finance_books') - finance_book.depreciation_start_date = args.depreciation_start_date or '2020-12-31' - finance_book.depreciation_method = args.depreciation_method or 'Straight Line' + finance_book = asset.append("finance_books") + finance_book.depreciation_start_date = args.depreciation_start_date or "2020-12-31" + finance_book.depreciation_method = args.depreciation_method or "Straight Line" finance_book.total_number_of_depreciations = cint(args.total_number_of_depreciations) or 3 finance_book.frequency_of_depreciation = cint(args.frequency_of_depreciation) or 12 finance_book.expected_value_after_useful_life = flt(args.expected_value_after_useful_life) @@ -305,17 +454,23 @@ def create_depreciation_asset(**args): def get_actual_gle_dict(name): - return dict(frappe.db.sql(""" + return dict( + frappe.db.sql( + """ select account, sum(debit-credit) as diff from `tabGL Entry` where voucher_type = 'Asset Capitalization' and voucher_no = %s group by account having diff != 0 - """, name)) + """, + name, + ) + ) def get_actual_sle_dict(name): - sles = frappe.db.sql(""" + sles = frappe.db.sql( + """ select item_code, warehouse, sum(actual_qty) as actual_qty, @@ -324,12 +479,16 @@ def get_actual_sle_dict(name): where voucher_type = 'Asset Capitalization' and voucher_no = %s group by item_code, warehouse having actual_qty != 0 - """, name, as_dict=1) + """, + name, + as_dict=1, + ) sle_dict = {} for d in sles: sle_dict[(d.item_code, d.warehouse)] = { - 'actual_qty': d.actual_qty, 'stock_value_difference': d.stock_value_difference + "actual_qty": d.actual_qty, + "stock_value_difference": d.stock_value_difference, } return sle_dict diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 6e06f52ac65d..2786349f7b68 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -129,18 +129,6 @@ def test_purchase_invoice(self): def test_gl_entries_with_perpetual_inventory(self): set_depreciation_settings_in_company(company="_Test Company with perpetual inventory") - asset_category = frappe.get_doc("Asset Category", "Computers") - asset_category.append( - "accounts", - { - "company_name": "_Test Company with perpetual inventory", - "fixed_asset_account": "_Test Fixed Asset - TCP1", - "accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1", - "depreciation_expense_account": "_Test Depreciations - TCP1", - }, - ) - asset_category.save() - asset_repair = create_asset_repair( capitalize_repair_cost=1, stock_consumption=1, diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 191c03f5f1ca..4e76ae781f9e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -727,7 +727,12 @@ def create_stock_reconciliation(**args): sr.set_posting_time = 1 sr.company = args.company or "_Test Company" sr.expense_account = args.expense_account or ( - "Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC" + ( + frappe.get_cached_value("Company", sr.company, "stock_adjustment_account") + or "Stock Adjustment - _TC" + ) + if frappe.get_all("Stock Ledger Entry", {"company": sr.company}) + else "Temporary Opening - _TC" ) sr.cost_center = ( args.cost_center From 740c17c23127fb2c0aa5a1c50fb4d6c105c43e77 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 13 Sep 2022 12:13:00 +0000 Subject: [PATCH 154/898] chore(release): Bumped to Version 14.1.1 ## [14.1.1](https://github.com/frappe/erpnext/compare/v14.1.0...v14.1.1) (2022-09-13) ### Bug Fixes * AD not getting copied from SCO while creating a Material Transfer ([#32109](https://github.com/frappe/erpnext/issues/32109)) ([09d8fa4](https://github.com/frappe/erpnext/commit/09d8fa4b5dbb821b2cad78ef4ea0e6b01ee755c9)), closes [#32106](https://github.com/frappe/erpnext/issues/32106) * add missing warehouse filter in BOM Stock Calculated report ([4e09203](https://github.com/frappe/erpnext/commit/4e09203ddcf6ad344a2dbc6632a207aeaf5071c9)) * conflict ([a408722](https://github.com/frappe/erpnext/commit/a40872298340a6fb2e92b3104b5e74a3b037d25b)) * consider Stock Entry purpose while getting total supplied qty ([5af0062](https://github.com/frappe/erpnext/commit/5af006278ede5ae20c84132e6179f3b21dc4ec84)) * customer code max characters limit issue ([#32177](https://github.com/frappe/erpnext/issues/32177)) ([93e5100](https://github.com/frappe/erpnext/commit/93e510023a9498e744c37daf6f2e45fb356b116c)) * delete linked payment ledger entries no source doc deletion ([ccd0449](https://github.com/frappe/erpnext/commit/ccd0449006183b43a617c1acc736730b7ac32bc6)) * drop old notes column from lead and prospect ([ded6e7f](https://github.com/frappe/erpnext/commit/ded6e7f5449d785ee0d862ac61da00b58b5259f7)) * **Employee:** shorter tab titles (backport [#32192](https://github.com/frappe/erpnext/issues/32192)) ([#32193](https://github.com/frappe/erpnext/issues/32193)) ([027e66f](https://github.com/frappe/erpnext/commit/027e66f637dcaf5dcddb797b139632f3f363ca1b)) * hide "Return of Components" button in closed SCO (backport [#32130](https://github.com/frappe/erpnext/issues/32130)) ([#32131](https://github.com/frappe/erpnext/issues/32131)) ([7840fed](https://github.com/frappe/erpnext/commit/7840fed7a50a08d9b90b27bae8096651a87e5a23)) * import error on bank statement import ([467ee97](https://github.com/frappe/erpnext/commit/467ee9793857b95352f860097aeef7696114e6e8)) * internal transfer flow ([4bac0da](https://github.com/frappe/erpnext/commit/4bac0daf9bc33487e6c0bab91a8d18bdb9843686)) * inventory dimension filter's label not showing in the reort ([2d2eef6](https://github.com/frappe/erpnext/commit/2d2eef6b2945536546896ae190baf47aa6bec42e)) * item wise sales register taxes and charges ([53f235b](https://github.com/frappe/erpnext/commit/53f235b6306ae2c95e23c87ca50cfd6ebe1f3020)) * Migrate old lead notes as per the new format ([28b1d4e](https://github.com/frappe/erpnext/commit/28b1d4e2a31daafaa7192bba8a0e9ad0159c57bc)) * option to start reposting from repost item valuation ([6483195](https://github.com/frappe/erpnext/commit/64831952d5c827089be95fba5bad074674ccaeea)) * pick_list - picked qty getting set to 1 ([3c3ab89](https://github.com/frappe/erpnext/commit/3c3ab897d58a169ef2b1564b759cba9ed51ffc48)) * promotional scheme min and max amount configuration ([702c16e](https://github.com/frappe/erpnext/commit/702c16eac4ebdc16f7c7cbae1b82263cbb41a62c)) * QR Code multi currency issue ([d14a9a1](https://github.com/frappe/erpnext/commit/d14a9a1e89901f6d2d6568c57e6b39f653ab22fe)) * Rate for internal PI have non stock UOM items ([c06e241](https://github.com/frappe/erpnext/commit/c06e241fbc1b555300fcc2a76a79bc5250202956)) * remove EmployeeBoardingController (backport [#32139](https://github.com/frappe/erpnext/issues/32139)) ([#32191](https://github.com/frappe/erpnext/issues/32191)) ([944b8a0](https://github.com/frappe/erpnext/commit/944b8a0ed79d8081632eca2ebb4877e2a7cb3817)) * remove multiple call to ple creation ([a2285d5](https://github.com/frappe/erpnext/commit/a2285d5e5e84df8bc50c87aecd1fcadb11908c76)) * reposting not working for internal transferred purchase receipt ([7ce6729](https://github.com/frappe/erpnext/commit/7ce672910015afe2e7d0383d4da2813f7901f826)) * require barcode item barcode ([#32111](https://github.com/frappe/erpnext/issues/32111)) ([404668f](https://github.com/frappe/erpnext/commit/404668fcc9778017eefc6a5470e6fad7d045f1b8)), closes [#31957](https://github.com/frappe/erpnext/issues/31957) * required_qty in BOM Stock Calculated report ([af883be](https://github.com/frappe/erpnext/commit/af883be065a9362f5a12cea8d41f818ac1e9dc8d)) * SCO Supplied Items returned-qty ([4568648](https://github.com/frappe/erpnext/commit/4568648d8ce4bdc4bb74076d90abacf5edf16fa6)) * Set filter condition and spell in AR ([174c750](https://github.com/frappe/erpnext/commit/174c7503c97b5ddf0ed264fe540d2fab28853074)) * status filter for Subcontracting Order in Stock Entry ([844f120](https://github.com/frappe/erpnext/commit/844f120a56822a3f87d2af09869445197515efe8)) * Subcontracting Receipt GL Entries (backport [#31918](https://github.com/frappe/erpnext/issues/31918)) ([#32124](https://github.com/frappe/erpnext/issues/32124)) ([f2ab220](https://github.com/frappe/erpnext/commit/f2ab220ce31cef507f8f0f4eb3eac3fb1aa6684d)) * **UX:** make Item attachments public by default (backport [#32196](https://github.com/frappe/erpnext/issues/32196)) ([#32197](https://github.com/frappe/erpnext/issues/32197)) ([2c4867c](https://github.com/frappe/erpnext/commit/2c4867cc981f02095d2f18a01f3f4c7130b8fb51)) * validate Subcontracting Order in Stock Entry ([d3cc9d4](https://github.com/frappe/erpnext/commit/d3cc9d4fa6369d636a0eb34678ec86e68a7fcb49)) ### Performance Improvements * lesser SQL queries and no validation ([7bb5f6e](https://github.com/frappe/erpnext/commit/7bb5f6eb2365c114573705753d93358221c2a8d2)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2b9c6c8c2413..6ce0f9cd2f61 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.1.0" +__version__ = "14.1.1" def get_default_company(user=None): From 38488c13e616d164097a60e08c229bbeedf3bab3 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 13 Sep 2022 21:52:58 +0530 Subject: [PATCH 155/898] fix: unknown column error while updating value of maintain-stock in item master (cherry picked from commit 7b878ea3d8f2a1da6c8bd9d4994e638c630e59ba) --- erpnext/stock/doctype/item/item.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 87fa72d74f02..143fe408c34b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -945,7 +945,12 @@ def _get_linked_submitted_documents(self, changed_fields: List[str]) -> Optional if doctype == "Product Bundle": filters = {"new_item_code": self.name} - if doctype in ( + if linked_doc := frappe.db.get_value( + doctype, filters, ["new_item_code as docname"], as_dict=True + ): + return linked_doc.update({"doctype": doctype}) + + elif doctype in ( "Purchase Invoice Item", "Sales Invoice Item", ): From 2fe72af359c74ffb6264108fa04dd9b3469f8113 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 13 Sep 2022 22:12:56 +0530 Subject: [PATCH 156/898] test: add test case for item master maintain-stock (cherry picked from commit bf1fa014f472e4b614c0b18fab9a72277fa147a2) --- erpnext/stock/doctype/item/test_item.py | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 89da72195fc3..1cee553be5be 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -786,6 +786,36 @@ def test_customer_codes_length(self): item.save() self.assertTrue(len(item.customer_code) > 140) + def test_update_is_stock_item(self): + # Step - 1: Create an Item with Maintain Stock enabled + item = make_item(properties={"is_stock_item": 1}) + + # Step - 2: Disable Maintain Stock + item.is_stock_item = 0 + item.save() + item.reload() + self.assertEqual(item.is_stock_item, 0) + + # Step - 3: Create Product Bundle + pb = frappe.new_doc("Product Bundle") + pb.new_item_code = item.name + pb.flags.ignore_mandatory = True + pb.save() + + # Step - 4: Try to enable Maintain Stock, should throw a validation error + item.is_stock_item = 1 + self.assertRaises(frappe.ValidationError, item.save) + item.reload() + + # Step - 5: Delete Product Bundle + pb.delete() + + # Step - 6: Again try to enable Maintain Stock + item.is_stock_item = 1 + item.save() + item.reload() + self.assertEqual(item.is_stock_item, 1) + def set_item_variant_settings(fields): doc = frappe.get_doc("Item Variant Settings") From 3b79e24c7edf77c471b89da2c349ebe47c66d572 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 14 Sep 2022 12:55:49 +0530 Subject: [PATCH 157/898] fix: always set default expense account in company --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index dc698886a014..490504a7c9d8 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -389,6 +389,7 @@ def set_default_accounts(self): "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", } if self.enable_perpetual_inventory: @@ -398,7 +399,6 @@ def set_default_accounts(self): "default_inventory_account": "Stock", "stock_adjustment_account": "Stock Adjustment", "expenses_included_in_valuation": "Expenses Included In Valuation", - "default_expense_account": "Cost of Goods Sold", } ) From b9a249918a644b48896b3aecaf9d38e8bf6b4519 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Wed, 14 Sep 2022 11:55:03 +0530 Subject: [PATCH 158/898] fix: pending accrual entries (cherry picked from commit f2209045f8e0deceaa4e743c40ac2fe037d85e3a) --- .../doctype/loan_repayment/loan_repayment.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 9b7603fc3b37..a69660dabf3d 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -516,6 +516,8 @@ def get_accrued_interest_entries(against_loan, posting_date=None): if not posting_date: posting_date = getdate() + precision = cint(frappe.db.get_default("currency_precision")) or 2 + unpaid_accrued_entries = frappe.db.sql( """ SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount, @@ -536,6 +538,13 @@ def get_accrued_interest_entries(against_loan, posting_date=None): as_dict=1, ) + # Skip entries with zero interest amount & payable principal amount + unpaid_accrued_entries = [ + d + for d in unpaid_accrued_entries + if flt(d.interest_amount, precision) > 0 or flt(d.payable_principal_amount, precision) > 0 + ] + return unpaid_accrued_entries From 153ef5f164d2b361ac5c428076fcc8c7bba8aee2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 14 Sep 2022 17:20:19 +0530 Subject: [PATCH 159/898] fix: test cases --- .../stock_reconciliation/test_stock_reconciliation.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 4e76ae781f9e..7b984d384750 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -729,15 +729,19 @@ def create_stock_reconciliation(**args): sr.expense_account = args.expense_account or ( ( frappe.get_cached_value("Company", sr.company, "stock_adjustment_account") - or "Stock Adjustment - _TC" + or frappe.get_cached_value( + "Account", {"account_type": "Stock Adjustment", "company": sr.company}, "name" + ) ) if frappe.get_all("Stock Ledger Entry", {"company": sr.company}) - else "Temporary Opening - _TC" + else frappe.get_cached_value( + "Account", {"account_type": "Temporary", "company": sr.company}, "name" + ) ) sr.cost_center = ( args.cost_center or frappe.get_cached_value("Company", sr.company, "cost_center") - or "_Test Cost Center - _TC" + or frappe.get_cached_value("Cost Center", filters={"is_group": 0, "company": sr.company}) ) sr.append( From f126e88e5e7b2bf71be4a22eb08fe0aba0083780 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 14 Sep 2022 18:31:15 +0530 Subject: [PATCH 160/898] fix: test cases --- .../assets/doctype/asset_repair/test_asset_repair.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 2786349f7b68..6e06f52ac65d 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -129,6 +129,18 @@ def test_purchase_invoice(self): def test_gl_entries_with_perpetual_inventory(self): set_depreciation_settings_in_company(company="_Test Company with perpetual inventory") + asset_category = frappe.get_doc("Asset Category", "Computers") + asset_category.append( + "accounts", + { + "company_name": "_Test Company with perpetual inventory", + "fixed_asset_account": "_Test Fixed Asset - TCP1", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1", + "depreciation_expense_account": "_Test Depreciations - TCP1", + }, + ) + asset_category.save() + asset_repair = create_asset_repair( capitalize_repair_cost=1, stock_consumption=1, From fed23536dcd36f9418d004913b38fe23e62cc7d6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 19:21:04 +0530 Subject: [PATCH 161/898] fix: correct sql output format in CRM patch (backport #32213) (#32214) fix: correct sql output format in CRM patch (#32213) (cherry picked from commit 97977cdb4ba42a1a20a6e747cc5586ae92e8954b) Co-authored-by: Ankush Menat --- .../v14_0/migrate_existing_lead_notes_as_per_the_new_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py index 032aeccc23d7..ec72527552c0 100644 --- a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py +++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py @@ -12,7 +12,7 @@ def execute(): frappe.qb.from_(dt) .select(dt.name, dt.notes, dt.modified_by, dt.modified) .where(dt.notes.isnotnull() & dt.notes != "") - ).run() + ).run(as_dict=True) for d in records: if strip_html(cstr(d.notes)).strip(): From 64aad8868414864d089bc552a196c843aae1c02c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 19:21:40 +0530 Subject: [PATCH 162/898] fix: correct sql output format in CRM patch (backport #32213) (#32215) fix: correct sql output format in CRM patch (#32213) (cherry picked from commit 97977cdb4ba42a1a20a6e747cc5586ae92e8954b) Co-authored-by: Ankush Menat --- .../v14_0/migrate_existing_lead_notes_as_per_the_new_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py index 032aeccc23d7..ec72527552c0 100644 --- a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py +++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py @@ -12,7 +12,7 @@ def execute(): frappe.qb.from_(dt) .select(dt.name, dt.notes, dt.modified_by, dt.modified) .where(dt.notes.isnotnull() & dt.notes != "") - ).run() + ).run(as_dict=True) for d in records: if strip_html(cstr(d.notes)).strip(): From 14e5f239ac2af01074973d341c2f0ee57c99901f Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 14 Sep 2022 13:52:36 +0000 Subject: [PATCH 163/898] chore(release): Bumped to Version 14.1.2 ## [14.1.2](https://github.com/frappe/erpnext/compare/v14.1.1...v14.1.2) (2022-09-14) ### Bug Fixes * correct sql output format in CRM patch (backport [#32213](https://github.com/frappe/erpnext/issues/32213)) ([#32214](https://github.com/frappe/erpnext/issues/32214)) ([fed2353](https://github.com/frappe/erpnext/commit/fed23536dcd36f9418d004913b38fe23e62cc7d6)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6ce0f9cd2f61..db168818a8c1 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.1.1" +__version__ = "14.1.2" def get_default_company(user=None): From f752822bb34d1c41fcc40dfeb2604ce140e25d9b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 15 Sep 2022 12:09:18 +0530 Subject: [PATCH 164/898] fix: abbreviation issue on renaming cost center (cherry picked from commit af21a11e1e3b2e372f19190f9854a26d3e8488bd) --- erpnext/accounts/doctype/account/account.py | 2 +- .../accounts/doctype/cost_center/cost_center.py | 2 +- erpnext/accounts/utils.py | 14 ++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 2610c8655ef5..9dff1168fded 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -37,7 +37,7 @@ def onload(self): def autoname(self): from erpnext.accounts.utils import get_autoname_with_number - self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company) + self.name = get_autoname_with_number(self.account_number, self.account_name, self.company) def validate(self): from erpnext.accounts.utils import validate_field_number diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 31055c3fb420..e8b34bbf034c 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -16,7 +16,7 @@ def autoname(self): from erpnext.accounts.utils import get_autoname_with_number self.name = get_autoname_with_number( - self.cost_center_number, self.cost_center_name, None, self.company + self.cost_center_number, self.cost_center_name, self.company ) def validate(self): diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f61e8ac960bc..c5eb7d8733f6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1037,7 +1037,7 @@ def update_cost_center(docname, cost_center_name, cost_center_number, company, m frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip()) - new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company) + new_name = get_autoname_with_number(cost_center_number, cost_center_name, company) if docname != new_name: frappe.rename_doc("Cost Center", docname, new_name, force=1, merge=merge) return new_name @@ -1060,16 +1060,14 @@ def validate_field_number(doctype_name, docname, number_value, company, field_na ) -def get_autoname_with_number(number_value, doc_title, name, company): +def get_autoname_with_number(number_value, doc_title, company): """append title with prefix as number and suffix as company's abbreviation separated by '-'""" - if name: - name_split = name.split("-") - parts = [doc_title.strip(), name_split[len(name_split) - 1].strip()] - else: - abbr = frappe.get_cached_value("Company", company, ["abbr"], as_dict=True) - parts = [doc_title.strip(), abbr.abbr] + company_abbr = frappe.get_cached_value("Company", company, "abbr") + parts = [doc_title.strip(), company_abbr] + if cstr(number_value).strip(): parts.insert(0, cstr(number_value).strip()) + return " - ".join(parts) From f370c7b50b0d8902c031b52ad4154bc896613915 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Sep 2022 11:47:10 +0530 Subject: [PATCH 165/898] fix: consider posting time for internal transfer PO (cherry picked from commit cb763938dced524f69f28341c57d4cf191f6dacd) --- erpnext/controllers/buying_controller.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 584266d53b18..5659ad0aa9b3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -6,6 +6,7 @@ from frappe import ValidationError, _, msgprint from frappe.contacts.doctype.address.address import get_address_display from frappe.utils import cint, cstr, flt, getdate +from frappe.utils.data import nowtime from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.party import get_party_details @@ -289,12 +290,16 @@ def set_incoming_rate(self): # Get outgoing rate based on original item cost based on valuation method if not d.get(frappe.scrub(ref_doctype)): + posting_time = self.get("posting_time") + if not posting_time and self.doctype == "Purchase Order": + posting_time = nowtime() + outgoing_rate = get_incoming_rate( { "item_code": d.item_code, "warehouse": d.get("from_warehouse"), "posting_date": self.get("posting_date") or self.get("transation_date"), - "posting_time": self.get("posting_time"), + "posting_time": posting_time, "qty": -1 * flt(d.get("stock_qty")), "serial_no": d.get("serial_no"), "batch_no": d.get("batch_no"), From 442f54a988e209d6d0ecfefd3cb5e3d1d8dea48a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 15 Sep 2022 11:27:35 +0530 Subject: [PATCH 166/898] fix: No permission to read doctype (cherry picked from commit c0da948a4ef9f5fb1dd5764ee1908bd6f0475c7e) --- .../bank_clearance_summary/bank_clearance_summary.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 9d2deea523bd..449ebdcd9240 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -22,8 +22,7 @@ def get_columns(): { "label": _("Payment Document Type"), "fieldname": "payment_document_type", - "fieldtype": "Link", - "options": "Doctype", + "fieldtype": "Data", "width": 130, }, { @@ -33,15 +32,15 @@ def get_columns(): "options": "payment_document_type", "width": 140, }, - {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100}, + {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120}, {"label": _("Cheque/Reference No"), "fieldname": "cheque_no", "width": 120}, - {"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 100}, + {"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 120}, { "label": _("Against Account"), "fieldname": "against", "fieldtype": "Link", "options": "Account", - "width": 170, + "width": 200, }, {"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 120}, ] From 17ea6cc5a585464bf81010997a3bbd08b4aebba7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 15 Sep 2022 13:11:53 +0530 Subject: [PATCH 167/898] fix: create dunning from sales invoice (cherry picked from commit 29db084dc314979e07288e34b43ac09292b5ff5b) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 608f0828feee..e51938b27f5d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2429,7 +2429,6 @@ def set_missing_values(source, target): target.closing_text = letter_text.get("closing_text") target.language = letter_text.get("language") amounts = calculate_interest_and_amount( - target.posting_date, target.outstanding_amount, target.rate_of_interest, target.dunning_fee, From 5cab0aa1d7c620cd6a9f0c7ac5eab83bebd34461 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 16 Sep 2022 11:44:25 +0530 Subject: [PATCH 168/898] fix: disable cwip in asset repair tests --- erpnext/assets/doctype/asset_repair/test_asset_repair.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 6e06f52ac65d..6599c078231d 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -130,6 +130,7 @@ def test_gl_entries_with_perpetual_inventory(self): set_depreciation_settings_in_company(company="_Test Company with perpetual inventory") asset_category = frappe.get_doc("Asset Category", "Computers") + asset_category.enable_cwip_accounting = 0 asset_category.append( "accounts", { From b700a0be1f6872535ae779afffc892470fb6eede Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 16 Sep 2022 09:45:12 +0530 Subject: [PATCH 169/898] refactor: rewrite Production Plan queries in QB (cherry picked from commit b8cf3b4c776534fe73cb94a9d67feb1a90c624a6) --- .../production_plan/production_plan.py | 472 +++++++++++------- 1 file changed, 287 insertions(+), 185 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 66d458bf7501..aa5c50f30f21 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -8,6 +8,7 @@ import frappe from frappe import _, msgprint from frappe.model.document import Document +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import ( add_days, ceil, @@ -20,6 +21,7 @@ nowdate, ) from frappe.utils.csvutils import build_csv_response +from pypika.terms import ExistsCriterion from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_children from erpnext.manufacturing.doctype.bom.bom import validate_bom_no @@ -100,39 +102,46 @@ def add_so_in_table(self, open_so): @frappe.whitelist() def get_pending_material_requests(self): """Pull Material Requests that are pending based on criteria selected""" - mr_filter = item_filter = "" + + bom = frappe.qb.DocType("BOM") + mr = frappe.qb.DocType("Material Request") + mr_item = frappe.qb.DocType("Material Request Item") + + pending_mr_query = ( + frappe.qb.from_(mr) + .from_(mr_item) + .select(mr.name, mr.transaction_date) + .distinct() + .where( + (mr_item.parent == mr.name) + & (mr.material_request_type == "Manufacture") + & (mr.docstatus == 1) + & (mr.status != "Stopped") + & (mr.company == self.company) + & (mr_item.qty > IfNull(mr_item.ordered_qty, 0)) + & ( + ExistsCriterion( + frappe.qb.from_(bom) + .select(bom.name) + .where((bom.item == mr_item.item_code) & (bom.is_active == 1)) + ) + ) + ) + ) + if self.from_date: - mr_filter += " and mr.transaction_date >= %(from_date)s" + pending_mr_query = pending_mr_query.where(mr.transaction_date >= self.from_date) + if self.to_date: - mr_filter += " and mr.transaction_date <= %(to_date)s" + pending_mr_query = pending_mr_query.where(mr.transaction_date <= self.to_date) + if self.warehouse: - mr_filter += " and mr_item.warehouse = %(warehouse)s" + pending_mr_query = pending_mr_query.where(mr_item.warehouse == self.warehouse) if self.item_code: - item_filter += " and mr_item.item_code = %(item)s" - - pending_mr = frappe.db.sql( - """ - select distinct mr.name, mr.transaction_date - from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item - where mr_item.parent = mr.name - and mr.material_request_type = "Manufacture" - and mr.docstatus = 1 and mr.status != "Stopped" and mr.company = %(company)s - and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1} - and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code - and bom.is_active = 1)) - """.format( - mr_filter, item_filter - ), - { - "from_date": self.from_date, - "to_date": self.to_date, - "warehouse": self.warehouse, - "item": self.item_code, - "company": self.company, - }, - as_dict=1, - ) + pending_mr_query = pending_mr_query.where(mr_item.item_code == self.item_code) + + pending_mr = pending_mr_query.run(as_dict=True) self.add_mr_in_table(pending_mr) @@ -160,16 +169,17 @@ def get_so_mr_list(self, field, table): so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)] return so_mr_list - def get_bom_item(self): + def get_bom_item_condition(self): """Check if Item or if its Template has a BOM.""" - bom_item = None + bom_item_condition = None has_bom = frappe.db.exists({"doctype": "BOM", "item": self.item_code, "docstatus": 1}) + if not has_bom: + bom = frappe.qb.DocType("BOM") template_item = frappe.db.get_value("Item", self.item_code, ["variant_of"]) - bom_item = ( - "bom.item = {0}".format(frappe.db.escape(template_item)) if template_item else bom_item - ) - return bom_item + bom_item_condition = bom.item == template_item or None + + return bom_item_condition def get_so_items(self): # Check for empty table or empty rows @@ -178,46 +188,73 @@ def get_so_items(self): so_list = self.get_so_mr_list("sales_order", "sales_orders") - item_condition = "" - bom_item = "bom.item = so_item.item_code" + bom = frappe.qb.DocType("BOM") + so_item = frappe.qb.DocType("Sales Order Item") + + items_subquery = frappe.qb.from_(bom).select(bom.name).where(bom.is_active == 1) + items_query = ( + frappe.qb.from_(so_item) + .select( + so_item.parent, + so_item.item_code, + so_item.warehouse, + ((so_item.qty - so_item.work_order_qty) * so_item.conversion_factor).as_("pending_qty"), + so_item.description, + so_item.name, + ) + .distinct() + .where( + (so_item.parent.isin(so_list)) + & (so_item.docstatus == 1) + & (so_item.qty > so_item.work_order_qty) + ) + ) + if self.item_code and frappe.db.exists("Item", self.item_code): - bom_item = self.get_bom_item() or bom_item - item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code)) - - items = frappe.db.sql( - """ - select - distinct parent, item_code, warehouse, - (qty - work_order_qty) * conversion_factor as pending_qty, - description, name - from - `tabSales Order Item` so_item - where - parent in (%s) and docstatus = 1 and qty > work_order_qty - and exists (select name from `tabBOM` bom where %s - and bom.is_active = 1) %s""" - % (", ".join(["%s"] * len(so_list)), bom_item, item_condition), - tuple(so_list), - as_dict=1, + items_query = items_query.where(so_item.item_code == self.item_code) + items_subquery = items_subquery.where( + self.get_bom_item_condition() or bom.item == so_item.item_code + ) + + items_query = items_query.where(ExistsCriterion(items_subquery)) + + items = items_query.run(as_dict=True) + + pi = frappe.qb.DocType("Packed Item") + + packed_items_query = ( + frappe.qb.from_(so_item) + .from_(pi) + .select( + pi.parent, + pi.item_code, + pi.warehouse.as_("warehouse"), + (((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty).as_("pending_qty"), + pi.parent_item, + pi.description, + so_item.name, + ) + .distinct() + .where( + (so_item.parent == pi.parent) + & (so_item.docstatus == 1) + & (pi.parent_item == so_item.item_code) + & (so_item.parent.isin(so_list)) + & (so_item.qty > so_item.work_order_qty) + & ( + ExistsCriterion( + frappe.qb.from_(bom) + .select(bom.name) + .where((bom.item == pi.item_code) & (bom.is_active == 1)) + ) + ) + ) ) if self.item_code: - item_condition = " and so_item.item_code = {0}".format(frappe.db.escape(self.item_code)) - - packed_items = frappe.db.sql( - """select distinct pi.parent, pi.item_code, pi.warehouse as warehouse, - (((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty) - as pending_qty, pi.parent_item, pi.description, so_item.name - from `tabSales Order Item` so_item, `tabPacked Item` pi - where so_item.parent = pi.parent and so_item.docstatus = 1 - and pi.parent_item = so_item.item_code - and so_item.parent in (%s) and so_item.qty > so_item.work_order_qty - and exists (select name from `tabBOM` bom where bom.item=pi.item_code - and bom.is_active = 1) %s""" - % (", ".join(["%s"] * len(so_list)), item_condition), - tuple(so_list), - as_dict=1, - ) + packed_items_query = packed_items_query.where(so_item.item_code == self.item_code) + + packed_items = packed_items_query.run(as_dict=True) self.add_items(items + packed_items) self.calculate_total_planned_qty() @@ -233,22 +270,39 @@ def get_mr_items(self): mr_list = self.get_so_mr_list("material_request", "material_requests") - item_condition = "" - if self.item_code: - item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code)) - - items = frappe.db.sql( - """select distinct parent, name, item_code, warehouse, description, - (qty - ordered_qty) * conversion_factor as pending_qty - from `tabMaterial Request Item` mr_item - where parent in (%s) and docstatus = 1 and qty > ordered_qty - and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code - and bom.is_active = 1) %s""" - % (", ".join(["%s"] * len(mr_list)), item_condition), - tuple(mr_list), - as_dict=1, + bom = frappe.qb.DocType("BOM") + mr_item = frappe.qb.DocType("Material Request Item") + + items_query = ( + frappe.qb.from_(mr_item) + .select( + mr_item.parent, + mr_item.name, + mr_item.item_code, + mr_item.warehouse, + mr_item.description, + ((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"), + ) + .distinct() + .where( + (mr_item.parent.isin(mr_list)) + & (mr_item.docstatus == 1) + & (mr_item.qty > mr_item.ordered_qty) + & ( + ExistsCriterion( + frappe.qb.from_(bom) + .select(bom.name) + .where((bom.item == mr_item.item_code) & (bom.is_active == 1)) + ) + ) + ) ) + if self.item_code: + items_query = items_query.where(mr_item.item_code == self.item_code) + + items = items_query.run(as_dict=True) + self.add_items(items) self.calculate_total_planned_qty() @@ -819,29 +873,45 @@ def download_raw_materials(doc, warehouses=None): def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1): - for d in frappe.db.sql( - """select bei.item_code, item.default_bom as bom, - ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name, - bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse, - item.default_material_request_type, item.min_order_qty, item_default.default_warehouse, - item.purchase_uom, item_uom.conversion_factor, item.safety_stock - from - `tabBOM Explosion Item` bei - JOIN `tabBOM` bom ON bom.name = bei.parent - JOIN `tabItem` item ON item.name = bei.item_code - LEFT JOIN `tabItem Default` item_default - ON item_default.parent = item.name and item_default.company=%s - LEFT JOIN `tabUOM Conversion Detail` item_uom - ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom - where - bei.docstatus < 2 - and bom.name=%s and item.is_stock_item in (1, {0}) - group by bei.item_code, bei.stock_uom""".format( - 0 if include_non_stock_items else 1 - ), - (planned_qty, company, bom_no), - as_dict=1, - ): + bei = frappe.qb.DocType("BOM Explosion Item") + bom = frappe.qb.DocType("BOM") + item = frappe.qb.DocType("Item") + item_default = frappe.qb.DocType("Item Default") + item_uom = frappe.qb.DocType("UOM Conversion Detail") + + data = ( + frappe.qb.from_(bei) + .join(bom) + .on(bom.name == bei.parent) + .join(item) + .on(item.name == bei.item_code) + .left_join(item_default) + .on((item_default.parent == item.name) & (item_default.company == company)) + .left_join(item_uom) + .on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom)) + .select( + (IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"), + item.item_name, + bei.description, + bei.stock_uom, + item.min_order_qty, + bei.source_warehouse, + item.default_material_request_type, + item.min_order_qty, + item_default.default_warehouse, + item.purchase_uom, + item_uom.conversion_factor, + item.safety_stock, + ) + .where( + (bei.docstatus < 2) + & (bom.name == bom_no) + & (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1) + ) + .groupby(bei.item_code, bei.stock_uom) + ).run(as_dict=True) + + for d in data: if not d.conversion_factor and d.purchase_uom: d.conversion_factor = get_uom_conversion_factor(d.item_code, d.purchase_uom) item_details.setdefault(d.get("item_code"), d) @@ -866,33 +936,47 @@ def get_subitems( parent_qty, planned_qty=1, ): - items = frappe.db.sql( - """ - SELECT - bom_item.item_code, default_material_request_type, item.item_name, - ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty, - item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse, - item.default_bom as default_bom, bom_item.description as description, - bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, item.safety_stock as safety_stock, - item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor - FROM - `tabBOM Item` bom_item - JOIN `tabBOM` bom ON bom.name = bom_item.parent - JOIN `tabItem` item ON bom_item.item_code = item.name - LEFT JOIN `tabItem Default` item_default - ON item.name = item_default.parent and item_default.company = %(company)s - LEFT JOIN `tabUOM Conversion Detail` item_uom - ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom - where - bom.name = %(bom)s - and bom_item.docstatus < 2 - and item.is_stock_item in (1, {0}) - group by bom_item.item_code""".format( - 0 if include_non_stock_items else 1 - ), - {"bom": bom_no, "parent_qty": parent_qty, "planned_qty": planned_qty, "company": company}, - as_dict=1, - ) + bom_item = frappe.qb.DocType("BOM Item") + bom = frappe.qb.DocType("BOM") + item = frappe.qb.DocType("Item") + item_default = frappe.qb.DocType("Item Default") + item_uom = frappe.qb.DocType("UOM Conversion Detail") + + items = ( + frappe.qb.from_(bom_item) + .join(bom) + .on(bom.name == bom_item.parent) + .join(item) + .on(bom_item.item_code == item.name) + .left_join(item_default) + .on((item.name == item_default.parent) & (item_default.company == company)) + .left_join(item_uom) + .on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom)) + .select( + bom_item.item_code, + item.default_material_request_type, + item.item_name, + IfNull(parent_qty * Sum(bom_item.stock_qty / IfNull(bom.quantity, 1)) * planned_qty, 0).as_( + "qty" + ), + item.is_sub_contracted_item.as_("is_sub_contracted"), + bom_item.source_warehouse, + item.default_bom.as_("default_bom"), + bom_item.description.as_("description"), + bom_item.stock_uom.as_("stock_uom"), + item.min_order_qty.as_("min_order_qty"), + item.safety_stock.as_("safety_stock"), + item_default.default_warehouse, + item.purchase_uom, + item_uom.conversion_factor, + ) + .where( + (bom.name == bom_no) + & (bom_item.docstatus < 2) + & (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1) + ) + .groupby(bom_item.item_code) + ).run(as_dict=True) for d in items: if not data.get("include_exploded_items") or not d.default_bom: @@ -980,48 +1064,69 @@ def get_material_request_items( def get_sales_orders(self): - so_filter = item_filter = "" - bom_item = "bom.item = so_item.item_code" + bom = frappe.qb.DocType("BOM") + pi = frappe.qb.DocType("Packed Item") + so = frappe.qb.DocType("Sales Order") + so_item = frappe.qb.DocType("Sales Order Item") + + open_so_subquery1 = frappe.qb.from_(bom).select(bom.name).where(bom.is_active == 1) + + open_so_subquery2 = ( + frappe.qb.from_(pi) + .select(pi.name) + .where( + (pi.parent == so.name) + & (pi.parent_item == so_item.item_code) + & ( + ExistsCriterion( + frappe.qb.from_(bom).select(bom.name).where((bom.item == pi.item_code) & (bom.is_active == 1)) + ) + ) + ) + ) + + open_so_query = ( + frappe.qb.from_(so) + .from_(so_item) + .select(so.name, so.transaction_date, so.customer, so.base_grand_total) + .distinct() + .where( + (so_item.parent == so.name) + & (so.docstatus == 1) + & (so.status.notin(["Stopped", "Closed"])) + & (so.company == self.company) + & (so_item.qty > so_item.work_order_qty) + ) + ) date_field_mapper = { - "from_date": (">=", "so.transaction_date"), - "to_date": ("<=", "so.transaction_date"), - "from_delivery_date": (">=", "so_item.delivery_date"), - "to_delivery_date": ("<=", "so_item.delivery_date"), + "from_date": self.from_date >= so.transaction_date, + "to_date": self.to_date <= so.transaction_date, + "from_delivery_date": self.from_delivery_date >= so_item.delivery_date, + "to_delivery_date": self.to_delivery_date <= so_item.delivery_date, } for field, value in date_field_mapper.items(): if self.get(field): - so_filter += f" and {value[1]} {value[0]} %({field})s" + open_so_query = open_so_query.where(value) - for field in ["customer", "project", "sales_order_status"]: + for field in ("customer", "project", "sales_order_status"): if self.get(field): so_field = "status" if field == "sales_order_status" else field - so_filter += f" and so.{so_field} = %({field})s" + open_so_query = open_so_query.where(so[so_field] == self.get(field)) if self.item_code and frappe.db.exists("Item", self.item_code): - bom_item = self.get_bom_item() or bom_item - item_filter += " and so_item.item_code = %(item_code)s" - - open_so = frappe.db.sql( - f""" - select distinct so.name, so.transaction_date, so.customer, so.base_grand_total - from `tabSales Order` so, `tabSales Order Item` so_item - where so_item.parent = so.name - and so.docstatus = 1 and so.status not in ('Stopped', 'Closed') - and so.company = %(company)s - and so_item.qty > so_item.work_order_qty {so_filter} {item_filter} - and (exists (select name from `tabBOM` bom where {bom_item} - and bom.is_active = 1) - or exists (select name from `tabPacked Item` pi - where pi.parent = so.name and pi.parent_item = so_item.item_code - and exists (select name from `tabBOM` bom where bom.item=pi.item_code - and bom.is_active = 1))) - """, - self.as_dict(), - as_dict=1, + open_so_query = open_so_query.where(so_item.item_code == self.item_code) + open_so_subquery1 = open_so_subquery1.where( + self.get_bom_item_condition() or bom.item == so_item.item_code + ) + + open_so_query = open_so_query.where( + (ExistsCriterion(open_so_subquery1) | ExistsCriterion(open_so_subquery2)) ) + open_so = open_so_query.run(as_dict=True) + return open_so @@ -1030,37 +1135,34 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): if isinstance(row, str): row = frappe._dict(json.loads(row)) - company = frappe.db.escape(company) - conditions, warehouse = "", "" + bin = frappe.qb.DocType("Bin") + wh = frappe.qb.DocType("Warehouse") + + subquery = frappe.qb.from_(wh).select(wh.name).where(wh.company == company) - conditions = " and warehouse in (select name from `tabWarehouse` where company = {0})".format( - company - ) if not all_warehouse: warehouse = for_warehouse or row.get("source_warehouse") or row.get("default_warehouse") if warehouse: lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - conditions = """ and warehouse in (select name from `tabWarehouse` - where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse and company = {2}) - """.format( - lft, rgt, company + subquery = subquery.where((wh.lft >= lft) & (wh.rgt <= rgt) & (wh.name == bin.warehouse)) + + query = ( + frappe.qb.from_(bin) + .select( + bin.warehouse, + IfNull(Sum(bin.projected_qty), 0).as_("projected_qty"), + IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"), + IfNull(Sum(bin.ordered_qty), 0).as_("ordered_qty"), + IfNull(Sum(bin.reserved_qty_for_production), 0).as_("reserved_qty_for_production"), + IfNull(Sum(bin.planned_qty), 0).as_("planned_qty"), ) - - return frappe.db.sql( - """ select ifnull(sum(projected_qty),0) as projected_qty, - ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty, - ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse, - ifnull(sum(planned_qty),0) as planned_qty - from `tabBin` where item_code = %(item_code)s {conditions} - group by item_code, warehouse - """.format( - conditions=conditions - ), - {"item_code": row["item_code"]}, - as_dict=1, + .where((bin.item_code == row["item_code"]) & (bin.warehouse.isin(subquery))) + .groupby(bin.item_code, bin.warehouse) ) + return query.run(as_dict=True) + @frappe.whitelist() def get_so_details(sales_order): From 5f3caf697560dc37537b4653669f0621ce2cb71b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 16 Sep 2022 14:39:39 +0530 Subject: [PATCH 170/898] fix: production plan pending-qty (cherry picked from commit 5be7d42dfd242a637a24501f12d0b855916f9e41) --- .../manufacturing/doctype/production_plan/production_plan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index aa5c50f30f21..f1d40c219cb1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -198,7 +198,9 @@ def get_so_items(self): so_item.parent, so_item.item_code, so_item.warehouse, - ((so_item.qty - so_item.work_order_qty) * so_item.conversion_factor).as_("pending_qty"), + ( + (so_item.qty - so_item.work_order_qty - so_item.delivered_qty) * so_item.conversion_factor + ).as_("pending_qty"), so_item.description, so_item.name, ) From ff789063614a65dab87ba57668816460100a76fe Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 16 Sep 2022 14:55:57 +0530 Subject: [PATCH 171/898] test: update test case for production plan pending-qty (cherry picked from commit bd6af7c6137d7bc111e2e51502e2e63cb44dcaa7) --- .../production_plan/test_production_plan.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 1d2d1bd9a841..60e63980724a 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -12,6 +12,7 @@ ) from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo +from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -610,15 +611,21 @@ def test_production_plan_pending_qty_with_sales_order(self): """ from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + make_stock_entry(item_code="_Test Item", target="Work In Progress - _TC", qty=2, basic_rate=100) make_stock_entry( - item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 - ) - make_stock_entry( - item_code="Raw Material Item 2", target="Work In Progress - _TC", qty=2, basic_rate=100 + item_code="_Test Item Home Desktop 100", target="Work In Progress - _TC", qty=4, basic_rate=100 ) - item = "Test Production Item 1" - so = make_sales_order(item_code=item, qty=1) + item = "_Test FG Item" + + make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=1) + + so = make_sales_order(item_code=item, qty=2) + + dn = make_delivery_note(so.name) + dn.items[0].qty = 1 + dn.save() + dn.submit() pln = create_production_plan( company=so.company, get_items_from="Sales Order", sales_order=so, skip_getting_mr_items=True From 436b7e3b70558dd0ec8907cbdfc7c6139dbab44f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 16 Sep 2022 15:23:10 +0530 Subject: [PATCH 172/898] fix: suggestion threshold label and rule was not working for other items with min and max amount (cherry picked from commit f5bd3fa952b9feb6a8f2cc1641e7f2f898fa74b9) --- .../doctype/pricing_rule/pricing_rule.json | 14 +++-- .../doctype/pricing_rule/pricing_rule.py | 8 ++- .../doctype/pricing_rule/test_pricing_rule.py | 62 +++++++++++++++++++ .../accounts/doctype/pricing_rule/utils.py | 56 +++++++++-------- erpnext/controllers/accounts_controller.py | 5 ++ erpnext/public/js/controllers/transaction.js | 20 ++++-- 6 files changed, 125 insertions(+), 40 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 99c5b34fa352..6e7ebd1414d8 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -176,7 +176,7 @@ }, { "collapsible": 1, - "depends_on": "eval:doc.apply_on != 'Transaction'", + "depends_on": "eval:doc.apply_on != 'Transaction' && !doc.mixed_conditions", "fieldname": "section_break_18", "fieldtype": "Section Break", "label": "Discount on Other Item" @@ -297,12 +297,12 @@ { "fieldname": "min_qty", "fieldtype": "Float", - "label": "Min Qty" + "label": "Min Qty (As Per Stock UOM)" }, { "fieldname": "max_qty", "fieldtype": "Float", - "label": "Max Qty" + "label": "Max Qty (As Per Stock UOM)" }, { "fieldname": "column_break_21", @@ -481,7 +481,7 @@ "description": "System will notify to increase or decrease quantity or amount ", "fieldname": "threshold_percentage", "fieldtype": "Percent", - "label": "Threshold for Suggestion" + "label": "Threshold for Suggestion (In Percentage)" }, { "description": "Higher the number, higher the priority", @@ -583,10 +583,11 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2021-08-06 15:10:04.219321", + "modified": "2022-09-16 16:00:38.356266", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -642,5 +643,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "title" -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 98e0a9b2158c..9af3188e476d 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -324,7 +324,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if isinstance(pricing_rule, str): pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule) - pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) + pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or [] if pricing_rule.get("suggestion"): continue @@ -337,7 +337,6 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: item_details.update( { - "apply_rule_on_other_items": json.dumps(pricing_rule.apply_rule_on_other_items), "price_or_product_discount": pricing_rule.price_or_product_discount, "apply_rule_on": ( frappe.scrub(pricing_rule.apply_rule_on_other) @@ -347,6 +346,9 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa } ) + if pricing_rule.apply_rule_on_other_items: + item_details["apply_rule_on_other_items"] = json.dumps(pricing_rule.apply_rule_on_other_items) + if pricing_rule.coupon_code_based == 1 and args.coupon_code == None: return item_details @@ -492,7 +494,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra ) if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"): - items = get_pricing_rule_items(pricing_rule) + items = get_pricing_rule_items(pricing_rule, other_items=True) item_details.apply_on = ( frappe.scrub(pricing_rule.apply_rule_on_other) if pricing_rule.apply_rule_on_other diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 3bd0cd2e837d..0a9db6b0f59a 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -766,6 +766,68 @@ def test_multiple_pricing_rules_with_min_qty(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2") + def test_pricing_rule_for_other_items_cond_with_amount(self): + item = make_item("Water Flask New") + other_item = make_item("Other Water Flask New") + make_item_price(item.name, "_Test Price List", 100) + make_item_price(other_item.name, "_Test Price List", 100) + + pricing_rule_record = { + "doctype": "Pricing Rule", + "title": "_Test Water Flask Rule", + "apply_on": "Item Code", + "apply_rule_on_other": "Item Code", + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "other_item_code": other_item.name, + "items": [ + { + "item_code": item.name, + } + ], + "selling": 1, + "currency": "INR", + "min_amt": 200, + "discount_percentage": 10, + "company": "_Test Company", + } + rule = frappe.get_doc(pricing_rule_record) + rule.insert() + + si = create_sales_invoice(do_not_save=True, item_code=item.name) + si.append( + "items", + { + "item_code": other_item.name, + "item_name": other_item.item_name, + "description": other_item.description, + "stock_uom": other_item.stock_uom, + "uom": other_item.stock_uom, + "cost_center": si.items[0].cost_center, + "expense_account": si.items[0].expense_account, + "warehouse": si.items[0].warehouse, + "conversion_factor": 1, + "qty": 1, + }, + ) + si.selling_price_list = "_Test Price List" + si.save() + + self.assertEqual(si.items[0].discount_percentage, 0) + self.assertEqual(si.items[1].discount_percentage, 0) + + si.items[0].qty = 2 + si.save() + + self.assertEqual(si.items[0].discount_percentage, 0) + self.assertEqual(si.items[0].stock_qty, 2) + self.assertEqual(si.items[0].amount, 200) + self.assertEqual(si.items[0].price_list_rate, 100) + self.assertEqual(si.items[1].discount_percentage, 10) + + si.delete() + rule.delete() + test_dependencies = ["Campaign"] diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 70926cfbd72e..1f29d732ba54 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -252,12 +252,6 @@ def filter_pricing_rules(args, pricing_rules, doc=None): stock_qty = flt(args.get("stock_qty")) amount = flt(args.get("price_list_rate")) * flt(args.get("qty")) - if pricing_rules[0].apply_rule_on_other: - field = frappe.scrub(pricing_rules[0].apply_rule_on_other) - - if field and pricing_rules[0].get("other_" + field) != args.get(field): - return - pr_doc = frappe.get_cached_doc("Pricing Rule", pricing_rules[0].name) if pricing_rules[0].mixed_conditions and doc: @@ -274,7 +268,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None): amount += data[1] if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc: - pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or [] + pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, args) or [] else: pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args) @@ -352,16 +346,14 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr if fieldname: msg = _( "If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item." - ).format( - type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description) - ) + ).format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.title)) if fieldname in ["min_amt", "max_amt"]: msg = _("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.").format( type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")), bold(item_code), - bold(args.rule_description), + bold(args.title), ) frappe.msgprint(msg) @@ -454,17 +446,29 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): return sum_qty, sum_amt, items -def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules): - items = get_pricing_rule_items(pr_doc) +def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item): + other_items = get_pricing_rule_items(pr_doc, other_items=True) + pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on")) + apply_on = frappe.scrub(pr_doc.get("apply_on")) + + items = [] + for d in pr_doc.get(pricing_rule_apply_on): + if apply_on == "item_group": + items.extend(get_child_item_groups(d.get(apply_on))) + else: + items.append(d.get(apply_on)) for row in doc.items: - if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items: - pricing_rules = filter_pricing_rules_for_qty_amount( - row.get("stock_qty"), row.get("amount"), pricing_rules, row - ) + if row.get(apply_on) in items: + if not row.get("qty"): + continue + + stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0) + amount = stock_qty * (row.get("price_list_rate") or row.get("rate")) + pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row) if pricing_rules and pricing_rules[0]: - pricing_rules[0].apply_rule_on_other_items = items + pricing_rules[0].apply_rule_on_other_items = other_items return pricing_rules @@ -658,21 +662,21 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values doc.append("items", args) -def get_pricing_rule_items(pr_doc): +def get_pricing_rule_items(pr_doc, other_items=False) -> list: apply_on_data = [] apply_on = frappe.scrub(pr_doc.get("apply_on")) pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on")) - for d in pr_doc.get(pricing_rule_apply_on): - if apply_on == "item_group": - apply_on_data.extend(get_child_item_groups(d.get(apply_on))) - else: - apply_on_data.append(d.get(apply_on)) - - if pr_doc.apply_rule_on_other: + if pr_doc.apply_rule_on_other and other_items: apply_on = frappe.scrub(pr_doc.apply_rule_on_other) apply_on_data.append(pr_doc.get("other_" + apply_on)) + else: + for d in pr_doc.get(pricing_rule_apply_on): + if apply_on == "item_group": + apply_on_data.extend(get_child_item_groups(d.get(apply_on))) + else: + apply_on_data.append(d.get(apply_on)) return list(set(apply_on_data)) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 938de63f33ac..8686cb5cc094 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -572,6 +572,11 @@ def apply_pricing_rule_on_items(self, item, pricing_rule_args): # if user changed the discount percentage then set user's discount percentage ? if pricing_rule_args.get("price_or_product_discount") == "Price": item.set("pricing_rules", pricing_rule_args.get("pricing_rules")) + if pricing_rule_args.get("apply_rule_on_other_items"): + other_items = json.loads(pricing_rule_args.get("apply_rule_on_other_items")) + if other_items and item.item_code not in other_items: + return + item.set("discount_percentage", pricing_rule_args.get("discount_percentage")) item.set("discount_amount", pricing_rule_args.get("discount_amount")) if pricing_rule_args.get("pricing_rule_for") == "Rate": diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c0a8c9e088c7..c17610b58a21 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1492,7 +1492,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe frappe.model.set_value(child.doctype, child.name, "rate", value); } + if (key === "pricing_rules") { + frappe.model.set_value(child.doctype, child.name, key, value); + } + if (key !== "free_item_data") { + if (child.apply_rule_on_other_items && JSON.parse(child.apply_rule_on_other_items).length) { + if (!in_list(JSON.parse(child.apply_rule_on_other_items), child.item_code)) { + continue; + } + } + frappe.model.set_value(child.doctype, child.name, key, value); } } @@ -1510,11 +1520,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.remove_pricing_rule(frappe.get_doc(child.doctype, child.name)); } - if (child.free_item_data.length > 0) { + if (child.free_item_data && child.free_item_data.length > 0) { this.apply_product_discount(child); } - if (child.apply_rule_on_other_items) { + if (child.apply_rule_on_other_items && JSON.parse(child.apply_rule_on_other_items).length) { items_rule_dict[child.name] = child; } } @@ -1530,11 +1540,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe for(var k in args) { let data = args[k]; - if (data && data.apply_rule_on_other_items) { + if (data && data.apply_rule_on_other_items && JSON.parse(data.apply_rule_on_other_items)) { me.frm.doc.items.forEach(d => { - if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) { + if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on])) { for(var k in data) { - if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'price' || k === 'pricing_rules')) { + if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'Price' || k === 'pricing_rules')) { frappe.model.set_value(d.doctype, d.name, k, data[k]); } } From a15f0d427c76f3ba01aec854ea010f1996f09dc7 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sat, 17 Sep 2022 14:29:42 +0530 Subject: [PATCH 173/898] fix: `sco_rm_detail` in Stock Entry (cherry picked from commit 2f97370b8e4dbef05d8431255e352dd15972519a) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 76bba8af6464..75e8c6a8171d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2606,6 +2606,7 @@ def post_process(source, target): "uom": item.stock_uom, "stock_uom": item.stock_uom, "conversion_factor": 1, + "sco_rm_detail": item.name, }, ) From cbaffb4858fee0678d823d47d4e3a3e89068ece6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 16 Sep 2022 22:44:23 +0530 Subject: [PATCH 174/898] fix: Parent Level project linkning on creating PO from project (cherry picked from commit 93e134aab028aaadb0d2fb66d25e3a4d5fa89286) --- erpnext/projects/doctype/project/project.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 4f19bbd51636..c48ed9180248 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -152,6 +152,7 @@ function open_form(frm, doctype, child_doctype, parentfield) { new_child_doc.parentfield = parentfield; new_child_doc.parenttype = doctype; new_doc[parentfield] = [new_child_doc]; + new_doc.project = frm.doc.name; frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); From 908944b68bd75c88991011e747e02e66b91a2b78 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sat, 17 Sep 2022 15:58:33 +0530 Subject: [PATCH 175/898] fix: make `po_detail` or `sco_rm_detail` mandatory for SE `Send to Subcontractor` (cherry picked from commit b90875575c20b05027aa1f9718db69e0d60ad133) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 75e8c6a8171d..62f2acd2fd7f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -919,6 +919,16 @@ def validate_subcontract_order(self): ) if order_rm_detail: se_item.db_set(self.subcontract_data.rm_detail_field, order_rm_detail) + else: + if not se_item.allow_alternative_item: + frappe.throw( + _("Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}").format( + se_item.idx, + se_item.item_code, + self.subcontract_data.order_doctype, + self.get(self.subcontract_data.order_field), + ) + ) elif backflush_raw_materials_based_on == "Material Transferred for Subcontract": for row in self.items: if not row.subcontracted_item: From c98413c9818df5f41231b23ba6bb86b70a8581b8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 16 Sep 2022 16:20:35 +0530 Subject: [PATCH 176/898] fix: use default supplier currency if default supplier is enabled (cherry picked from commit 77fdc37cb75d465a7a5297fc89bba31b8193ebeb) --- erpnext/selling/doctype/sales_order/sales_order.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 09a9652cca6e..25806d6ed869 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -880,6 +880,9 @@ def get_events(start, end, filters=None): @frappe.whitelist() def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None): """Creates Purchase Order for each Supplier. Returns a list of doc objects.""" + + from erpnext.setup.utils import get_exchange_rate + if not selected_items: return @@ -888,6 +891,15 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t def set_missing_values(source, target): target.supplier = supplier + target.currency = frappe.db.get_value( + "Supplier", filters={"name": supplier}, fieldname=["default_currency"] + ) + company_currency = frappe.db.get_value( + "Company", filters={"name": target.company}, fieldname=["default_currency"] + ) + + target.conversion_rate = get_exchange_rate(target.currency, company_currency, args="for_buying") + target.apply_discount_on = "" target.additional_discount_percentage = 0.0 target.discount_amount = 0.0 From ff210c73eb44bef4e89a10826f3d5415f56b56d0 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 19 Sep 2022 18:47:46 +0530 Subject: [PATCH 177/898] fix: `po_detail` or `sco_rm_detail` not getting set while while mapping SE (cherry picked from commit 3a9c08e7c9c2647c5fe67adbf6061c127e61b276) --- .../doctype/purchase_order/purchase_order.js | 121 +-------------- .../controllers/subcontracting_controller.py | 139 +++++++++++------- .../stock/doctype/stock_entry/stock_entry.js | 8 +- .../stock/doctype/stock_entry/stock_entry.py | 51 ++----- .../subcontracting_order.js | 10 -- 5 files changed, 99 insertions(+), 230 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index fbb42fe2f64e..fc99d776d4a5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -295,131 +295,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e } make_stock_entry() { - var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }); - var me = this; - - if(items.length >= 1){ - me.raw_material_data = []; - me.show_dialog = 1; - let title = __('Transfer Material to Supplier'); - let fields = [ - {fieldtype:'Section Break', label: __('Raw Materials')}, - {fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'), - fields: [ - { - fieldtype:'Data', - fieldname:'item_code', - label: __('Item'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Data', - fieldname:'rm_item_code', - label: __('Raw Material'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'qty', - label: __('Quantity'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Data', - read_only:1, - fieldname:'warehouse', - label: __('Reserve Warehouse'), - in_list_view:1 - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'rate', - label: __('Rate'), - hidden:1 - }, - { - fieldtype:'Float', - read_only:1, - fieldname:'amount', - label: __('Amount'), - hidden:1 - }, - { - fieldtype:'Link', - read_only:1, - fieldname:'uom', - label: __('UOM'), - hidden:1 - } - ], - data: me.raw_material_data, - get_data: function() { - return me.raw_material_data; - } - } - ] - - me.dialog = new frappe.ui.Dialog({ - title: title, fields: fields - }); - - if (me.frm.doc['supplied_items']) { - me.frm.doc['supplied_items'].forEach((item, index) => { - if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) { - me.raw_material_data.push ({ - 'name':item.name, - 'item_code': item.main_item_code, - 'rm_item_code': item.rm_item_code, - 'item_name': item.rm_item_code, - 'qty': item.required_qty - item.supplied_qty, - 'warehouse':item.reserve_warehouse, - 'rate':item.rate, - 'amount':item.amount, - 'stock_uom':item.stock_uom - }); - me.dialog.fields_dict.sub_con_rm_items.grid.refresh(); - } - }) - } - - me.dialog.get_field('sub_con_rm_items').check_all_rows() - - me.dialog.show() - this.dialog.set_primary_action(__('Transfer'), function() { - me.values = me.dialog.get_values(); - if(me.values) { - me.values.sub_con_rm_items.map((row,i) => { - if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) { - let row_id = i+1; - frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id])); - } - }) - me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children()) - me.dialog.hide() - } - }); - } - - me.dialog.get_close_btn().on('click', () => { - me.dialog.hide(); - }); - - } - - _make_rm_stock_entry(rm_items) { frappe.call({ method:"erpnext.controllers.subcontracting_controller.make_rm_stock_entry", args: { subcontract_order: cur_frm.doc.name, - rm_items: rm_items, order_doctype: cur_frm.doc.doctype - } - , + }, callback: function(r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index bbd950ed37ad..202a880750e1 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -770,7 +770,7 @@ def get_item_details(items): item = frappe.qb.DocType("Item") item_list = ( frappe.qb.from_(item) - .select(item.item_code, item.description, item.allow_alternative_item) + .select(item.item_code, item.item_name, item.description, item.allow_alternative_item) .where(item.name.isin(items)) .run(as_dict=True) ) @@ -783,68 +783,93 @@ def get_item_details(items): @frappe.whitelist() -def make_rm_stock_entry(subcontract_order, rm_items, order_doctype="Subcontracting Order"): - rm_items_list = rm_items +def make_rm_stock_entry( + subcontract_order, rm_items=None, order_doctype="Subcontracting Order", target_doc=None +): + if subcontract_order: + subcontract_order = frappe.get_doc(order_doctype, subcontract_order) - if isinstance(rm_items, str): - rm_items_list = json.loads(rm_items) - elif not rm_items: - frappe.throw(_("No Items available for transfer")) + if not rm_items: + if not subcontract_order.supplied_items: + frappe.throw(_("No item available for transfer.")) - if rm_items_list: - fg_items = list(set(item["item_code"] for item in rm_items_list)) - else: - frappe.throw(_("No Items selected for transfer")) + rm_items = subcontract_order.supplied_items - if subcontract_order: - subcontract_order = frappe.get_doc(order_doctype, subcontract_order) + fg_item_code_list = list( + set(item.get("main_item_code") or item.get("item_code") for item in rm_items) + ) - if fg_items: - items = tuple(set(item["rm_item_code"] for item in rm_items_list)) - item_wh = get_item_details(items) + if fg_item_code_list: + rm_item_code_list = tuple(set(item.get("rm_item_code") for item in rm_items)) + item_wh = get_item_details(rm_item_code_list) - stock_entry = frappe.new_doc("Stock Entry") - stock_entry.purpose = "Send to Subcontractor" - if order_doctype == "Purchase Order": - stock_entry.purchase_order = subcontract_order.name - else: - stock_entry.subcontracting_order = subcontract_order.name - stock_entry.supplier = subcontract_order.supplier - stock_entry.supplier_name = subcontract_order.supplier_name - stock_entry.supplier_address = subcontract_order.supplier_address - stock_entry.address_display = subcontract_order.address_display - stock_entry.company = subcontract_order.company - stock_entry.to_warehouse = subcontract_order.supplier_warehouse - stock_entry.set_stock_entry_type() - - if order_doctype == "Purchase Order": - rm_detail_field = "po_detail" - else: - rm_detail_field = "sco_rm_detail" - - for item_code in fg_items: - for rm_item_data in rm_items_list: - if rm_item_data["item_code"] == item_code: - rm_item_code = rm_item_data["rm_item_code"] - items_dict = { - rm_item_code: { - rm_detail_field: rm_item_data.get("name"), - "item_name": rm_item_data["item_name"], - "description": item_wh.get(rm_item_code, {}).get("description", ""), - "qty": rm_item_data["qty"], - "from_warehouse": rm_item_data["warehouse"], - "stock_uom": rm_item_data["stock_uom"], - "serial_no": rm_item_data.get("serial_no"), - "batch_no": rm_item_data.get("batch_no"), - "main_item_code": rm_item_data["item_code"], - "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), + field_no_map, rm_detail_field = "purchase_order", "sco_rm_detail" + if order_doctype == "Purchase Order": + field_no_map, rm_detail_field = "subcontracting_order", "po_detail" + + if target_doc and target_doc.get("items"): + target_doc.items = [] + + stock_entry = get_mapped_doc( + order_doctype, + subcontract_order.name, + { + order_doctype: { + "doctype": "Stock Entry", + "field_map": { + "to_warehouse": "supplier_warehouse", + }, + "field_no_map": [field_no_map], + "validation": { + "docstatus": ["=", 1], + }, + }, + }, + target_doc, + ignore_child_tables=True, + ) + + stock_entry.purpose = "Send to Subcontractor" + + if order_doctype == "Purchase Order": + stock_entry.purchase_order = subcontract_order.name + else: + stock_entry.subcontracting_order = subcontract_order.name + + stock_entry.set_stock_entry_type() + + for fg_item_code in fg_item_code_list: + for rm_item in rm_items: + + if rm_item.get("main_item_code") or rm_item.get("item_code") == fg_item_code: + rm_item_code = rm_item.get("rm_item_code") + + items_dict = { + rm_item_code: { + rm_detail_field: rm_item.get("name"), + "item_name": rm_item.get("item_name") + or item_wh.get(rm_item_code, {}).get("item_name", ""), + "description": item_wh.get(rm_item_code, {}).get("description", ""), + "qty": rm_item.get("qty") + or max(rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0), + "from_warehouse": rm_item.get("warehouse") or rm_item.get("reserve_warehouse"), + "to_warehouse": subcontract_order.supplier_warehouse, + "stock_uom": rm_item.get("stock_uom"), + "serial_no": rm_item.get("serial_no"), + "batch_no": rm_item.get("batch_no"), + "main_item_code": fg_item_code, + "allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"), + } } - } - stock_entry.add_to_stock_entry_detail(items_dict) - return stock_entry.as_dict() - else: - frappe.throw(_("No Items selected for transfer")) - return subcontract_order.name + + stock_entry.add_to_stock_entry_detail(items_dict) + + if target_doc: + return stock_entry + else: + return stock_entry.as_dict() + else: + frappe.throw(_("No Items selected for transfer.")) def add_items_in_ste( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a952a93ac725..266ea5f674fb 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -625,6 +625,12 @@ frappe.ui.form.on('Stock Entry', { purchase_order: (frm) => { if (frm.doc.purchase_order) { frm.set_value("subcontracting_order", ""); + erpnext.utils.map_current_doc({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontract_order', + source_name: frm.doc.purchase_order, + target_doc: frm, + freeze: true, + }); } }, @@ -632,7 +638,7 @@ frappe.ui.form.on('Stock Entry', { if (frm.doc.subcontracting_order) { frm.set_value("purchase_order", ""); erpnext.utils.map_current_doc({ - method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontract_order', source_name: frm.doc.subcontracting_order, target_doc: frm, freeze: true, diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 62f2acd2fd7f..738ac330e395 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1945,6 +1945,8 @@ def add_to_stock_entry_detail(self, item_dict, bom_no=None): se_child.is_finished_item = item_row.get("is_finished_item", 0) se_child.is_scrap_item = item_row.get("is_scrap_item", 0) se_child.is_process_loss = item_row.get("is_process_loss", 0) + se_child.po_detail = item_row.get("po_detail") + se_child.sco_rm_detail = item_row.get("sco_rm_detail") for field in [ self.subcontract_data.rm_detail_field, @@ -2591,50 +2593,15 @@ def get_supplied_items( @frappe.whitelist() -def get_items_from_subcontracting_order(source_name, target_doc=None): - def post_process(source, target): - target.stock_entry_type = target.purpose = "Send to Subcontractor" - target.subcontracting_order = source_name +def get_items_from_subcontract_order(source_name, target_doc=None): + from erpnext.controllers.subcontracting_controller import make_rm_stock_entry - if target.items: - target.items = [] + if isinstance(target_doc, str): + target_doc = frappe.get_doc(json.loads(target_doc)) - warehouses = {} - for item in source.items: - warehouses[item.name] = item.warehouse - - for item in source.supplied_items: - target.append( - "items", - { - "s_warehouse": warehouses.get(item.reference_name), - "t_warehouse": source.supplier_warehouse, - "subcontracted_item": item.main_item_code, - "item_code": item.rm_item_code, - "qty": max(item.required_qty - item.total_supplied_qty, 0), - "transfer_qty": item.required_qty, - "uom": item.stock_uom, - "stock_uom": item.stock_uom, - "conversion_factor": 1, - "sco_rm_detail": item.name, - }, - ) - - target_doc = get_mapped_doc( - "Subcontracting Order", - source_name, - { - "Subcontracting Order": { - "doctype": "Stock Entry", - "field_no_map": ["purchase_order"], - "validation": { - "docstatus": ["=", 1], - }, - }, - }, - target_doc, - post_process, - ignore_child_tables=True, + order_doctype = "Purchase Order" if target_doc.purchase_order else "Subcontracting Order" + target_doc = make_rm_stock_entry( + subcontract_order=source_name, order_doctype=order_doctype, target_doc=target_doc ) return target_doc diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 40963f863730..15a2ac909127 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -205,20 +205,10 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll } make_stock_entry() { - frappe.model.open_mapped_doc({ - method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', - source_name: cur_frm.doc.name, - freeze: true, - freeze_message: __('Creating Stock Entry ...') - }); - } - - make_rm_stock_entry(rm_items) { frappe.call({ method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry', args: { subcontract_order: cur_frm.doc.name, - rm_items: rm_items, order_doctype: cur_frm.doc.doctype }, callback: (r) => { From 887663129e95544023b9cd2d5cf506e94f6c0263 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 16 Sep 2022 14:14:14 +0530 Subject: [PATCH 178/898] fix: fetch description only if empty on the payment schedule added fetch_if_empty on description field of payment_schedule. (cherry picked from commit f4b64686ae5a649f09211ef01a84400ea26f12dd) --- .../accounts/doctype/payment_schedule/payment_schedule.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 6ed7a3154e52..dde9980ce533 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -39,6 +39,7 @@ { "columns": 2, "fetch_from": "payment_term.description", + "fetch_if_empty": 1, "fieldname": "description", "fieldtype": "Small Text", "in_list_view": 1, @@ -159,7 +160,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-28 05:41:35.084233", + "modified": "2022-09-16 13:57:06.382859", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", @@ -168,5 +169,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From dcfc11df7a007c4e61eef1238707162e7de15500 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 10 Sep 2022 16:16:38 +0530 Subject: [PATCH 179/898] fix: incorrect gl if tax on multi currency payment entry (cherry picked from commit f0ae77b23b8e474f222d50a1f4aeef08b204b0d2) --- .../doctype/payment_entry/payment_entry.js | 35 +++++++++++++++---- .../doctype/payment_entry/payment_entry.py | 31 +++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 0f5307940305..6039bdfe95fd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -1091,7 +1091,7 @@ frappe.ui.form.on('Payment Entry', { $.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; }); - frm.doc.paid_amount_after_tax = frm.doc.paid_amount; + frm.doc.paid_amount_after_tax = frm.doc.base_paid_amount; }); }, @@ -1182,7 +1182,7 @@ frappe.ui.form.on('Payment Entry', { } cumulated_tax_fraction += tax.tax_fraction_for_current_item; - frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction)) + frm.doc.paid_amount_after_tax = flt(frm.doc.base_paid_amount/(1+cumulated_tax_fraction)) }); }, @@ -1214,6 +1214,7 @@ frappe.ui.form.on('Payment Entry', { frm.doc.total_taxes_and_charges = 0.0; frm.doc.base_total_taxes_and_charges = 0.0; + let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; let actual_tax_dict = {}; // maintain actual tax rate based on idx @@ -1234,8 +1235,8 @@ frappe.ui.form.on('Payment Entry', { } } - tax.tax_amount = current_tax_amount; - tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate; + // tax accounts are only in company currency + tax.base_tax_amount = current_tax_amount; current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; if(i==0) { @@ -1244,9 +1245,29 @@ frappe.ui.form.on('Payment Entry', { tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax)); } - tax.base_total = tax.total * frm.doc.source_exchange_rate; - frm.doc.total_taxes_and_charges += current_tax_amount; - frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate; + // tac accounts are only in company currency + tax.base_total = tax.total + + // calculate total taxes and base total taxes + if(frm.doc.payment_type == "Pay") { + // tax accounts only have company currency + if(tax.currency != frm.doc.paid_to_account_currency) { + //total_taxes_and_charges has the target currency. so using target conversion rate + frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.target_exchange_rate); + + } else { + frm.doc.total_taxes_and_charges += current_tax_amount; + } + } else if(frm.doc.payment_type == "Receive") { + if(tax.currency != frm.doc.paid_from_account_currency) { + //total_taxes_and_charges has the target currency. so using source conversion rate + frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.source_exchange_rate); + } else { + frm.doc.total_taxes_and_charges += current_tax_amount; + } + } + + frm.doc.base_total_taxes_and_charges += tax.base_tax_amount; frm.refresh_field('taxes'); frm.refresh_field('total_taxes_and_charges'); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 4618d0807cbf..7f245fd083a0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -940,6 +940,13 @@ def add_tax_gl_entries(self, gl_entries): ) if not d.included_in_paid_amount: + if get_account_currency(payment_account) != self.company_currency: + if self.payment_type == "Receive": + exchange_rate = self.target_exchange_rate + elif self.payment_type in ["Pay", "Internal Transfer"]: + exchange_rate = self.source_exchange_rate + base_tax_amount = flt((tax_amount / exchange_rate), self.precision("paid_amount")) + gl_entries.append( self.get_gl_dict( { @@ -1033,7 +1040,7 @@ def initialize_taxes(self): for fieldname in tax_fields: tax.set(fieldname, 0.0) - self.paid_amount_after_tax = self.paid_amount + self.paid_amount_after_tax = self.base_paid_amount def determine_exclusive_rate(self): if not any(cint(tax.included_in_paid_amount) for tax in self.get("taxes")): @@ -1052,7 +1059,7 @@ def determine_exclusive_rate(self): cumulated_tax_fraction += tax.tax_fraction_for_current_item - self.paid_amount_after_tax = flt(self.paid_amount / (1 + cumulated_tax_fraction)) + self.paid_amount_after_tax = flt(self.base_paid_amount / (1 + cumulated_tax_fraction)) def calculate_taxes(self): self.total_taxes_and_charges = 0.0 @@ -1075,7 +1082,7 @@ def calculate_taxes(self): current_tax_amount += actual_tax_dict[tax.idx] tax.tax_amount = current_tax_amount - tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate + tax.base_tax_amount = current_tax_amount if tax.add_deduct_tax == "Deduct": current_tax_amount *= -1.0 @@ -1089,14 +1096,20 @@ def calculate_taxes(self): self.get("taxes")[i - 1].total + current_tax_amount, self.precision("total", tax) ) - tax.base_total = tax.total * self.source_exchange_rate + tax.base_total = tax.total if self.payment_type == "Pay": - self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate) - self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate) - else: - self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate) - self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate) + if tax.currency != self.paid_to_account_currency: + self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate) + else: + self.total_taxes_and_charges += current_tax_amount + elif self.payment_type == "Receive": + if tax.currency != self.paid_from_account_currency: + self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate) + else: + self.total_taxes_and_charges += current_tax_amount + + self.base_total_taxes_and_charges += tax.base_tax_amount if self.get("taxes"): self.paid_amount_after_tax = self.get("taxes")[-1].base_total From 5ba5b7bf510716f170268a655a69bf046669b0e6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 16 Sep 2022 15:57:58 +0530 Subject: [PATCH 180/898] test: gl entries of payments with advance tax (cherry picked from commit 5bd5dd726229dc101de757ed72bb40b94ffc33c9) --- .../payment_entry/test_payment_entry.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 02627eb00743..123b5dfd512b 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -4,6 +4,7 @@ import unittest import frappe +from frappe import qb from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, nowdate @@ -722,6 +723,46 @@ def test_multi_currency_payment_entry_with_taxes(self): flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2) ) + def test_gl_of_multi_currency_payment_with_taxes(self): + payment_entry = create_payment_entry( + party="_Test Supplier USD", paid_to="_Test Payable USD - _TC", save=True + ) + payment_entry.append( + "taxes", + { + "account_head": "_Test Account Service Tax - _TC", + "charge_type": "Actual", + "tax_amount": 100, + "add_deduct_tax": "Add", + "description": "Test", + }, + ) + payment_entry.target_exchange_rate = 80 + payment_entry.received_amount = 12.5 + payment_entry = payment_entry.submit() + gle = qb.DocType("GL Entry") + gl_entries = ( + qb.from_(gle) + .select( + gle.account, + gle.debit, + gle.credit, + gle.debit_in_account_currency, + gle.credit_in_account_currency, + ) + .orderby(gle.account) + .where(gle.voucher_no == payment_entry.name) + .run() + ) + + expected_gl_entries = ( + ("_Test Account Service Tax - _TC", 100.0, 0.0, 100.0, 0.0), + ("_Test Bank - _TC", 0.0, 1100.0, 0.0, 1100.0), + ("_Test Payable USD - _TC", 1000.0, 0.0, 12.5, 0), + ) + + self.assertEqual(gl_entries, expected_gl_entries) + def test_payment_entry_against_onhold_purchase_invoice(self): pi = make_purchase_invoice() From a56b5ed8e50faf9265005267034001791d2764e5 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 09:43:12 +0530 Subject: [PATCH 181/898] refactor: rewrite `Item Shortage Report` queries in QB (cherry picked from commit f0a78aa559c94e45564130f9571d269eefe6c551) --- .../item_shortage_report.py | 67 ++++++++----------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py index 03a3a6a0b83b..9fafe91c3f96 100644 --- a/erpnext/stock/report/item_shortage_report/item_shortage_report.py +++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py @@ -8,8 +8,7 @@ def execute(filters=None): columns = get_columns() - conditions = get_conditions(filters) - data = get_data(conditions, filters) + data = get_data(filters) if not data: return [], [], None, [] @@ -19,49 +18,39 @@ def execute(filters=None): return columns, data, None, chart_data -def get_conditions(filters): - conditions = "" +def get_data(filters): + bin = frappe.qb.DocType("Bin") + wh = frappe.qb.DocType("Warehouse") + item = frappe.qb.DocType("Item") - if filters.get("warehouse"): - conditions += "AND warehouse in %(warehouse)s" - if filters.get("company"): - conditions += "AND company = %(company)s" - - return conditions - - -def get_data(conditions, filters): - data = frappe.db.sql( - """ - SELECT + query = ( + frappe.qb.from_(bin) + .from_(wh) + .from_(item) + .select( bin.warehouse, bin.item_code, - bin.actual_qty , - bin.ordered_qty , - bin.planned_qty , - bin.reserved_qty , + bin.actual_qty, + bin.ordered_qty, + bin.planned_qty, + bin.reserved_qty, bin.reserved_qty_for_production, - bin.projected_qty , - warehouse.company, - item.item_name , - item.description - FROM - `tabBin` bin, - `tabWarehouse` warehouse, - `tabItem` item - WHERE - bin.projected_qty<0 - AND warehouse.name = bin.warehouse - AND bin.item_code=item.name - {0} - ORDER BY bin.projected_qty;""".format( - conditions - ), - filters, - as_dict=1, + bin.projected_qty, + wh.company, + item.item_name, + item.description, + ) + .where((bin.projected_qty < 0) & (wh.name == bin.warehouse) & (bin.item_code == item.name)) + .orderby(bin.projected_qty) ) - return data + if filters.get("warehouse"): + query = query.where(bin.warehouse.isin(filters.get("warehouse"))) + + if filters.get("company"): + query = query.where(wh.company == filters.get("company")) + + return query.run(as_dict=True) def get_chart_data(data): From 273ed40cfb0c629599156c165dbd5a798a3892f6 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 10:46:28 +0530 Subject: [PATCH 182/898] test: add test cases for `Item Shortage Report` (cherry picked from commit 3dc754cac2f21cd738a896e6bb3bf9d54be1d6b1) --- .../test_item_shortage_report.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 erpnext/stock/report/item_shortage_report/test_item_shortage_report.py diff --git a/erpnext/stock/report/item_shortage_report/test_item_shortage_report.py b/erpnext/stock/report/item_shortage_report/test_item_shortage_report.py new file mode 100644 index 000000000000..5884c32acc7a --- /dev/null +++ b/erpnext/stock/report/item_shortage_report/test_item_shortage_report.py @@ -0,0 +1,51 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase + +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.report.item_shortage_report.item_shortage_report import ( + execute as item_shortage_report, +) + + +class TestItemShortageReport(FrappeTestCase): + def test_item_shortage_report(self): + item = make_item().name + so = make_sales_order(item_code=item) + + reserved_qty, projected_qty = frappe.db.get_value( + "Bin", + { + "item_code": item, + "warehouse": so.items[0].warehouse, + }, + ["reserved_qty", "projected_qty"], + ) + self.assertEqual(reserved_qty, so.items[0].qty) + self.assertEqual(projected_qty, -(so.items[0].qty)) + + filters = { + "company": so.company, + } + report_data = item_shortage_report(filters)[1] + item_code_list = [row.get("item_code") for row in report_data] + self.assertIn(item, item_code_list) + + filters = { + "company": so.company, + "warehouse": [so.items[0].warehouse], + } + report_data = item_shortage_report(filters)[1] + item_code_list = [row.get("item_code") for row in report_data] + self.assertIn(item, item_code_list) + + filters = { + "company": so.company, + "warehouse": ["Work In Progress - _TC"], + } + report_data = item_shortage_report(filters)[1] + item_code_list = [row.get("item_code") for row in report_data] + self.assertNotIn(item, item_code_list) From e8076629fa281b8a7666deb9e595be2ebfe7d6c4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Sep 2022 20:05:20 +0530 Subject: [PATCH 183/898] fix: Add child table for tax withheld vouchers (cherry picked from commit 246c1a9380238212b821951db6ed3e8ef88d9922) --- .../purchase_invoice/purchase_invoice.json | 21 ++++++-- .../doctype/tax_withheld_vouchers/__init__.py | 0 .../tax_withheld_vouchers.json | 48 +++++++++++++++++++ .../tax_withheld_vouchers.py | 9 ++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py create mode 100644 erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json create mode 100644 erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 534b879e7832..1d5965140588 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -93,6 +93,8 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", + "tax_withheld_vouchers_section", + "tax_withheld_vouchers", "section_break_44", "apply_discount_on", "base_discount_amount", @@ -1367,7 +1369,7 @@ "width": "50px" }, { - "depends_on": "eval:doc.is_subcontracted", + "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", @@ -1426,13 +1428,25 @@ "hidden": 1, "label": "Is Old Subcontracting Flow", "read_only": 1 - } + }, + { + "fieldname": "tax_withheld_vouchers_section", + "fieldtype": "Section Break", + "label": "Tax Withheld Vouchers" + }, + { + "fieldname": "tax_withheld_vouchers", + "fieldtype": "Table", + "label": "Tax Withheld Vouchers", + "options": "Tax Withheld Vouchers", + "read_only": 1 + } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-06-15 15:40:58.527065", + "modified": "2022-09-13 16:22:04.103982", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -1492,6 +1506,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "supplier", "title_field": "title", "track_changes": 1 diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py b/erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json new file mode 100644 index 000000000000..cecc6fb20ab6 --- /dev/null +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -0,0 +1,48 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2022-09-13 16:18:59.404842", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "voucher_type", + "voucher_name", + "taxable_amount" + ], + "fields": [ + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Voucher Type", + "options": "DocType" + }, + { + "fieldname": "voucher_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Voucher Name", + "options": "voucher_type" + }, + { + "fieldname": "taxable_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Taxable Amount" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-09-13 17:31:52.321034", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withheld Vouchers", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py new file mode 100644 index 000000000000..ea54c5403a8b --- /dev/null +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TaxWithheldVouchers(Document): + pass From f759c29d5576c8288872e000dfb12de54e2d889a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Sep 2022 20:31:31 +0530 Subject: [PATCH 184/898] fix: Fetch vouchers to show in Invoice (cherry picked from commit 3fb1595a4ecdaeac4a6d85be1d8bfa4a62f181d8) --- .../purchase_invoice/purchase_invoice.py | 15 +++- .../tax_withholding_category.py | 72 +++++++++---------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fea81e9c272b..d18530028912 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1519,7 +1519,7 @@ def set_tax_withholding(self): if not self.tax_withholding_category: return - tax_withholding_details, advance_taxes = get_party_tax_withholding_details( + tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details( self, self.tax_withholding_category ) @@ -1548,6 +1548,19 @@ def set_tax_withholding(self): for d in to_remove: self.remove(d) + ## Add pending vouchers on which tax was withheld + self.set("tax_withheld_vouchers", []) + + for voucher_no, voucher_details in voucher_wise_amount.items(): + self.append( + "tax_withheld_vouchers", + { + "voucher_name": voucher_no, + "voucher_type": voucher_details.get("voucher_type"), + "taxable_amount": voucher_details.get("amount"), + }, + ) + # calculate totals again after applying TDS self.calculate_taxes_and_totals() diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 6004e2b19b21..f2fa77004c18 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -109,7 +109,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): ).format(tax_withholding_category, inv.company, party) ) - tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount( + tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount = get_tax_amount( party_type, parties, inv, tax_details, posting_date, pan_no ) @@ -119,7 +119,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) if inv.doctype == "Purchase Invoice": - return tax_row, tax_deducted_on_advances + return tax_row, tax_deducted_on_advances, voucher_wise_amount else: return tax_row @@ -217,7 +217,9 @@ def get_lower_deduction_certificate(tax_details, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): - vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type) + vouchers, voucher_wise_amount = get_invoice_vouchers( + parties, tax_details, inv.company, party_type=party_type + ) advance_vouchers = get_advance_vouchers( parties, company=inv.company, @@ -236,6 +238,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_deducted = get_deducted_tax(taxable_vouchers, tax_details) tax_amount = 0 + if party_type == "Supplier": ldc = get_lower_deduction_certificate(tax_details, pan_no) if tax_deducted: @@ -261,12 +264,13 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N if cint(tax_details.round_off_tax_amount): tax_amount = round(tax_amount) - return tax_amount, tax_deducted, tax_deducted_on_advances + return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): - dr_or_cr = "credit" if party_type == "Supplier" else "debit" doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" + voucher_wise_amount = {} + vouchers = [] filters = { "company": company, @@ -281,29 +285,42 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""] + invoices_details = frappe.get_all( + doctype, filters=filters, fields=["name", "base_net_total"] + ) or [""] - journal_entries = frappe.db.sql( + for d in invoices_details: + vouchers.append(d.name) + voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}}) + + journal_entries_details = frappe.db.sql( """ - SELECT j.name + SELECT j.name, ja.credit - ja.debit AS amount FROM `tabJournal Entry` j, `tabJournal Entry Account` ja WHERE - j.docstatus = 1 + j.name = ja.parent + AND j.docstatus = 1 AND j.is_opening = 'No' AND j.posting_date between %s and %s - AND ja.{dr_or_cr} > 0 AND ja.party in %s - """.format( - dr_or_cr=dr_or_cr + AND j.apply_tds = 1 + AND j.tax_withholding_category = %s + """, + ( + tax_details.from_date, + tax_details.to_date, + tuple(parties), + tax_details.get("tax_withholding_category"), ), - (tax_details.from_date, tax_details.to_date, tuple(parties)), - as_list=1, + as_dict=1, ) - if journal_entries: - journal_entries = journal_entries[0] + if journal_entries_details: + for d in journal_entries_details: + vouchers.append(d.name) + voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}}) - return invoices + journal_entries + return vouchers, voucher_wise_amount def get_advance_vouchers( @@ -394,11 +411,6 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): supp_credit_amt += supp_jv_credit_amt supp_credit_amt += inv.net_total - debit_note_amount = get_debit_note_amount( - parties, tax_details.from_date, tax_details.to_date, inv.company - ) - supp_credit_amt -= debit_note_amount - threshold = tax_details.get("threshold", 0) cumulative_threshold = tax_details.get("cumulative_threshold", 0) @@ -515,22 +527,6 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net return tds_amount -def get_debit_note_amount(suppliers, from_date, to_date, company=None): - - filters = { - "supplier": ["in", suppliers], - "is_return": 1, - "docstatus": 1, - "posting_date": ["between", (from_date, to_date)], - } - fields = ["abs(sum(net_total)) as net_total"] - - if company: - filters["company"] = company - - return frappe.get_all("Purchase Invoice", filters, fields)[0].get("net_total") or 0.0 - - def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): if current_amount < (certificate_limit - deducted_amount): return current_amount * rate / 100 From 27fdd41a6e2affea48c56ae18fbeb1a2b9410b61 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 14 Sep 2022 09:13:02 +0530 Subject: [PATCH 185/898] test: Add tests (cherry picked from commit b6184ce4715111add6dad3acf839097639cb4b51) --- .../purchase_invoice/purchase_invoice.json | 6 +- .../tax_withheld_vouchers.json | 5 +- .../tax_withholding_category.py | 4 +- .../test_tax_withholding_category.py | 68 ++++++++++++++++++- erpnext/www/lms/__init__.py | 0 5 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 erpnext/www/lms/__init__.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 1d5965140588..1eeaf13abc90 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -83,6 +83,8 @@ "section_break_51", "taxes_and_charges", "taxes", + "tax_withheld_vouchers_section", + "tax_withheld_vouchers", "sec_tax_breakup", "other_charges_calculation", "totals", @@ -93,8 +95,6 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", - "tax_withheld_vouchers_section", - "tax_withheld_vouchers", "section_break_44", "apply_discount_on", "base_discount_amount", @@ -1446,7 +1446,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-09-13 16:22:04.103982", + "modified": "2022-09-13 23:39:54.525037", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index cecc6fb20ab6..ce8c0c370865 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -29,13 +29,14 @@ "fieldname": "taxable_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Taxable Amount" + "label": "Taxable Amount", + "options": "Company:company:default_currency" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-09-13 17:31:52.321034", + "modified": "2022-09-13 23:40:41.479208", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index f2fa77004c18..15f75d151032 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -285,9 +285,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices_details = frappe.get_all( - doctype, filters=filters, fields=["name", "base_net_total"] - ) or [""] + invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"]) for d in invoices_details: vouchers.append(d.name) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 3059f8d64b8c..5c031a995424 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -148,7 +148,7 @@ def test_cumulative_threshold_tcs(self): self.assertEqual(tcs_charged, 500) invoices.append(si) - # delete invoices to avoid clashing + # cancel invoices to avoid clashing for d in invoices: d.cancel() @@ -182,7 +182,7 @@ def test_tds_calculation_on_net_total(self): self.assertEqual(pi1.taxes[0].tax_amount, 4000) - # delete invoices to avoid clashing + # cancel invoices to avoid clashing for d in invoices: d.cancel() @@ -207,10 +207,52 @@ def test_multi_category_single_supplier(self): self.assertEqual(pi1.taxes[0].tax_amount, 250) - # delete invoices to avoid clashing + # cancel invoices to avoid clashing for d in invoices: d.cancel() + def test_tax_withholding_category_voucher_display(self): + frappe.db.set_value( + "Supplier", "Test TDS Supplier6", "tax_withholding_category", "Test Multi Invoice Category" + ) + invoices = [] + + pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=4000, do_not_save=True) + pi.apply_tds = 1 + pi.tax_withholding_category = "Test Multi Invoice Category" + pi.save() + pi.submit() + invoices.append(pi) + + pi1 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=2000, do_not_save=True) + pi1.apply_tds = 1 + pi1.is_return = 1 + pi1.items[0].qty = -1 + pi1.tax_withholding_category = "Test Multi Invoice Category" + pi1.save() + pi1.submit() + invoices.append(pi1) + + pi2 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=9000, do_not_save=True) + pi2.apply_tds = 1 + pi2.tax_withholding_category = "Test Multi Invoice Category" + pi2.save() + pi2.submit() + invoices.append(pi2) + + pi2.load_from_db() + + self.assertTrue(pi2.taxes[0].tax_amount, 1100) + + self.assertTrue(pi2.tax_withheld_vouchers[0].voucher_name == pi1.name) + self.assertTrue(pi2.tax_withheld_vouchers[0].taxable_amount == pi1.net_total) + self.assertTrue(pi2.tax_withheld_vouchers[1].voucher_name == pi.name) + self.assertTrue(pi2.tax_withheld_vouchers[1].taxable_amount == pi.net_total) + + # cancel invoices to avoid clashing + for d in reversed(invoices): + d.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all( @@ -308,6 +350,7 @@ def create_records(): "Test TDS Supplier3", "Test TDS Supplier4", "Test TDS Supplier5", + "Test TDS Supplier6", ]: if frappe.db.exists("Supplier", name): continue @@ -498,3 +541,22 @@ def create_tax_with_holding_category(): "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], } ).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"): + frappe.get_doc( + { + "doctype": "Tax Withholding Category", + "name": "Test Multi Invoice Category", + "category_name": "Test Multi Invoice Category", + "rates": [ + { + "from_date": fiscal_year[1], + "to_date": fiscal_year[2], + "tax_withholding_rate": 10, + "single_threshold": 5000, + "cumulative_threshold": 10000, + } + ], + "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], + } + ).insert() diff --git a/erpnext/www/lms/__init__.py b/erpnext/www/lms/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 From eba46dae6c4c03d2fe4de8998039a0e64b3a9d8f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 16 Sep 2022 13:50:37 +0530 Subject: [PATCH 186/898] fix: TDS deduction via journal entry (cherry picked from commit 36d0906ea26b65b9a4111439e2046bcf16305273) --- .../doctype/journal_entry/journal_entry.py | 4 ++- .../tax_withholding_category.py | 32 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 63c6547f1d45..52690e1e662c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -184,7 +184,9 @@ def apply_tax_withholding(self): } ) - tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category) + tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details( + inv, self.tax_withholding_category + ) if not tax_withholding_details: return diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 15f75d151032..0b5df9e0cc04 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -344,23 +344,25 @@ def get_advance_vouchers( def get_taxes_deducted_on_advances_allocated(inv, tax_details): - advances = [d.reference_name for d in inv.get("advances")] tax_info = [] - if advances: - pe = frappe.qb.DocType("Payment Entry").as_("pe") - at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") - - tax_info = ( - frappe.qb.from_(at) - .inner_join(pe) - .on(pe.name == at.parent) - .select(at.parent, at.name, at.tax_amount, at.allocated_amount) - .where(pe.tax_withholding_category == tax_details.get("tax_withholding_category")) - .where(at.parent.isin(advances)) - .where(at.account_head == tax_details.account_head) - .run(as_dict=True) - ) + if inv.get("advances"): + advances = [d.reference_name for d in inv.get("advances")] + + if advances: + pe = frappe.qb.DocType("Payment Entry").as_("pe") + at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") + + tax_info = ( + frappe.qb.from_(at) + .inner_join(pe) + .on(pe.name == at.parent) + .select(at.parent, at.name, at.tax_amount, at.allocated_amount) + .where(pe.tax_withholding_category == tax_details.get("tax_withholding_category")) + .where(at.parent.isin(advances)) + .where(at.account_head == tax_details.account_head) + .run(as_dict=True) + ) return tax_info From 302f2d10e2ae817512d14d1afac8827f831132b1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Sep 2022 09:06:18 +0530 Subject: [PATCH 187/898] chore: fix tests (cherry picked from commit 9aa1f84d4578901902bfdb3889c571fced8861c1) --- .../test_tax_withholding_category.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 5c031a995424..e80fe11ab303 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -52,7 +52,7 @@ def test_cumulative_threshold_tds(self): invoices.append(pi) # delete invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_single_threshold_tds(self): @@ -88,7 +88,7 @@ def test_single_threshold_tds(self): self.assertEqual(pi.taxes_and_charges_deducted, 1000) # delete invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_tax_withholding_category_checks(self): @@ -114,7 +114,7 @@ def test_tax_withholding_category_checks(self): # TDS should be applied only on 1000 self.assertEqual(pi1.taxes[0].tax_amount, 1000) - for d in invoices: + for d in reversed(invoices): d.cancel() def test_cumulative_threshold_tcs(self): @@ -149,7 +149,7 @@ def test_cumulative_threshold_tcs(self): invoices.append(si) # cancel invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_tds_calculation_on_net_total(self): @@ -183,7 +183,7 @@ def test_tds_calculation_on_net_total(self): self.assertEqual(pi1.taxes[0].tax_amount, 4000) # cancel invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_multi_category_single_supplier(self): @@ -208,7 +208,7 @@ def test_multi_category_single_supplier(self): self.assertEqual(pi1.taxes[0].tax_amount, 250) # cancel invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_tax_withholding_category_voucher_display(self): From 4939153f8c7e0d1a1488d6a698050d2ca147b4fa Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 18 Sep 2022 19:41:05 +0530 Subject: [PATCH 188/898] fix: Depreciation posting date only when calculate depreciation is checked (cherry picked from commit fac82cf69be487f4bfba953c722700fcc515b044) --- erpnext/assets/doctype/asset/asset.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index f414930d7224..a43a16c9ec52 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -239,8 +239,10 @@ frappe.ui.form.on('Asset', { item_code: function(frm) { - if(frm.doc.item_code) { + if(frm.doc.item_code && frm.doc.calculate_depreciation) { frm.trigger('set_finance_book'); + } else { + frm.set_value('finance_books', []); } }, @@ -381,6 +383,11 @@ 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 ) { + frm.trigger("set_finance_book"); + } else { + frm.set_value("finance_books", []); + } }, gross_purchase_amount: function(frm) { From c0003195b8e097b32662cb3491b6d6d80f52a86b Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 16 Sep 2022 18:26:00 +0530 Subject: [PATCH 189/898] fix: remove no_copy for ignore_pricing_rule (cherry picked from commit 8c5b420aea221b001ea8537a8391654699757586) --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 +-- erpnext/buying/doctype/purchase_order/purchase_order.json | 3 +-- erpnext/selling/doctype/quotation/quotation.json | 3 +-- erpnext/selling/doctype/sales_order/sales_order.json | 3 +-- erpnext/stock/doctype/delivery_note/delivery_note.json | 3 +-- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 3 +-- 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 534b879e7832..a93965e2a548 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -512,7 +512,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1432,7 +1431,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-06-15 15:40:58.527065", + "modified": "2022-09-16 17:45:25.345996", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -1492,6 +1491,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "supplier", "title_field": "title", "track_changes": 1 diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 1c9d3fbfb2d7..2da515737a97 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -649,7 +649,6 @@ "hide_days": 1, "hide_seconds": 1, "label": "Ignore Pricing Rule", - "no_copy": 1, "print_hide": 1 }, { @@ -2022,7 +2021,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-07-11 17:43:56.435382", + "modified": "2022-09-16 17:44:22.227332", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index acca380672d3..fb8f25a0dfc4 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -441,7 +441,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1180,7 +1179,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-07 11:06:46.035093", + "modified": "2022-09-16 17:45:04.954055", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index bb2f95dd173d..c58a46ba513a 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -402,7 +402,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -986,7 +985,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-06-11 20:35:32.635804", + "modified": "2022-09-16 17:44:43.221804", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 74c5c07e47ba..ff269d0e6840 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -544,7 +544,6 @@ "hide_days": 1, "hide_seconds": 1, "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1549,7 +1548,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-06-10 03:52:22.212953", + "modified": "2022-09-16 17:43:57.007441", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index f9e934921d8b..a8f907ed7113 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -490,7 +490,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1336,7 +1335,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-06-10 03:52:04.197415", + "modified": "2022-09-16 17:46:17.701904", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index a70415dfc36e..acaac920c9e3 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -405,7 +405,6 @@ "fieldname": "ignore_pricing_rule", "fieldtype": "Check", "label": "Ignore Pricing Rule", - "no_copy": 1, "permlevel": 1, "print_hide": 1 }, @@ -1158,7 +1157,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-06-15 15:43:40.664382", + "modified": "2022-09-16 17:45:58.430132", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 7554ac9569d1b8a2bc3d36ae266010aa656c3790 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Sep 2022 17:32:12 +0530 Subject: [PATCH 190/898] fix: get amount in words for debit note (cherry picked from commit 70f6484d9d9afc3b89e6df6a18993214584722f8) --- erpnext/controllers/buying_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 5659ad0aa9b3..48fe7cb083d3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -194,16 +194,16 @@ def set_total_in_words(self): if self.meta.get_field("base_in_words"): if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled(): - amount = self.base_rounded_total + amount = abs(self.base_rounded_total) else: - amount = self.base_grand_total + amount = abs(self.base_grand_total) self.base_in_words = money_in_words(amount, self.company_currency) if self.meta.get_field("in_words"): if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled(): - amount = self.rounded_total + amount = abs(self.rounded_total) else: - amount = self.grand_total + amount = abs(self.grand_total) self.in_words = money_in_words(amount, self.currency) From 181dccd8d85c2b54f68f3668078efd40e511b15b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 16:18:59 +0530 Subject: [PATCH 191/898] refactor: rewrite `BOM Stock Report` queries in `QB` (cherry picked from commit 8fd7c04920ddfb942451ec9151db36225f86f83b) --- .../bom_stock_report/bom_stock_report.py | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 34e9826305ef..1e1b4356008e 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Floor, Sum +from pypika.terms import ExistsCriterion def execute(filters=None): @@ -11,7 +13,6 @@ def execute(filters=None): filters = {} columns = get_columns() - data = get_bom_stock(filters) return columns, data @@ -33,59 +34,57 @@ def get_columns(): def get_bom_stock(filters): - conditions = "" - bom = filters.get("bom") - - table = "`tabBOM Item`" - qty_field = "stock_qty" - - qty_to_produce = filters.get("qty_to_produce", 1) - if int(qty_to_produce) <= 0: + qty_to_produce = filters.get("qty_to_produce") or 1 + if int(qty_to_produce) < 0: frappe.throw(_("Quantity to Produce can not be less than Zero")) if filters.get("show_exploded_view"): - table = "`tabBOM Explosion Item`" + bom_item_table = "BOM Explosion Item" + else: + bom_item_table = "BOM Item" + + bin = frappe.qb.DocType("Bin") + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType(bom_item_table) + + query = ( + frappe.qb.from_(bom) + .inner_join(bom_item) + .on(bom.name == bom_item.parent) + .left_join(bin) + .on(bom_item.item_code == bin.item_code) + .select( + bom_item.item_code, + bom_item.description, + bom_item.stock_qty, + bom_item.stock_uom, + bom_item.stock_qty * qty_to_produce / bom.quantity, + Sum(bin.actual_qty).as_("actual_qty"), + Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))), + ) + .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) + .groupby(bom_item.item_code) + ) if filters.get("warehouse"): warehouse_details = frappe.db.get_value( "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 ) + if warehouse_details: - conditions += ( - " and exists (select name from `tabWarehouse` wh \ - where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" - % (warehouse_details.lft, warehouse_details.rgt) + wh = frappe.qb.DocType("Warehouse") + query = query.where( + ExistsCriterion( + frappe.qb.from_(wh) + .select(wh.name) + .where( + (wh.lft >= warehouse_details.lft) + & (wh.rgt <= warehouse_details.rgt) + & (bin.warehouse == wh.name) + ) + ) ) else: - conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse")) + query = query.where(bin.warehouse == filters.get("warehouse")) - else: - conditions += "" - - return frappe.db.sql( - """ - SELECT - bom_item.item_code, - bom_item.description , - bom_item.{qty_field}, - bom_item.stock_uom, - bom_item.{qty_field} * {qty_to_produce} / bom.quantity, - sum(ledger.actual_qty) as actual_qty, - sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity))) - FROM - `tabBOM` AS bom INNER JOIN {table} AS bom_item - ON bom.name = bom_item.parent - LEFT JOIN `tabBin` AS ledger - ON bom_item.item_code = ledger.item_code - {conditions} - WHERE - bom_item.parent = {bom} and bom_item.parenttype='BOM' - - GROUP BY bom_item.item_code""".format( - qty_field=qty_field, - table=table, - conditions=conditions, - bom=frappe.db.escape(bom), - qty_to_produce=qty_to_produce or 1, - ) - ) + return query.run() From ed9a896f7294138879756cf5d539d0f999f0cb92 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 16:25:20 +0530 Subject: [PATCH 192/898] fix: warehouse filter in `BOM Stock Calculated Report` (cherry picked from commit 390ce5719d290db38172aef69f542a862a662090) --- .../report/bom_stock_calculated/bom_stock_calculated.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index ec4b25c859ff..550445c1f77f 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -146,7 +146,7 @@ def get_bom_data(filters): ) ) else: - query = query.where(bin.warehouse == frappe.db.escape(filters.get("warehouse"))) + query = query.where(bin.warehouse == filters.get("warehouse")) return query.run(as_dict=True) From c50d6c5e74bc2a4aa5b3acba91bc6096df406336 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 20 Sep 2022 18:31:07 +0000 Subject: [PATCH 193/898] chore(release): Bumped to Version 14.2.0 # [14.2.0](https://github.com/frappe/erpnext/compare/v14.1.2...v14.2.0) (2022-09-20) ### Bug Fixes * `po_detail` or `sco_rm_detail` not getting set while while mapping SE ([ff210c7](https://github.com/frappe/erpnext/commit/ff210c73eb44bef4e89a10826f3d5415f56b56d0)) * `sco_rm_detail` in Stock Entry ([a15f0d4](https://github.com/frappe/erpnext/commit/a15f0d427c76f3ba01aec854ea010f1996f09dc7)) * abbreviation issue on renaming cost center ([f752822](https://github.com/frappe/erpnext/commit/f752822bb34d1c41fcc40dfeb2604ce140e25d9b)) * Add child table for tax withheld vouchers ([e807662](https://github.com/frappe/erpnext/commit/e8076629fa281b8a7666deb9e595be2ebfe7d6c4)) * always set default expense account in company ([3b79e24](https://github.com/frappe/erpnext/commit/3b79e24c7edf77c471b89da2c349ebe47c66d572)) * **Asset Capitalization:** Hide source items section if table is empty ([003cfe2](https://github.com/frappe/erpnext/commit/003cfe27172f1f1eecf1e663b7c0559c1a009be6)) * **Asset Capitalization:** Reverse depreciation on cancel ([85d1a23](https://github.com/frappe/erpnext/commit/85d1a237ce13d857ebbecd6a7e196c06d9aa3735)) * **Asset Capitalization:** update code for changes in depreciation logic ([dc3c27f](https://github.com/frappe/erpnext/commit/dc3c27fd1b6382c1f1e7a6cdbe4af3826859b247)) * **Asset Capitalization:** validation edge cases ([c311b8e](https://github.com/frappe/erpnext/commit/c311b8ea4f5c51c18462f671d26ede1c18f7f5b6)) * **Asset Captalization:** run_serially on posting_date changed ([132b517](https://github.com/frappe/erpnext/commit/132b517584352a8a6c895fd3256bdc08b03c1df5)) * **Asset:** On Depreciation reversal, remove Journal Entry reference ([e832944](https://github.com/frappe/erpnext/commit/e832944dfede4e979691ec7a4dd22c2fd89de4f7)) * consider posting time for internal transfer PO ([f370c7b](https://github.com/frappe/erpnext/commit/f370c7b50b0d8902c031b52ad4154bc896613915)) * correct sql output format in CRM patch (backport [#32213](https://github.com/frappe/erpnext/issues/32213)) ([#32215](https://github.com/frappe/erpnext/issues/32215)) ([64aad88](https://github.com/frappe/erpnext/commit/64aad8868414864d089bc552a196c843aae1c02c)) * create dunning from sales invoice ([17ea6cc](https://github.com/frappe/erpnext/commit/17ea6cc5a585464bf81010997a3bbd08b4aebba7)) * Depreciation posting date only when calculate depreciation is checked ([4939153](https://github.com/frappe/erpnext/commit/4939153f8c7e0d1a1488d6a698050d2ca147b4fa)) * disable cwip in asset repair tests ([5cab0aa](https://github.com/frappe/erpnext/commit/5cab0aa1d7c620cd6a9f0c7ac5eab83bebd34461)) * fetch description only if empty on the payment schedule ([8876631](https://github.com/frappe/erpnext/commit/887663129e95544023b9cd2d5cf506e94f6c0263)) * Fetch vouchers to show in Invoice ([f759c29](https://github.com/frappe/erpnext/commit/f759c29d5576c8288872e000dfb12de54e2d889a)) * incorrect gl if tax on multi currency payment entry ([dcfc11d](https://github.com/frappe/erpnext/commit/dcfc11df7a007c4e61eef1238707162e7de15500)) * make `po_detail` or `sco_rm_detail` mandatory for SE `Send to Subcontractor` ([908944b](https://github.com/frappe/erpnext/commit/908944b68bd75c88991011e747e02e66b91a2b78)) * merge conflict ([cd613c6](https://github.com/frappe/erpnext/commit/cd613c60336c7c7a9e9ec90e3109d52b72d3b489)) * No permission to read doctype ([442f54a](https://github.com/frappe/erpnext/commit/442f54a988e209d6d0ecfefd3cb5e3d1d8dea48a)) * Parent Level project linkning on creating PO from project ([cbaffb4](https://github.com/frappe/erpnext/commit/cbaffb4858fee0678d823d47d4e3a3e89068ece6)) * pending accrual entries ([b9a2499](https://github.com/frappe/erpnext/commit/b9a249918a644b48896b3aecaf9d38e8bf6b4519)) * production plan pending-qty ([5f3caf6](https://github.com/frappe/erpnext/commit/5f3caf697560dc37537b4653669f0621ce2cb71b)) * remove no_copy for ignore_pricing_rule ([c000319](https://github.com/frappe/erpnext/commit/c0003195b8e097b32662cb3491b6d6d80f52a86b)) * suggestion threshold label and rule was not working for other items with min and max amount ([436b7e3](https://github.com/frappe/erpnext/commit/436b7e3b70558dd0ec8907cbdfc7c6139dbab44f)) * TDS deduction via journal entry ([eba46da](https://github.com/frappe/erpnext/commit/eba46dae6c4c03d2fe4de8998039a0e64b3a9d8f)) * test cases ([f126e88](https://github.com/frappe/erpnext/commit/f126e88e5e7b2bf71be4a22eb08fe0aba0083780)) * test cases ([153ef5f](https://github.com/frappe/erpnext/commit/153ef5f164d2b361ac5c428076fcc8c7bba8aee2)) * unknown column error while updating value of maintain-stock in item master ([38488c1](https://github.com/frappe/erpnext/commit/38488c13e616d164097a60e08c229bbeedf3bab3)) * use default supplier currency if default supplier is enabled ([c98413c](https://github.com/frappe/erpnext/commit/c98413c9818df5f41231b23ba6bb86b70a8581b8)) * warehouse filter in `BOM Stock Calculated Report` ([ed9a896](https://github.com/frappe/erpnext/commit/ed9a896f7294138879756cf5d539d0f999f0cb92)) ### Features * Asset Capitalization ([38c3107](https://github.com/frappe/erpnext/commit/38c31077c9ca63ef4da586680cd5bd326f04144a)) * Asset Capitalization Form ([6c74896](https://github.com/frappe/erpnext/commit/6c748966e732387383399389176afeb2fcc1ab7d)) * **Asset Capitalization:** Accounting Fields ([702b5c3](https://github.com/frappe/erpnext/commit/702b5c32c1e4c1e30bf9e84ac738df30a3b4a435)) * **Asset Capitalization:** Finance Book field in Asset Row ([3b9bc8e](https://github.com/frappe/erpnext/commit/3b9bc8e4effcd46d53c30169ea24df2ae849ad5c)) * **Asset Capitalization:** Submission and Cancellation ([7a5d75b](https://github.com/frappe/erpnext/commit/7a5d75b68d278cf100c9b73ab4ae0475b820eb67)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index db168818a8c1..104a49017625 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.1.2" +__version__ = "14.2.0" def get_default_company(user=None): From d54bf6d91cc7b8afacdbe2fcbf0d4e7292d70445 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 17:56:47 +0530 Subject: [PATCH 194/898] refactor: rewrite `BOM Variance Report` queries in `QB` --- .../bom_variance_report.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index da283435b9f0..70a1850fd0fd 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -64,22 +64,21 @@ def get_columns(filters): def get_data(filters): - cond = "1=1" + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name.as_("work_order"), wo.qty, wo.produced_qty, wo.production_item, wo.bom_no) + .where((wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + ) if filters.get("bom_no") and not filters.get("work_order"): - cond += " and bom_no = '%s'" % filters.get("bom_no") + query = query.where(wo.bom_no == filters.get("bom_no")) if filters.get("work_order"): - cond += " and name = '%s'" % filters.get("work_order") + query = query.where(wo.name == filters.get("work_order")) results = [] - for d in frappe.db.sql( - """ select name as work_order, qty, produced_qty, production_item, bom_no - from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format( - cond - ), - as_dict=1, - ): + for d in query.run(as_dict=True): results.append(d) for data in frappe.get_all( @@ -95,16 +94,17 @@ def get_data(filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_work_orders(doctype, txt, searchfield, start, page_len, filters): - cond = "1=1" - if filters.get("bom_no"): - cond += " and bom_no = '%s'" % filters.get("bom_no") - - return frappe.db.sql( - """select name from `tabWork Order` - where name like %(name)s and {0} and produced_qty > qty and docstatus = 1 - order by name limit {2} offset {1}""".format( - cond, start, page_len - ), - {"name": "%%%s%%" % txt}, - as_list=1, + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name) + .where((wo.name.like(f"{txt}%")) & (wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + .orderby(wo.name) + .limit(page_len) + .offset(start) ) + + if filters.get("bom_no"): + query = query.where(wo.bom_no == filters.get("bom_no")) + + return query.run(as_list=True) From f5c517d08d3089fc7a3fde3cebf2430403fc6c8d Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 21 Sep 2022 09:54:38 +0530 Subject: [PATCH 195/898] refactor: rewrite `Exponential Smoothing Forecasting` queries in `QB` --- .../exponential_smoothing_forecasting.py | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 7500744c2280..d3bce8315518 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -96,38 +96,39 @@ def prepare_periodical_data(self): value["avg"] = flt(sum(list_of_period_value)) / flt(sum(total_qty)) def get_data_for_forecast(self): - cond = "" - if self.filters.item_code: - cond = " AND soi.item_code = %s" % (frappe.db.escape(self.filters.item_code)) - - warehouses = [] - if self.filters.warehouse: - warehouses = get_child_warehouses(self.filters.warehouse) - cond += " AND soi.warehouse in ({})".format(",".join(["%s"] * len(warehouses))) - - input_data = [self.filters.from_date, self.filters.company] - if warehouses: - input_data.extend(warehouses) + parent = frappe.qb.DocType(self.doctype) + child = frappe.qb.DocType(self.child_doctype) date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date" - return frappe.db.sql( - """ - SELECT - so.{date_field} as posting_date, soi.item_code, soi.warehouse, - soi.item_name, soi.stock_qty as qty, soi.base_amount as amount - FROM - `tab{doc}` so, `tab{child_doc}` soi - WHERE - so.docstatus = 1 AND so.name = soi.parent AND - so.{date_field} < %s AND so.company = %s {cond} - """.format( - doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond - ), - tuple(input_data), - as_dict=1, + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( + parent[date_field].as_("posting_date"), + child.item_code, + child.warehouse, + child.item_name, + child.stock_qty.as_("qty"), + child.base_amount.as_("amount"), + ) + .where( + (parent.docstatus == 1) + & (parent.name == child.parent) + & (parent[date_field] < self.filters.from_date) + & (parent.company == self.filters.company) + ) ) + if self.filters.item_code: + query = query.where(child.item_code == self.filters.item_code) + + if self.filters.warehouse: + warehouses = get_child_warehouses(self.filters.warehouse) or [] + query = query.where(child.warehouse.isin(warehouses)) + + return query.run(as_dict=True) + def prepare_final_data(self): self.data = [] From 4e31448e3ddd51ad7c5bab053d852a1d2ed7bfc4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Sep 2022 13:11:00 +0530 Subject: [PATCH 196/898] fix: item_code key error in production plan --- erpnext/manufacturing/doctype/production_plan/production_plan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index f1d40c219cb1..4bb4dcc648ef 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -894,6 +894,7 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p .select( (IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"), item.item_name, + item.name.as_("item_code"), bei.description, bei.stock_uom, item.min_order_qty, From bcfa9b859332e81d2aefb799aed11a8a61578720 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Sep 2022 16:34:10 +0530 Subject: [PATCH 197/898] fix: Reduce font size for Process Statement of accounts print/pdf (cherry picked from commit 6bfd193b0d20464f2d3370a850cec6b360e604e1) --- .../process_statement_of_accounts.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 82705a9cea46..0da44a464e76 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -25,7 +25,7 @@

- +
From 21a4556f9919ce52107f7f9798c3e3d206fbadfd Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 22 Sep 2022 11:17:11 +0530 Subject: [PATCH 198/898] refactor: rewrite `Production Planning Report` queries in `QB` (cherry picked from commit 8417b9b99c0730c04e5d4b6194522eab500e4760) --- .../production_planning_report.py | 166 +++++++++--------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 140488820a5c..16c25ce7e6dd 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -4,42 +4,10 @@ import frappe from frappe import _ +from pypika import Order from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses -# and bom_no is not null and bom_no !='' - -mapper = { - "Sales Order": { - "fields": """ item_code as production_item, item_name as production_item_name, stock_uom, - stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse, - `tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """, - "filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty - and `tabSales Order`.per_delivered < 100.0""", - }, - "Material Request": { - "fields": """ item_code as production_item, item_name as production_item_name, stock_uom, - stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse, - `tabMaterial Request Item`.schedule_date """, - "filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100 - and `tabMaterial Request`.material_request_type = 'Manufacture' """, - }, - "Work Order": { - "fields": """ production_item, item_name as production_item_name, planned_start_date, - stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """, - "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')", - }, -} - -order_mapper = { - "Sales Order": { - "Delivery Date": "`tabSales Order Item`.delivery_date asc", - "Total Amount": "`tabSales Order`.base_grand_total desc", - }, - "Material Request": {"Required Date": "`tabMaterial Request Item`.schedule_date asc"}, - "Work Order": {"Planned Start Date": "planned_start_date asc"}, -} - def execute(filters=None): return ProductionPlanReport(filters).execute_report() @@ -63,40 +31,78 @@ def execute_report(self): return self.columns, self.data def get_open_orders(self): - doctype = ( - "`tabWork Order`" - if self.filters.based_on == "Work Order" - else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on) - ) + doctype, order_by = self.filters.based_on, self.filters.order_by + + parent = frappe.qb.DocType(doctype) + query = None + + if doctype == "Work Order": + query = ( + frappe.qb.from_(parent) + .select( + parent.production_item, + parent.item_name.as_("production_item_name"), + parent.planned_start_date, + parent.stock_uom, + parent.qty.as_("qty_to_manufacture"), + parent.name, + parent.bom_no, + parent.fg_warehouse.as_("warehouse"), + ) + .where(parent.status.notin(["Completed", "Stopped"])) + ) - filters = mapper.get(self.filters.based_on)["filters"] - filters = self.prepare_other_conditions(filters, self.filters.based_on) - order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by]) - - self.orders = frappe.db.sql( - """ SELECT {fields} from {doctype} - WHERE {filters} {order_by}""".format( - doctype=doctype, - filters=filters, - order_by=order_by, - fields=mapper.get(self.filters.based_on)["fields"], - ), - tuple(self.filters.docnames), - as_dict=1, - ) + if order_by == "Planned Start Date": + query = query.orderby(parent.planned_start_date, order=Order.asc) + + if self.filters.docnames: + query = query.where(parent.name.isin(self.filters.docnames)) + + else: + child = frappe.qb.DocType(f"{doctype} Item") + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( + child.bom_no, + child.stock_uom, + child.warehouse, + child.parent.as_("name"), + child.item_code.as_("production_item"), + child.stock_qty.as_("qty_to_manufacture"), + child.item_name.as_("production_item_name"), + ) + .where(parent.name == child.parent) + ) + + if self.filters.docnames: + query = query.where(child.parent.isin(self.filters.docnames)) - def prepare_other_conditions(self, filters, doctype): - if self.filters.docnames: - field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype) - filters += " and %s in (%s)" % (field, ",".join(["%s"] * len(self.filters.docnames))) + if doctype == "Sales Order": + query = query.select( + child.delivery_date, + parent.base_grand_total, + ).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0)) - if doctype != "Work Order": - filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype) + if order_by == "Delivery Date": + query = query.orderby(child.delivery_date, order=Order.asc) + elif order_by == "Total Amount": + query = query.orderby(parent.base_grand_total, order=Order.desc) + + elif doctype == "Material Request": + query = query.select(child.schedule_date,).where( + (parent.per_ordered < 100) & (parent.material_request_type == "Manufacture") + ) + + if order_by == "Required Date": + query = query.orderby(child.schedule_date, order=Order.asc) + + query = query.where(parent.docstatus == 1) if self.filters.company: - filters += " and `tab%s`.company = %s" % (doctype, frappe.db.escape(self.filters.company)) + query = query.where(parent.company == self.filters.company) - return filters + self.orders = query.run(as_dict=True) def get_raw_materials(self): if not self.orders: @@ -134,29 +140,29 @@ def get_raw_materials(self): bom_nos.append(bom_no) - bom_doctype = ( + bom_item_doctype = ( "BOM Explosion Item" if self.filters.include_subassembly_raw_materials else "BOM Item" ) - qty_field = ( - "qty_consumed_per_unit" - if self.filters.include_subassembly_raw_materials - else "(bom_item.qty / bom.quantity)" - ) + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType(bom_item_doctype) - raw_materials = frappe.db.sql( - """ SELECT bom_item.parent, bom_item.item_code, - bom_item.item_name as raw_material_name, {0} as required_qty_per_unit - FROM - `tabBOM` as bom, `tab{1}` as bom_item - WHERE - bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1 - """.format( - qty_field, bom_doctype, ",".join(["%s"] * len(bom_nos)) - ), - tuple(bom_nos), - as_dict=1, - ) + if self.filters.include_subassembly_raw_materials: + qty_field = bom_item.qty_consumed_per_unit + else: + qty_field = bom_item.qty / bom.quantity + + raw_materials = ( + frappe.qb.from_(bom) + .from_(bom_item) + .select( + bom_item.parent, + bom_item.item_code, + bom_item.item_name.as_("raw_material_name"), + qty_field.as_("required_qty_per_unit"), + ) + .where((bom_item.parent.isin(bom_nos)) & (bom_item.parent == bom.name) & (bom.docstatus == 1)) + ).run(as_dict=True) if not raw_materials: return From b3897406b08e444c0597e1a27c52fee49e8f2cdc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 18 Sep 2022 16:16:38 +0530 Subject: [PATCH 199/898] fix: difference amount calculation on payment reconciliation (cherry picked from commit 122d5f2729b28105272fd9c714d0a0ca3a8125e5) --- .../payment_reconciliation.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 601fc87a227a..52efd33fefac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -14,6 +14,7 @@ QueryPaymentLedger, get_outstanding_invoices, reconcile_against_document, + update_reference_in_payment_entry, ) from erpnext.controllers.accounts_controller import get_advance_payment_entries @@ -212,6 +213,23 @@ def add_invoice_entries(self, non_reconciled_invoices): inv.currency = entry.get("currency") inv.outstanding_amount = flt(entry.get("outstanding_amount")) + def get_difference_amount(self, allocated_entry): + if allocated_entry.get("reference_type") != "Payment Entry": + return + + dr_or_cr = ( + "credit_in_account_currency" + if erpnext.get_party_account_type(self.party_type) == "Receivable" + else "debit_in_account_currency" + ) + + row = self.get_payment_details(allocated_entry, dr_or_cr) + + doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name) + update_reference_in_payment_entry(row, doc, do_not_save=True) + + return doc.difference_amount + @frappe.whitelist() def allocate_entries(self, args): self.validate_entries() @@ -227,12 +245,16 @@ def allocate_entries(self, args): res = self.get_allocated_entry(pay, inv, pay["amount"]) inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount")) pay["amount"] = 0 + + res.difference_amount = self.get_difference_amount(res) + if pay.get("amount") == 0: entries.append(res) break elif inv.get("outstanding_amount") == 0: entries.append(res) continue + else: break From 24234d584d55e5a5a25d27ac2e072d353e1c03ff Mon Sep 17 00:00:00 2001 From: nishibakabeer <59765279+nishibakabeer@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:40:03 +0400 Subject: [PATCH 200/898] fix: total value in all keys Gross and net profit report showing wrong values in monthly quarterly and half yearly filters which is the total value @ruthra-kumar added in develop branch as suggested ( https://github.com/frappe/erpnext/pull/32020) (cherry picked from commit 6919f389aa704f21a83d6a4e8cb6e8a754582663) --- .../gross_and_net_profit_report/gross_and_net_profit_report.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 9d566785416a..cd5f36670715 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -155,7 +155,6 @@ def adjust_account(data, period_list, consolidated=False): for d in data: for period in period_list: key = period if consolidated else period.key - d[key] = totals[d["account"]] d["total"] = totals[d["account"]] return data From e599b61ac54d9287446287c9c72b1fc436c7731e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Sep 2022 11:37:14 +0530 Subject: [PATCH 201/898] chore: drop 1 runner from stable branch --- .github/workflows/server-tests-mariadb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index 64134bc53970..c4ae61091ff1 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: - container: [1, 2, 3, 4] + container: [1, 2, 3] name: Python Unit Tests From a403e729d68d919dba762c62faacb96acb8b3eaf Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 22 Sep 2022 15:42:46 -0600 Subject: [PATCH 202/898] fix: typo on opportunity summary by sales stage report (cherry picked from commit c4919cf5ec6ab6c3dd60364e82dd072cf6f4b023) --- .../opportunity_summary_by_sales_stage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js index 116db2f5a27b..7cd1710a7f20 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -44,7 +44,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { }, { fieldname: "opportunity_source", - label: __("Oppoturnity Source"), + label: __("Opportunity Source"), fieldtype: "Link", options: "Lead Source", }, @@ -62,4 +62,4 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { default: frappe.defaults.get_user_default("Company") } ] -}; \ No newline at end of file +}; From 2eeff8a00bdac96b1df7e2131ce568ba9d99885f Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 10:13:30 -0600 Subject: [PATCH 203/898] fix: Add strings to translate function (cherry picked from commit 9decebe6e1960687934dc0b99535019c547889c0) --- erpnext/projects/report/project_summary/project_summary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index 606c0c2d81d7..7a35fd236a01 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -91,9 +91,9 @@ def get_chart_data(data): "data": { "labels": labels[:30], "datasets": [ - {"name": "Overdue", "values": overdue[:30]}, - {"name": "Completed", "values": completed[:30]}, - {"name": "Total Tasks", "values": total[:30]}, + {"name": _("Overdue"), "values": overdue[:30]}, + {"name": _("Completed"), "values": completed[:30]}, + {"name": _("Total Tasks"), "values": total[:30]}, ], }, "type": "bar", From e464de3c5dbce204efe3ee60d3a3a12500bdcdf4 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 11:55:13 -0600 Subject: [PATCH 204/898] fix: add translate function to strings (cherry picked from commit aa49ec815a3837ff5777903f929477b836d92472) --- .../report/work_order_summary/work_order_summary.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index 2368bfdf2c6f..41ffcbb19043 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -83,6 +83,7 @@ def get_chart_based_on_status(data): for d in data: status_wise_data[d.status] += 1 + labels = [_(label) for label in labels] values = [status_wise_data[label] for label in labels] chart = { @@ -95,7 +96,7 @@ def get_chart_based_on_status(data): def get_chart_based_on_age(data): - labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"] + labels = [_("0-30 Days"), _("30-60 Days"), _("60-90 Days"), _("90 Above")] age_wise_data = {"0-30 Days": 0, "30-60 Days": 0, "60-90 Days": 0, "90 Above": 0} @@ -135,8 +136,8 @@ def get_chart_based_on_qty(data, filters): pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Pending", "values": pending}) - datasets.append({"name": "Completed", "values": completed}) + datasets.append({"name": _("Pending"), "values": pending}) + datasets.append({"name": _("Completed"), "values": completed}) chart = { "data": {"labels": labels, "datasets": datasets}, From f5a3f8b48649a0cc4bd650c0eee214b4a127fcc9 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 11:56:42 -0600 Subject: [PATCH 205/898] fix: add translate function to strings (cherry picked from commit 94199b78675e8d20c98bf9d7d95fb3d6d7d2b831) --- .../manufacturing/report/job_card_summary/job_card_summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 5083b7369dec..63c2d97d574b 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -85,8 +85,8 @@ def get_chart_data(job_card_details, filters): open_job_cards.append(periodic_data.get("Open").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Open", "values": open_job_cards}) - datasets.append({"name": "Completed", "values": completed}) + datasets.append({"name": _("Open"), "values": open_job_cards}) + datasets.append({"name": _("Completed"), "values": completed}) chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"} From f1d774034cecfeebe9ac92e96391baf3a80710e5 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 09:40:49 +0530 Subject: [PATCH 206/898] refactor: rewrite `Item Price Stock Report` queries in `QB` (cherry picked from commit 22299d2382c912e8bc8dddca3af7d4cf94374339) --- .../item_price_stock/item_price_stock.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index 15218e63a876..1b07f596c7b3 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -62,22 +62,28 @@ def get_data(filters, columns): def get_item_price_qty_data(filters): - conditions = "" - if filters.get("item_code"): - conditions += "where a.item_code=%(item_code)s" - - item_results = frappe.db.sql( - """select a.item_code, a.item_name, a.name as price_list_name, - a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty - from `tabItem Price` a left join `tabBin` b - ON a.item_code = b.item_code - {conditions}""".format( - conditions=conditions - ), - filters, - as_dict=1, + item_price = frappe.qb.DocType("Item Price") + bin = frappe.qb.DocType("Bin") + + query = ( + frappe.qb.from_(item_price) + .left_join(bin) + .on(item_price.item_code == bin.item_code) + .select( + item_price.item_code, + item_price.item_name, + item_price.name.as_("price_list_name"), + item_price.brand.as_("brand"), + bin.warehouse.as_("warehouse"), + bin.actual_qty.as_("actual_qty"), + ) ) + if filters.get("item_code"): + query = query.where(item_price.item_code == filters.get("item_code")) + + item_results = query.run(as_dict=True) + price_list_names = list(set(item.price_list_name for item in item_results)) buying_price_map = get_price_map(price_list_names, buying=1) From 8c6ddb67219d49e573e7e50a49c310a4f117e2e2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 09:44:29 +0530 Subject: [PATCH 207/898] refactor: rewrite `Incorrect Stock Value Report` queries in `QB` (cherry picked from commit b93331e844376fb5207acb41b4e7795ea6e1f059) --- .../incorrect_stock_value_report.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index 23e3c8a97f55..df01b14d11a6 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder import Field +from frappe.query_builder.functions import Min, Timestamp from frappe.utils import add_days, getdate, today import erpnext @@ -28,7 +30,7 @@ def execute(filters=None): def get_unsync_date(filters): date = filters.from_date if not date: - date = frappe.db.sql(""" SELECT min(posting_date) from `tabStock Ledger Entry`""") + date = (frappe.qb.from_("Stock Ledger Entry").select(Min(Field("posting_date")))).run() date = date[0][0] if not date: @@ -54,22 +56,27 @@ def get_data(report_filters): result = [] voucher_wise_dict = {} - data = frappe.db.sql( - """ - SELECT - name, posting_date, posting_time, voucher_type, voucher_no, - stock_value_difference, stock_value, warehouse, item_code - FROM - `tabStock Ledger Entry` - WHERE - posting_date - = %s and company = %s - and is_cancelled = 0 - ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, - (from_date, report_filters.company), - as_dict=1, - ) + sle = frappe.qb.DocType("Stock Ledger Entry") + data = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.voucher_type, + sle.voucher_no, + sle.stock_value_difference, + sle.stock_value, + sle.warehouse, + sle.item_code, + ) + .where( + (sle.posting_date == from_date) + & (sle.company == report_filters.company) + & (sle.is_cancelled == 0) + ) + .orderby(Timestamp(sle.posting_date, sle.posting_time), sle.creation) + ).run(as_dict=True) for d in data: voucher_wise_dict.setdefault((d.item_code, d.warehouse), []).append(d) From 931aa620725aa8c8095bca6fafb35fd08229506c Mon Sep 17 00:00:00 2001 From: hendrik Date: Mon, 26 Sep 2022 19:17:24 +0700 Subject: [PATCH 208/898] fix: report chart field type * fix: report chart field type Co-authored-by: Sagar Sharma Co-authored-by: Deepesh Garg (cherry picked from commit f41d1500b0ce024e1321df8de9aab6c4d15be9f1) --- .../buying/report/purchase_order_trends/purchase_order_trends.py | 1 + .../stock/report/delivery_note_trends/delivery_note_trends.py | 1 + .../report/purchase_receipt_trends/purchase_receipt_trends.py | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py index dbdc62e9ec74..d089473a16ae 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py @@ -53,4 +53,5 @@ def get_chart_data(data, conditions, filters): }, "type": "line", "lineOptions": {"regionFill": 1}, + "fieldtype": "Currency", } diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py index 7a1b8c0cee91..0ec4e1ce957a 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py @@ -45,4 +45,5 @@ def get_chart_data(data, filters): "datasets": [{"name": _("Total Delivered Amount"), "values": datapoints}], }, "type": "bar", + "fieldtype": "Currency", } diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py index fe2d55a3913d..b62a6ee6fd8f 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py @@ -46,4 +46,5 @@ def get_chart_data(data, filters): }, "type": "bar", "colors": ["#5e64ff"], + "fieldtype": "Currency", } From a83e36b8013f49b74cad41d90d9d0f685bc691e6 Mon Sep 17 00:00:00 2001 From: Dhananjay Palshikar Date: Thu, 1 Sep 2022 19:07:13 +0530 Subject: [PATCH 209/898] Allow Item Templates in Work Order Items Item Variants can be a part of the items tables, however, when BOM items are fetched to be a part of the Work Order items, item variants were being filtered out. The filtering out does not serve a purpose. Having Item variants in BOMs allows for template like behaviour. (cherry picked from commit 75396c02d28c37eeb44d2429e51d3b9f55a39fe9) --- erpnext/manufacturing/doctype/bom/bom.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 70637d3ef206..ff84991c36e5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1019,7 +1019,6 @@ def get_bom_items_as_dict( where bom_item.docstatus < 2 and bom.name = %(bom)s - and ifnull(item.has_variants, 0) = 0 and item.is_stock_item in (1, {is_stock_item}) {where_conditions} group by item_code, stock_uom From 2fd0291afbae4c36c4d1204c616e388340207ed9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Sep 2022 15:21:09 +0530 Subject: [PATCH 210/898] fix: opening entry causing discepancy between stock and trial balance (cherry picked from commit bc3ab45af2be165988b143b62318d865f83ee673) --- erpnext/accounts/report/general_ledger/general_ledger.py | 4 ++-- erpnext/accounts/report/trial_balance/trial_balance.py | 3 ++- .../trial_balance_for_party/trial_balance_for_party.py | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e77e828e1664..82f38dacd2af 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -237,9 +237,9 @@ def get_conditions(filters): or filters.get("party") or filters.get("group_by") in ["Group by Account", "Group by Party"] ): - conditions.append("posting_date >=%(from_date)s") + conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')") - conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')") + conditions.append("(posting_date <=%(to_date)s)") if filters.get("project"): conditions.append("project in %(project)s") diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 6bd08ad837a1..6d2cd8ed4112 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type): query_filters = { "company": filters.company, "from_date": filters.from_date, + "to_date": filters.to_date, "report_type": report_type, "year_start_date": filters.year_start_date, "project": filters.project, @@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type): where company=%(company)s {additional_conditions} - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) and account in (select name from `tabAccount` where report_type=%(report_type)s) and is_cancelled = 0 group by account""".format( diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index 5fcfdff6f1a2..ee223484d478 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -104,12 +104,17 @@ def get_opening_balances(filters): where company=%(company)s and is_cancelled=0 and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) {account_filter} group by party""".format( account_filter=account_filter ), - {"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type}, + { + "company": filters.company, + "from_date": filters.from_date, + "to_date": filters.to_date, + "party_type": filters.party_type, + }, as_dict=True, ) From 305eb02bfcd337e1260acb76e3f2c9571d4e074e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Sep 2022 13:47:54 +0530 Subject: [PATCH 211/898] fix: allow to return expired batches using purchase return (cherry picked from commit a4a86ee23fb36f448a308c4f3566ba4be05697ab) --- .../purchase_invoice/test_purchase_invoice.py | 31 +++++++++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 31 +++++++++++++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 3 ++ 3 files changed, 65 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0a4f25b87699..f901257ccf63 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1543,6 +1543,37 @@ def test_item_less_defaults(self): pi.save() self.assertEqual(pi.items[0].conversion_factor, 1000) + def test_batch_expiry_for_purchase_invoice(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = self.make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_invoice( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b77c3a513487..62697244babc 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1241,6 +1241,37 @@ def test_backdated_transaction_for_internal_transfer(self): self.assertEqual(query[0].value, 0) + def test_batch_expiry_for_purchase_receipt(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_receipt( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier 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 329cd7da09bc..f7f8cbe4ee08 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -153,6 +153,9 @@ def scrub_posting_time(self): def validate_batch(self): if self.batch_no and self.voucher_type != "Stock Entry": + if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0: + return + expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date") if expiry_date: if getdate(self.posting_date) > getdate(expiry_date): From a28ae4d6534f40cb4b7844c09998e9589e4d4fb5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:07:38 +0530 Subject: [PATCH 212/898] fix: Scrap Asset Accounting Dimensions (backport #31949) (#32376) fix: Scrap Asset Accounting Dimensions (#31949) * fix: Scrap Asset Accounting Dimensions (cherry picked from commit c760ca232365b8e868c8420f877c9c744ec2b4e7) Co-authored-by: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> --- .../doctype/sales_invoice/sales_invoice.py | 4 +- erpnext/assets/doctype/asset/asset.js | 2 +- erpnext/assets/doctype/asset/depreciation.py | 110 ++++++++++++------ .../asset_capitalization.py | 6 +- 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e51938b27f5d..94db9a9adc43 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1081,7 +1081,7 @@ def make_item_gl_entries(self, gl_entries): if self.is_return: fixed_asset_gl_entries = get_gl_entries_on_asset_regain( - asset, item.base_net_amount, item.finance_book + asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", None) @@ -1091,7 +1091,7 @@ def make_item_gl_entries(self, gl_entries): else: fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, item.base_net_amount, item.finance_book + asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", self.posting_date) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index a43a16c9ec52..5512d4159d86 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -230,7 +230,7 @@ frappe.ui.form.on('Asset', { datasets: [{ color: 'green', values: asset_values, - formatted: asset_values.map(d => d.toFixed(2)) + formatted: asset_values.map(d => d?.toFixed(2)) }] }, type: 'line' diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 74386384c5d6..a4bb960d8e11 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -235,7 +235,9 @@ def restore_asset(asset_name): asset.set_status() -def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_regain( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): ( fixed_asset_account, asset, @@ -247,28 +249,45 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): ) = get_asset_details(asset, finance_book) gl_entries = [ - { - "account": fixed_asset_account, - "debit_in_account_currency": asset.gross_purchase_amount, - "debit": asset.gross_purchase_amount, - "cost_center": depreciation_cost_center, - }, - { - "account": accumulated_depr_account, - "credit_in_account_currency": accumulated_depr_amount, - "credit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - }, + asset.get_gl_dict( + { + "account": fixed_asset_account, + "debit_in_account_currency": asset.gross_purchase_amount, + "debit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "credit_in_account_currency": accumulated_depr_amount, + "credit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), ] profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount)) if profit_amount: - get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center + ) + + if voucher_type and voucher_no: + for entry in gl_entries: + entry["voucher_type"] = voucher_type + entry["voucher_no"] = voucher_no return gl_entries -def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_disposal( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): ( fixed_asset_account, asset, @@ -280,23 +299,38 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) ) = get_asset_details(asset, finance_book) gl_entries = [ - { - "account": fixed_asset_account, - "credit_in_account_currency": asset.gross_purchase_amount, - "credit": asset.gross_purchase_amount, - "cost_center": depreciation_cost_center, - }, - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - }, + asset.get_gl_dict( + { + "account": fixed_asset_account, + "credit_in_account_currency": asset.gross_purchase_amount, + "credit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), ] profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: - get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center + ) + + if voucher_type and voucher_no: + for entry in gl_entries: + entry["voucher_type"] = voucher_type + entry["voucher_no"] = voucher_no return gl_entries @@ -333,15 +367,21 @@ def get_asset_details(asset, finance_book=None): ) -def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center): +def get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center +): debit_or_credit = "debit" if profit_amount < 0 else "credit" gl_entries.append( - { - "account": disposal_account, - "cost_center": depreciation_cost_center, - debit_or_credit: abs(profit_amount), - debit_or_credit + "_in_account_currency": abs(profit_amount), - } + asset.get_gl_dict( + { + "account": disposal_account, + "cost_center": depreciation_cost_center, + debit_or_credit: abs(profit_amount), + debit_or_credit + "_in_account_currency": abs(profit_amount), + "posting_date": getdate(), + }, + item=asset, + ) ) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 2e6f0ad7b020..93194c0333ad 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -428,7 +428,11 @@ def get_gl_entries_for_consumed_asset_items( asset.reload() fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, item.asset_value, item.get("finance_book") or self.get("finance_book") + asset, + item.asset_value, + item.get("finance_book") or self.get("finance_book"), + self.get("doctype"), + self.get("name"), ) asset.db_set("disposal_date", self.posting_date) From 4b6e1f489213e4c911d2ee367ae0b4e558c2d93f Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 27 Sep 2022 12:00:50 +0530 Subject: [PATCH 213/898] fix: `For Quantity` error msg in `Stock Entry` (cherry picked from commit 9049db41ae8793cdcb5fb770fe202c096c9ad53c) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 738ac330e395..8bcd772d9099 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1073,8 +1073,8 @@ def validate_finished_goods(self): # No work order could mean independent Manufacture entry, if so skip validation if self.work_order and self.fg_completed_qty > allowed_qty: frappe.throw( - _("For quantity {0} should not be greater than work order quantity {1}").format( - flt(self.fg_completed_qty), wo_qty + _("For quantity {0} should not be greater than allowed quantity {1}").format( + flt(self.fg_completed_qty), allowed_qty ) ) From 4a29c7fafb2401c7dca6d388f36c394ec9554781 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 27 Sep 2022 15:30:08 +0530 Subject: [PATCH 214/898] fix: consider overproduction percentage for WO finish button (cherry picked from commit 05392e0918fce4aafffc016f246f21c8afcc5d8a) --- .../doctype/work_order/work_order.js | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 20f15039efee..f3640b93b22a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -557,37 +557,52 @@ erpnext.work_order = { if(!frm.doc.skip_transfer){ // If "Material Consumption is check in Manufacturing Settings, allow Material Consumption - if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) - && frm.doc.status != 'Stopped') { - frm.has_finish_btn = true; - - if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { - // Only show "Material Consumption" when required_qty > consumed_qty - var counter = 0; - var tbl = frm.doc.required_items || []; - var tbl_lenght = tbl.length; - for (var i = 0, len = tbl_lenght; i < len; i++) { - let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; - if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { - counter += 1; + if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') { + if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { + frm.has_finish_btn = true; + + if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { + // Only show "Material Consumption" when required_qty > consumed_qty + var counter = 0; + var tbl = frm.doc.required_items || []; + var tbl_lenght = tbl.length; + for (var i = 0, len = tbl_lenght; i < len; i++) { + let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; + if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { + counter += 1; + } + } + if (counter > 0) { + var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { + const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; + erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); + }); + consumption_btn.addClass('btn-primary'); } } - if (counter > 0) { - var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { - const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; - erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); - }); - consumption_btn.addClass('btn-primary'); - } - } - var finish_btn = frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); + var finish_btn = frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); - if(doc.material_transferred_for_manufacturing>=doc.qty) { - // all materials transferred for manufacturing, make this primary - finish_btn.addClass('btn-primary'); + if(doc.material_transferred_for_manufacturing>=doc.qty) { + // all materials transferred for manufacturing, make this primary + finish_btn.addClass('btn-primary'); + } + } else { + frappe.db.get_doc("Manufacturing Settings").then((doc) => { + let allowance_percentage = doc.overproduction_percentage_for_work_order; + + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); + + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + } + } + }); } } } else { From 32ad36ad0f5bfe36d76adfbc7d07387edcf1f7c3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 15:37:41 +0530 Subject: [PATCH 215/898] fix: Add return against indexes for POS Invoice (cherry picked from commit 1f6205e1ea569dd25ac298f10812f6e9f44409ef) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 2 +- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index b126d57400a9..6f8b3822c2e1 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1553,7 +1553,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-03-22 13:00:24.166684", + "modified": "2022-09-27 13:00:24.166684", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 6e3a0766f10c..a5e4d9a6dd39 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -740,3 +740,7 @@ def append_payment(payment_mode): ]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) append_payment(payment_mode[0]) + + +def on_doctype_update(): + frappe.db.add_index("POS Invoice", ["customer", "is_return", "return_against"]) From 3abe4a16af88c0e0e223399a29dbdd3c417d04f3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 16:22:59 +0530 Subject: [PATCH 216/898] fix: Add return against indexes for POS Invoice (cherry picked from commit cbfe28286acc9319901aeaa2c77fa50e95c0e481) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index a5e4d9a6dd39..9eded8a77702 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -743,4 +743,4 @@ def append_payment(payment_mode): def on_doctype_update(): - frappe.db.add_index("POS Invoice", ["customer", "is_return", "return_against"]) + frappe.db.add_index("POS Invoice", ["return_against"]) From b2f280dee60a5b266a0ea0b685716ce5794935e6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 22:12:32 +0530 Subject: [PATCH 217/898] fix: Move subscription process to hourly long quque (cherry picked from commit 82a2f31ada358d896943d04bdb517ac33b9e2d7a) --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a08feb44476d..6ef77f3f5ba6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -391,12 +391,12 @@ "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts", ], "hourly": [ - "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization", "erpnext.projects.doctype.project.project.hourly_reminder", "erpnext.projects.doctype.project.project.collect_project_status", ], "hourly_long": [ + "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", ], From 29c4a32c335cae5fd2e2de8bc84bd028b5f2c217 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 27 Sep 2022 15:44:38 +0530 Subject: [PATCH 218/898] fix: POS only validate QTY if is_stock_item POS invoice raised " Item not available " validation error even though item is non_stock. (cherry picked from commit e39e088f18177ab4a189da60c18d57b92a6f8aff) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 9eded8a77702..39ddeb0c854a 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -239,14 +239,14 @@ def validate_stock_availablility(self): frappe.bold(d.warehouse), frappe.bold(d.qty), ) - if flt(available_stock) <= 0: + if is_stock_item and flt(available_stock) <= 0: frappe.throw( _("Row #{}: Item Code: {} is not available under warehouse {}.").format( d.idx, item_code, warehouse ), title=_("Item Unavailable"), ) - elif flt(available_stock) < flt(d.qty): + elif is_stock_item and flt(available_stock) < flt(d.qty): frappe.throw( _( "Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}." From 6b55a01834ff66ac0ee3ecb3aba17969d14b6d17 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 27 Sep 2022 23:44:56 +0530 Subject: [PATCH 219/898] fix: POS properly validate stock for bundle products Stock availability was not calculated properly for Product Bundle with non stock item so i have added logic to properly calculate that as well. (cherry picked from commit e392ea1104fee5add5c893c4e092edb6ad21f486) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 9 ++++++--- erpnext/selling/page/point_of_sale/pos_controller.js | 4 ++-- erpnext/selling/page/point_of_sale/pos_item_details.js | 5 +++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 39ddeb0c854a..fbe0ef39f839 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -632,11 +632,12 @@ def get_stock_availability(item_code, warehouse): pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) return bin_qty - pos_sales_qty, is_stock_item else: - is_stock_item = False + is_stock_item = True if frappe.db.exists("Product Bundle", item_code): return get_bundle_availability(item_code, warehouse), is_stock_item else: - # Is a service item + is_stock_item = False + # Is a service item or non_stock item return 0, is_stock_item @@ -650,7 +651,9 @@ def get_bundle_availability(bundle_item_code, warehouse): available_qty = item_bin_qty - item_pos_reserved_qty max_available_bundles = available_qty / item.qty - if bundle_bin_qty > max_available_bundles: + if bundle_bin_qty > max_available_bundles and frappe.get_value( + "Item", item.item_code, "is_stock_item" + ): bundle_bin_qty = max_available_bundles pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index da7576e08def..24375d8252da 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -660,7 +660,7 @@ erpnext.PointOfSale.Controller = class { } else { return; } - } else if (available_qty < qty_needed) { + } else if (is_stock_item && available_qty < qty_needed) { frappe.throw({ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]), indicator: 'orange' @@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class { callback(res) { if (!me.item_stock_map[item_code]) me.item_stock_map[item_code] = {}; - me.item_stock_map[item_code][warehouse] = res.message[0]; + me.item_stock_map[item_code][warehouse] = res.message; } }); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index b75ffb235ed7..f9b5bb2e4529 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -242,13 +242,14 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0]; + const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]); if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse me.warehouse_control.set_value(this.value); }) - } else if (available_qty === 0) { + } else if (available_qty === 0 && is_stock_item) { me.warehouse_control.set_value(''); const bold_item_code = me.item_row.item_code.bold(); const bold_warehouse = this.value.bold(); From f5f13c4611458121d4c26acb913fe557933b9046 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 13:15:34 +0530 Subject: [PATCH 220/898] fix: calculate depreciation properly on asset sale entry and scrap entry (cherry picked from commit ff5cad1cd617a23d6ffc9903f29d713a8db8d949) # Conflicts: # erpnext/assets/doctype/asset/depreciation.py --- .../doctype/sales_invoice/sales_invoice.py | 15 ++- erpnext/assets/doctype/asset/depreciation.py | 93 ++++++++++++++++++- erpnext/controllers/accounts_controller.py | 83 ----------------- 3 files changed, 101 insertions(+), 90 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 94db9a9adc43..afd5a59df4e0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -22,9 +22,12 @@ from erpnext.accounts.party import get_due_date, get_party_account, get_party_details from erpnext.accounts.utils import get_account_currency from erpnext.assets.doctype.asset.depreciation import ( + depreciate_asset, get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, + reset_depreciation_schedule, + reverse_depreciation_entry_made_after_disposal, ) from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.selling_controller import SellingController @@ -1086,18 +1089,20 @@ def make_item_gl_entries(self, gl_entries): asset.db_set("disposal_date", None) if asset.calculate_depreciation: - self.reverse_depreciation_entry_made_after_disposal(asset) - self.reset_depreciation_schedule(asset) + posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") + reverse_depreciation_entry_made_after_disposal(asset, posting_date) + reset_depreciation_schedule(asset, self.posting_date) else: + if asset.calculate_depreciation: + depreciate_asset(asset, self.posting_date) + asset.reload() + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", self.posting_date) - if asset.calculate_depreciation: - self.depreciate_asset(asset) - for gle in fixed_asset_gl_entries: gle["against"] = self.customer gl_entries.append(self.get_gl_dict(gle, item=item)) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index a4bb960d8e11..4fe7381854fb 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -4,11 +4,12 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, getdate, today +from frappe.utils import add_months, cint, flt, getdate, nowdate, today from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) +from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry def post_depreciation_entries(date=None, commit=True): @@ -196,6 +197,11 @@ def scrap_asset(asset_name): _("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status) ) + date = today() + + depreciate_asset(asset, date) + asset.reload() + depreciation_series = frappe.get_cached_value( "Company", asset.company, "series_for_depreciation_entry" ) @@ -203,7 +209,7 @@ def scrap_asset(asset_name): je = frappe.new_doc("Journal Entry") je.voucher_type = "Journal Entry" je.naming_series = depreciation_series - je.posting_date = today() + je.posting_date = date je.company = asset.company je.remark = "Scrap Entry for asset {0}".format(asset_name) @@ -225,6 +231,9 @@ def scrap_asset(asset_name): def restore_asset(asset_name): asset = frappe.get_doc("Asset", asset_name) + reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date) + reset_depreciation_schedule(asset, asset.disposal_date) + je = asset.journal_entry_for_scrap asset.db_set("disposal_date", None) @@ -235,9 +244,89 @@ def restore_asset(asset_name): asset.set_status() +<<<<<<< HEAD def get_gl_entries_on_asset_regain( asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None ): +======= +def depreciate_asset(asset, date): + asset.flags.ignore_validate_update_after_submit = True + asset.prepare_depreciation_data(date_of_disposal=date) + asset.save() + + make_depreciation_entry(asset.name, date) + + +def reset_depreciation_schedule(asset, date): + asset.flags.ignore_validate_update_after_submit = True + + # recreate original depreciation schedule of the asset + asset.prepare_depreciation_data(date_of_return=date) + + modify_depreciation_schedule_for_asset_repairs(asset) + asset.save() + + +def modify_depreciation_schedule_for_asset_repairs(asset): + asset_repairs = frappe.get_all( + "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"] + ) + + for repair in asset_repairs: + if repair.increase_in_asset_life: + asset_repair = frappe.get_doc("Asset Repair", repair.name) + asset_repair.modify_depreciation_schedule() + asset.prepare_depreciation_data() + + +def reverse_depreciation_entry_made_after_disposal(asset, date): + row = -1 + finance_book = asset.get("schedules")[0].get("finance_book") + for schedule in asset.get("schedules"): + if schedule.finance_book != finance_book: + row = 0 + finance_book = schedule.finance_book + else: + row += 1 + + if schedule.schedule_date == date: + if not disposal_was_made_on_original_schedule_date( + asset, schedule, row, date + ) or disposal_happens_in_the_future(date): + + reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) + reverse_journal_entry.posting_date = nowdate() + frappe.flags.is_reverse_depr_entry = True + reverse_journal_entry.submit() + + frappe.flags.is_reverse_depr_entry = False + asset.flags.ignore_validate_update_after_submit = True + schedule.journal_entry = None + asset.save() + + +# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone +def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal): + for finance_book in asset.get("finance_books"): + if schedule.finance_book == finance_book.finance_book: + orginal_schedule_date = add_months( + finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation) + ) + + if orginal_schedule_date == posting_date_of_disposal: + return True + return False + + +def disposal_happens_in_the_future(posting_date_of_disposal): + if posting_date_of_disposal > getdate(): + return True + + return False + + +def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): +>>>>>>> ff5cad1cd6 (fix: calculate depreciation properly on asset sale entry and scrap entry) ( fixed_asset_account, asset, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 8686cb5cc094..22291a354411 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -38,7 +38,6 @@ validate_party_frozen_disabled, ) from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year -from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.print_settings import ( set_print_templates_for_item_table, @@ -1891,88 +1890,6 @@ def check_finance_books(self, item, asset): _("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx) ) - def depreciate_asset(self, asset): - asset.flags.ignore_validate_update_after_submit = True - asset.prepare_depreciation_data(date_of_disposal=self.posting_date) - asset.save() - - make_depreciation_entry(asset.name, self.posting_date) - - def reset_depreciation_schedule(self, asset): - asset.flags.ignore_validate_update_after_submit = True - - # recreate original depreciation schedule of the asset - asset.prepare_depreciation_data(date_of_return=self.posting_date) - - self.modify_depreciation_schedule_for_asset_repairs(asset) - asset.save() - - def modify_depreciation_schedule_for_asset_repairs(self, asset): - asset_repairs = frappe.get_all( - "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"] - ) - - for repair in asset_repairs: - if repair.increase_in_asset_life: - asset_repair = frappe.get_doc("Asset Repair", repair.name) - asset_repair.modify_depreciation_schedule() - asset.prepare_depreciation_data() - - def reverse_depreciation_entry_made_after_disposal(self, asset): - from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry - - posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry() - - row = -1 - finance_book = asset.get("schedules")[0].get("finance_book") - for schedule in asset.get("schedules"): - if schedule.finance_book != finance_book: - row = 0 - finance_book = schedule.finance_book - else: - row += 1 - - if schedule.schedule_date == posting_date_of_original_disposal: - if not self.disposal_was_made_on_original_schedule_date( - asset, schedule, row, posting_date_of_original_disposal - ) or self.disposal_happens_in_the_future(posting_date_of_original_disposal): - - reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) - reverse_journal_entry.posting_date = nowdate() - frappe.flags.is_reverse_depr_entry = True - reverse_journal_entry.submit() - - frappe.flags.is_reverse_depr_entry = False - asset.flags.ignore_validate_update_after_submit = True - schedule.journal_entry = None - asset.save() - - def get_posting_date_of_disposal_entry(self): - if self.doctype == "Sales Invoice" and self.return_against: - return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") - else: - return self.posting_date - - # if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone - def disposal_was_made_on_original_schedule_date( - self, asset, schedule, row, posting_date_of_disposal - ): - for finance_book in asset.get("finance_books"): - if schedule.finance_book == finance_book.finance_book: - orginal_schedule_date = add_months( - finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation) - ) - - if orginal_schedule_date == posting_date_of_disposal: - return True - return False - - def disposal_happens_in_the_future(self, posting_date_of_disposal): - if posting_date_of_disposal > getdate(): - return True - - return False - @frappe.whitelist() def get_tax_rate(account_head): From 671033755f2deedda583c51766f853fc0d1ace61 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 22:43:18 +0530 Subject: [PATCH 221/898] fix: asset tests (cherry picked from commit 11ac20e5ee1f9ba00bbccd15388144dc4b4eea83) --- erpnext/assets/doctype/asset/depreciation.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 52 ++++++++++++++------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 4fe7381854fb..98cf8a777663 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -220,7 +220,7 @@ def scrap_asset(asset_name): je.flags.ignore_permissions = True je.submit() - frappe.db.set_value("Asset", asset_name, "disposal_date", today()) + frappe.db.set_value("Asset", asset_name, "disposal_date", date) frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name) asset.set_status("Scrapped") diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index e7af9bd5bc25..bdda509e91de 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -4,7 +4,16 @@ import unittest import frappe -from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate +from frappe.utils import ( + add_days, + add_months, + cstr, + flt, + get_first_day, + get_last_day, + getdate, + nowdate, +) from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset @@ -178,17 +187,20 @@ def test_is_fixed_asset_set(self): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): + date = nowdate() + purchase_date = add_months(get_first_day(date), -2) + asset = create_asset( calculate_depreciation=1, - available_for_use_date="2020-01-01", - purchase_date="2020-01-01", + available_for_use_date=purchase_date, + purchase_date=purchase_date, expected_value_after_useful_life=10000, total_number_of_depreciations=10, frequency_of_depreciation=1, submit=1, ) - post_depreciation_entries(date=add_months("2020-01-01", 4)) + post_depreciation_entries(date=add_months(purchase_date, 2)) scrap_asset(asset.name) @@ -196,10 +208,15 @@ def test_scrap_asset(self): self.assertEqual(asset.status, "Scrapped") self.assertTrue(asset.journal_entry_for_scrap) + pro_rata_amount, _, _ = asset.get_pro_rata_amt( + asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date + ) + pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 36000.0, 0.0), + ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0), ) gle = frappe.db.sql( @@ -217,18 +234,20 @@ def test_scrap_asset(self): self.assertEqual(asset.status, "Partially Depreciated") def test_gle_made_by_asset_sale(self): + date = nowdate() + purchase_date = add_months(get_first_day(date), -2) + asset = create_asset( calculate_depreciation=1, - available_for_use_date="2020-06-06", - purchase_date="2020-01-01", + available_for_use_date=purchase_date, + purchase_date=purchase_date, expected_value_after_useful_life=10000, - total_number_of_depreciations=3, - frequency_of_depreciation=10, - depreciation_start_date="2020-12-31", + total_number_of_depreciations=10, + frequency_of_depreciation=1, submit=1, ) - post_depreciation_entries(date="2021-01-01") + post_depreciation_entries(date=add_months(purchase_date, 2)) si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") si.customer = "_Test Customer" @@ -239,10 +258,15 @@ def test_gle_made_by_asset_sale(self): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + pro_rata_amount, _, _ = asset.get_pro_rata_amt( + asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date + ) + pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 20490.2, 0.0), + ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0), ("Debtors - _TC", 25000.0, 0.0), ) From bad3b330f4eeb89761dcf621cfe3f95644cef030 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 22:52:25 +0530 Subject: [PATCH 222/898] fix: refactor asset capitilization (cherry picked from commit 5a8b28c1943d24b7aedf7b96c57fe93d2b5ea5f6) --- .../doctype/asset_capitalization/asset_capitalization.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 93194c0333ad..08355f047e56 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -12,8 +12,11 @@ import erpnext from erpnext.assets.doctype.asset.depreciation import ( + depreciate_asset, get_gl_entries_on_asset_disposal, get_value_after_depreciation_on_disposal_date, + reset_depreciation_schedule, + reverse_depreciation_entry_made_after_disposal, ) from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( @@ -424,7 +427,7 @@ def get_gl_entries_for_consumed_asset_items( asset = self.get_asset(item) if asset.calculate_depreciation: - self.depreciate_asset(asset) + depreciate_asset(asset, self.posting_date) asset.reload() fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( @@ -520,8 +523,8 @@ def update_target_asset(self): self.set_consumed_asset_status(asset) if asset.calculate_depreciation: - self.reverse_depreciation_entry_made_after_disposal(asset) - self.reset_depreciation_schedule(asset) + reverse_depreciation_entry_made_after_disposal(asset, self.posting_date) + reset_depreciation_schedule(asset, self.posting_date) def get_asset(self, item): asset = frappe.get_doc("Asset", item.asset) From 86052450a56eb4786bbc9caacecae9add2ae7386 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 23:25:04 +0530 Subject: [PATCH 223/898] fix: fix restore asset value after depreciation (cherry picked from commit 43a3400221118cb6ad86c6ef15e201d07bc1941e) --- erpnext/assets/doctype/asset/depreciation.py | 9 ++++++++ erpnext/assets/doctype/asset/test_asset.py | 24 +++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 98cf8a777663..5ee48a210067 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -302,9 +302,18 @@ def reverse_depreciation_entry_made_after_disposal(asset, date): frappe.flags.is_reverse_depr_entry = False asset.flags.ignore_validate_update_after_submit = True schedule.journal_entry = None + depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry) + asset.finance_books[0].value_after_depreciation += depreciation_amount asset.save() +def get_depreciation_amount_in_je(journal_entry): + if journal_entry.accounts[0].debit_in_account_currency: + return journal_entry.accounts[0].debit_in_account_currency + else: + return journal_entry.accounts[0].credit_in_account_currency + + # if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal): for finance_book in asset.get("finance_books"): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index bdda509e91de..f72b5249a441 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -201,17 +201,29 @@ def test_scrap_asset(self): ) post_depreciation_entries(date=add_months(purchase_date, 2)) + asset.load_from_db() - scrap_asset(asset.name) + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), + ) + self.assertEquals(accumulated_depr_amount, 18000.0) + scrap_asset(asset.name) asset.load_from_db() - self.assertEqual(asset.status, "Scrapped") - self.assertTrue(asset.journal_entry_for_scrap) + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), + ) pro_rata_amount, _, _ = asset.get_pro_rata_amt( asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount) + + self.assertEqual(asset.status, "Scrapped") + self.assertTrue(asset.journal_entry_for_scrap) expected_gle = ( ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0), @@ -233,6 +245,12 @@ def test_scrap_asset(self): self.assertFalse(asset.journal_entry_for_scrap) self.assertEqual(asset.status, "Partially Depreciated") + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), + ) + self.assertEquals(accumulated_depr_amount, 18000.0) + def test_gle_made_by_asset_sale(self): date = nowdate() purchase_date = add_months(get_first_day(date), -2) From b3b7650ac80ff5eb01b93cd019c13340662a6612 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 27 Sep 2022 20:43:08 +0530 Subject: [PATCH 224/898] chore: add blank lines (cherry picked from commit c6a7de0e545746556a7e80e0bd4dcf46159d3f24) # Conflicts: # erpnext/assets/doctype/asset/depreciation.py --- 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 5ee48a210067..f9a226f37341 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -244,11 +244,14 @@ def restore_asset(asset_name): asset.set_status() +<<<<<<< HEAD <<<<<<< HEAD def get_gl_entries_on_asset_regain( asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None ): ======= +======= +>>>>>>> c6a7de0e54 (chore: add blank lines) def depreciate_asset(asset, date): asset.flags.ignore_validate_update_after_submit = True asset.prepare_depreciation_data(date_of_disposal=date) @@ -334,8 +337,14 @@ def disposal_happens_in_the_future(posting_date_of_disposal): return False +<<<<<<< HEAD def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): >>>>>>> ff5cad1cd6 (fix: calculate depreciation properly on asset sale entry and scrap entry) +======= +def get_gl_entries_on_asset_regain( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): +>>>>>>> c6a7de0e54 (chore: add blank lines) ( fixed_asset_account, asset, From 9c915ceeb2e600fdfab91f59f483bd0cbae808da Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 28 Sep 2022 09:49:04 +0000 Subject: [PATCH 225/898] chore(release): Bumped to Version 14.2.1 ## [14.2.1](https://github.com/frappe/erpnext/compare/v14.2.0...v14.2.1) (2022-09-28) ### Bug Fixes * `For Quantity` error msg in `Stock Entry` ([4b6e1f4](https://github.com/frappe/erpnext/commit/4b6e1f489213e4c911d2ee367ae0b4e558c2d93f)) * Add return against indexes for POS Invoice ([3abe4a1](https://github.com/frappe/erpnext/commit/3abe4a16af88c0e0e223399a29dbdd3c417d04f3)) * Add return against indexes for POS Invoice ([32ad36a](https://github.com/frappe/erpnext/commit/32ad36ad0f5bfe36d76adfbc7d07387edcf1f7c3)) * Add strings to translate function ([2eeff8a](https://github.com/frappe/erpnext/commit/2eeff8a00bdac96b1df7e2131ce568ba9d99885f)) * add translate function to strings ([f5a3f8b](https://github.com/frappe/erpnext/commit/f5a3f8b48649a0cc4bd650c0eee214b4a127fcc9)) * add translate function to strings ([e464de3](https://github.com/frappe/erpnext/commit/e464de3c5dbce204efe3ee60d3a3a12500bdcdf4)) * allow to return expired batches using purchase return ([305eb02](https://github.com/frappe/erpnext/commit/305eb02bfcd337e1260acb76e3f2c9571d4e074e)) * consider overproduction percentage for WO finish button ([4a29c7f](https://github.com/frappe/erpnext/commit/4a29c7fafb2401c7dca6d388f36c394ec9554781)) * difference amount calculation on payment reconciliation ([b389740](https://github.com/frappe/erpnext/commit/b3897406b08e444c0597e1a27c52fee49e8f2cdc)) * get amount in words for debit note ([7554ac9](https://github.com/frappe/erpnext/commit/7554ac9569d1b8a2bc3d36ae266010aa656c3790)) * item_code key error in production plan ([4e31448](https://github.com/frappe/erpnext/commit/4e31448e3ddd51ad7c5bab053d852a1d2ed7bfc4)) * Move subscription process to hourly long quque ([b2f280d](https://github.com/frappe/erpnext/commit/b2f280dee60a5b266a0ea0b685716ce5794935e6)) * opening entry causing discepancy between stock and trial balance ([2fd0291](https://github.com/frappe/erpnext/commit/2fd0291afbae4c36c4d1204c616e388340207ed9)) * POS only validate QTY if is_stock_item ([29c4a32](https://github.com/frappe/erpnext/commit/29c4a32c335cae5fd2e2de8bc84bd028b5f2c217)) * POS properly validate stock for bundle products ([6b55a01](https://github.com/frappe/erpnext/commit/6b55a01834ff66ac0ee3ecb3aba17969d14b6d17)) * Reduce font size for Process Statement of accounts print/pdf ([bcfa9b8](https://github.com/frappe/erpnext/commit/bcfa9b859332e81d2aefb799aed11a8a61578720)) * report chart field type ([931aa62](https://github.com/frappe/erpnext/commit/931aa620725aa8c8095bca6fafb35fd08229506c)) * Scrap Asset Accounting Dimensions (backport [#31949](https://github.com/frappe/erpnext/issues/31949)) ([#32376](https://github.com/frappe/erpnext/issues/32376)) ([a28ae4d](https://github.com/frappe/erpnext/commit/a28ae4d6534f40cb4b7844c09998e9589e4d4fb5)) * total value in all keys ([24234d5](https://github.com/frappe/erpnext/commit/24234d584d55e5a5a25d27ac2e072d353e1c03ff)) * typo on opportunity summary by sales stage report ([a403e72](https://github.com/frappe/erpnext/commit/a403e729d68d919dba762c62faacb96acb8b3eaf)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 104a49017625..a87507f42506 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.2.0" +__version__ = "14.2.1" def get_default_company(user=None): From d91ac5e549264e69f4956492351b30dd9d18ef85 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 28 Sep 2022 16:46:48 +0530 Subject: [PATCH 226/898] fix: supplied items added twice in Stock Entry (cherry picked from commit ac7409bbf33c56b936ee1958270dadb459b0a18c) --- 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 202a880750e1..6bc88d1964f9 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -841,7 +841,7 @@ def make_rm_stock_entry( for fg_item_code in fg_item_code_list: for rm_item in rm_items: - if rm_item.get("main_item_code") or rm_item.get("item_code") == fg_item_code: + if rm_item.get("main_item_code") == fg_item_code or rm_item.get("item_code") == fg_item_code: rm_item_code = rm_item.get("rm_item_code") items_dict = { From 097398914eb8fedc2646e1051a994d609673aad0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Sep 2022 15:36:59 +0530 Subject: [PATCH 227/898] fix: Disbursement Account in patch to update old loans (cherry picked from commit be623ce8e8e9c25d2327383c057b5e810b28c4a7) --- erpnext/patches/v13_0/update_old_loans.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index a1d40b739eba..0bd3fcdec4c5 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -100,6 +100,7 @@ def execute(): "mode_of_payment": loan.mode_of_payment, "loan_account": loan.loan_account, "payment_account": loan.payment_account, + "disbursement_account": loan.payment_account, "interest_income_account": loan.interest_income_account, "penalty_income_account": loan.penalty_income_account, }, @@ -190,6 +191,7 @@ def create_loan_type(loan, loan_type_name, penalty_account): loan_type_doc.company = loan.company loan_type_doc.mode_of_payment = loan.mode_of_payment loan_type_doc.payment_account = loan.payment_account + loan_type_doc.disbursement_account = loan.payment_account loan_type_doc.loan_account = loan.loan_account loan_type_doc.interest_income_account = loan.interest_income_account loan_type_doc.penalty_income_account = penalty_account From 381231257b0c83aec8baafb3af075a83e116577d Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 29 Sep 2022 11:59:53 +0530 Subject: [PATCH 228/898] chore: fix merge conflicts --- erpnext/assets/doctype/asset/depreciation.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index f9a226f37341..97941706aa85 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -244,14 +244,6 @@ def restore_asset(asset_name): asset.set_status() -<<<<<<< HEAD -<<<<<<< HEAD -def get_gl_entries_on_asset_regain( - asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None -): -======= -======= ->>>>>>> c6a7de0e54 (chore: add blank lines) def depreciate_asset(asset, date): asset.flags.ignore_validate_update_after_submit = True asset.prepare_depreciation_data(date_of_disposal=date) @@ -337,14 +329,9 @@ def disposal_happens_in_the_future(posting_date_of_disposal): return False -<<<<<<< HEAD -def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): ->>>>>>> ff5cad1cd6 (fix: calculate depreciation properly on asset sale entry and scrap entry) -======= def get_gl_entries_on_asset_regain( asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None ): ->>>>>>> c6a7de0e54 (chore: add blank lines) ( fixed_asset_account, asset, From f167abbbfd7ccd719a2a039d8d4a32733c996ebb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 28 Sep 2022 23:00:45 +0530 Subject: [PATCH 229/898] fix: searchfields as per customize form not working for Item (cherry picked from commit fd889fd29a24b71e85b9d72df29ce844ad7a1f0d) --- erpnext/controllers/queries.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 4f8b5c79d240..8eae0a070281 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -212,21 +212,15 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals meta = frappe.get_meta(doctype, cached=True) searchfields = meta.get_search_fields() - # these are handled separately - ignored_search_fields = ("item_name", "description") - for ignored_field in ignored_search_fields: - if ignored_field in searchfields: - searchfields.remove(ignored_field) - columns = "" - extra_searchfields = [ - field - for field in searchfields - if not field in ["name", "item_group", "description", "item_name"] - ] + extra_searchfields = [field for field in searchfields if not field in ["name", "description"]] if extra_searchfields: - columns = ", " + ", ".join(extra_searchfields) + columns += ", " + ", ".join(extra_searchfields) + + if "description" in searchfields: + columns += """, if(length(tabItem.description) > 40, \ + concat(substr(tabItem.description, 1, 40), "..."), description) as description""" searchfields = searchfields + [ field @@ -266,12 +260,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if frappe.db.count(doctype, cache=True) < 50000: # scan description only if items are less than 50000 description_cond = "or tabItem.description LIKE %(txt)s" + return frappe.db.sql( """select - tabItem.name, tabItem.item_name, tabItem.item_group, - if(length(tabItem.description) > 40, \ - concat(substr(tabItem.description, 1, 40), "..."), description) as description - {columns} + tabItem.name {columns} from tabItem where tabItem.docstatus < 2 and tabItem.disabled=0 From 3f0a3b702d35386034f4722484ad43b7bb2fa4f3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 29 Sep 2022 14:01:51 +0530 Subject: [PATCH 230/898] test: added test case (cherry picked from commit 3656f7d06ffe4b8bf44884e57fe4a2a89fac4187) --- erpnext/stock/doctype/item/test_item.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 1cee553be5be..e35c8bf335e3 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -5,6 +5,7 @@ import json import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.test_runner import make_test_objects from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, today @@ -816,6 +817,30 @@ def test_update_is_stock_item(self): item.reload() self.assertEqual(item.is_stock_item, 1) + def test_serach_fields_for_item(self): + from erpnext.controllers.queries import item_query + + make_property_setter("Item", None, "search_fields", "item_name", "Data", for_doctype="Doctype") + + item = make_item(properties={"item_name": "Test Item", "description": "Test Description"}) + data = item_query( + "Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True + ) + self.assertEqual(data[0].name, item.name) + self.assertEqual(data[0].item_name, item.item_name) + self.assertTrue("description" not in data[0]) + + make_property_setter( + "Item", None, "search_fields", "item_name, description", "Data", for_doctype="Doctype" + ) + data = item_query( + "Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True + ) + self.assertEqual(data[0].name, item.name) + self.assertEqual(data[0].item_name, item.item_name) + self.assertEqual(data[0].description, item.description) + self.assertTrue("description" in data[0]) + def set_item_variant_settings(fields): doc = frappe.get_doc("Item Variant Settings") From fd86876a0eaa8d17f375080d88cafda959514da8 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 28 Sep 2022 15:40:07 +0530 Subject: [PATCH 231/898] fix: show `Make Purchase Invoice` button based on permission (cherry picked from commit 80080a3d7bc2e4a69c7a18742ce89534920ab118) --- erpnext/templates/pages/order.html | 2 +- erpnext/templates/pages/order.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index a10870db2787..ec1d49788bda 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -18,7 +18,7 @@

{{ doc.name }}

- - - +
+
+
+ {{ _("Net Total") }} +
+
+ {{ doc.get_formatted("net_total") }} +
+
+
{% endif %} {% for d in doc.taxes %} {% if d.base_tax_amount %} -
- - - +
+
+
+ {{ d.description }} +
+
+ {{ doc.get_formatted("net_total") }} +
+
+
{% endif %} {% endfor %} {% if doc.doctype == 'Quotation' %} {% if doc.coupon_code %} -
- - - +
+
+
+ {{ _("Savings") }} +
+
+ {% set tot_quotation_discount = [] %} + {%- for item in doc.items -%} + {% if tot_quotation_discount.append((((item.price_list_rate * item.qty) + * item.discount_percentage) / 100)) %} + {% endif %} + {% endfor %} + {{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
+
+
{% endif %} {% endif %} {% if doc.doctype == 'Sales Order' %} {% if doc.coupon_code %} -
- - - - - - - +
+
+
+ {{ _("Total Amount") }} +
+
+ + {% set total_amount = [] %} + {%- for item in doc.items -%} + {% if total_amount.append((item.price_list_rate * item.qty)) %}{% endif %} + {% endfor %} + {{ frappe.utils.fmt_money((total_amount | sum),currency=doc.currency) }} + +
+
+
+
+
+
+ {{ _("Applied Coupon Code") }} +
+
+ + {%- for row in frappe.get_all(doctype="Coupon Code", + fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%} + {{ row.coupon_code }} + {% endfor %} + +
+
+
+
+
+
+ {{ _("Savings") }} +
+
+ + {% set tot_SO_discount = [] %} + {%- for item in doc.items -%} + {% if tot_SO_discount.append((((item.price_list_rate * item.qty) + * item.discount_percentage) / 100)) %}{% endif %} + {% endfor %} + {{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }} + +
+
+
{% endif %} {% endif %} -
- - - +
+
+
+ {{ _("Grand Total") }} +
+
+ {{ doc.get_formatted("grand_total") }} +
+
+
diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 3cfb8d8440cb..72d498c998fb 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -1,20 +1,22 @@
-
+
- - {{ doc.name }} + {{ doc.name }}
{{ frappe.utils.global_date_format(doc.modified) }}
-
+
+ {{doc.status}} +
+
{{ doc.items_preview }}
{% if doc.get('grand_total') %} -
+
{{ doc.get_formatted("grand_total") }}
{% endif %} diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index ec1d49788bda..6b354b2fab6d 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -5,149 +5,159 @@ {% include "templates/includes/breadcrumbs.html" %} {% endblock %} -{% block title %}{{ doc.name }}{% endblock %} +{% block title %} + {{ doc.name }} +{% endblock %} {% block header %} -

{{ doc.name }}

+

{{ doc.name }}

{% endblock %} {% block header_actions %} -
{{ _("Date") }}
- {{ _("Net Total") }} - - {{ doc.get_formatted("net_total") }} -
- {{ d.description }} - - {{ d.get_formatted("base_tax_amount") }} -
- {{ _("Savings") }} - - {% set tot_quotation_discount = [] %} - {%- for item in doc.items -%} - {% if tot_quotation_discount.append((((item.price_list_rate * item.qty) - * item.discount_percentage) / 100)) %} - {% endif %} - {% endfor %} - {{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }} -
- {{ _("Applied Coupon Code") }} - - - {%- for row in frappe.get_all(doctype="Coupon Code", - fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%} - {{ row.coupon_code }} - {% endfor %} - -
- {{ _("Savings") }} - - - {% set tot_SO_discount = [] %} - {%- for item in doc.items -%} - {% if tot_SO_discount.append((((item.price_list_rate * item.qty) - * item.discount_percentage) / 100)) %}{% endif %} - {% endfor %} - {{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }} - -
- {{ _("Grand Total") }} - - {{ doc.get_formatted("grand_total") }} -
- - - - - - - {% for d in doc.items %} - - - - - - {% endfor %} - -
- {{ _("Item") }} - - {{ _("Quantity") }} - - {{ _("Amount") }} -
- {{ item_name_and_description(d) }} - - {{ d.qty }} - {% if d.delivered_qty is defined and d.delivered_qty != None %} -

{{ _("Delivered") }} {{ d.delivered_qty }}

- {% endif %} -
- {{ d.get_formatted("amount") }} -

{{ _("Rate:") }} {{ d.get_formatted("rate") }}

-
- -
- + +
{% include "erpnext/templates/includes/order/order_taxes.html" %} -
+
{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0) - or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0)) %} + or (doc.doctype=="Sales Invoice" and doc.outstanding_amount> 0)) %}
-
-
-
- Payment -
-
-
{% if available_loyalty_points %} +
+
+
+ Loyalty Points +
+
+
+
Enter Loyalty Points
- +
-

Available Points: {{ available_loyalty_points }}

+

Available Points: {{ + available_loyalty_points }}

{% endif %}
- - -
-
@@ -172,17 +182,17 @@

{{ doc.name }}

{% endif %} -
{% if doc.terms %}
-

{{ doc.terms }}

+
+

{{ doc.terms }}

{% endif %} {% endblock %} {% block script %} - + -{% endblock %} +{% endblock %} \ No newline at end of file From 217e407371f889a16cb38a375a45f480a437346b Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 4 Oct 2022 12:13:08 +0000 Subject: [PATCH 252/898] chore(release): Bumped to Version 14.2.2 ## [14.2.2](https://github.com/frappe/erpnext/compare/v14.2.1...v14.2.2) (2022-10-04) ### Bug Fixes * (bulk transaction) key error and better error logging (backport [#32445](https://github.com/frappe/erpnext/issues/32445)) ([#32463](https://github.com/frappe/erpnext/issues/32463)) ([7a80385](https://github.com/frappe/erpnext/commit/7a8038500d862a6fcbea880fa6b12b73053abd6d)) * add non-stock item filter for `fg_item` in PO ([11f9279](https://github.com/frappe/erpnext/commit/11f92797b99679e53959e1625e22dcf49551b05d)) * add validation for non-stock item in SCR ([793fa18](https://github.com/frappe/erpnext/commit/793fa182258d8ddec55b6beb9b60208f0eaea0d5)) * asset requiring maintenance sold status and added test [v14-hotfix] ([c0a141b](https://github.com/frappe/erpnext/commit/c0a141bfcdb423c7775b85b8f8dcd0c51ea89482)) * asset tests ([6710337](https://github.com/frappe/erpnext/commit/671033755f2deedda583c51766f853fc0d1ace61)) * calculate depreciation properly on asset sale entry and scrap entry ([f5f13c4](https://github.com/frappe/erpnext/commit/f5f13c4611458121d4c26acb913fe557933b9046)) * Create accounting dimension fields in asset capitalization ([9ec0d10](https://github.com/frappe/erpnext/commit/9ec0d104a5cec46e6f1c5c8e1bdf08fcda4b40e1)) * Disbursement Account in patch to update old loans ([0973989](https://github.com/frappe/erpnext/commit/097398914eb8fedc2646e1051a994d609673aad0)) * don't allow to update `Maintain Stock` if the item has a `BOM` ([e5d1f59](https://github.com/frappe/erpnext/commit/e5d1f59d964953371f1e4fd183f2d31a7c970899)) * fetch swift_number in payment_request ([f09222a](https://github.com/frappe/erpnext/commit/f09222a9e5d0b9c9bb2ae19d912cbe700465e870)) * fix restore asset value after depreciation ([8605245](https://github.com/frappe/erpnext/commit/86052450a56eb4786bbc9caacecae9add2ae7386)) * fixes query builder order by issue in postgres (backport [#32441](https://github.com/frappe/erpnext/issues/32441)) ([#32465](https://github.com/frappe/erpnext/issues/32465)) ([8851826](https://github.com/frappe/erpnext/commit/8851826a574b7b06f034c9546534bacabbc67719)) * Incorrect TCS amount deducted in Sales Invoice ([4d51d73](https://github.com/frappe/erpnext/commit/4d51d73797fa9dd7ae6b5780fee17a5d6f9fb4d7)) * Item details fetching on making transaction from item dashboard ([db60299](https://github.com/frappe/erpnext/commit/db60299cf32d337c0f3a64ea08d82cfb6077dde6)) * not able to return sold expired batches ([9c56a5f](https://github.com/frappe/erpnext/commit/9c56a5ff7c0cf61a52920ce68cc29f4e482b862f)) * payment request make bank field Link instead of Read Only ([c8c0f34](https://github.com/frappe/erpnext/commit/c8c0f345d7e23584311ab6c15696ba00f7a07db1)) * pick list picked-qty for batch item ([32107c4](https://github.com/frappe/erpnext/commit/32107c4a79db9715b9d4eabb75f4401bafeaab22)) * refactor asset capitilization ([bad3b33](https://github.com/frappe/erpnext/commit/bad3b330f4eeb89761dcf621cfe3f95644cef030)) * searchfields as per customize form not working for Item ([f167abb](https://github.com/frappe/erpnext/commit/f167abbbfd7ccd719a2a039d8d4a32733c996ebb)) * show `Make Purchase Invoice` button based on permission ([fd86876](https://github.com/frappe/erpnext/commit/fd86876a0eaa8d17f375080d88cafda959514da8)) * supplied items added twice in Stock Entry ([d91ac5e](https://github.com/frappe/erpnext/commit/d91ac5e549264e69f4956492351b30dd9d18ef85)) * test_scrap_asset ([fd232d1](https://github.com/frappe/erpnext/commit/fd232d1db7219930317e2355a2e609ee4dc8f200)) * typo in sales_register's filter mode_of_payment (backport [#32371](https://github.com/frappe/erpnext/issues/32371)) ([#32446](https://github.com/frappe/erpnext/issues/32446)) ([1918b5c](https://github.com/frappe/erpnext/commit/1918b5c4b0e83ac2c471b6f31aac722cde7a1bc5)) * update with new Frappe color. fix [#32455](https://github.com/frappe/erpnext/issues/32455) ([#32456](https://github.com/frappe/erpnext/issues/32456)) ([3e7582f](https://github.com/frappe/erpnext/commit/3e7582f55bd88a771320ce3f2f7c77a4f879b4aa)) ### Reverts * Revert "fix: fetch swift_number in payment_request" ([d3c151f](https://github.com/frappe/erpnext/commit/d3c151f32bf2c340c546f0a799ede80977958010)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a87507f42506..6aabfd334884 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.2.1" +__version__ = "14.2.2" def get_default_company(user=None): From 70e990c4f3ff16497cb82d84af6c54d90622ac7b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Oct 2022 12:35:59 +0530 Subject: [PATCH 253/898] refactor: remove duplicate entries on remarks migration patch (cherry picked from commit 518ab93e039d68827506c3ac92db3c09aea644e3) --- ...grate_remarks_from_gl_to_payment_ledger.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py index 062d24b78bdd..fd2a2a39cc68 100644 --- a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py +++ b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py @@ -3,6 +3,29 @@ from frappe.utils import create_batch +def remove_duplicate_entries(pl_entries): + unique_vouchers = set() + for x in pl_entries: + unique_vouchers.add( + (x.company, x.account, x.party_type, x.party, x.voucher_type, x.voucher_no, x.gle_remarks) + ) + + entries = [] + for x in unique_vouchers: + entries.append( + frappe._dict( + company=x[0], + account=x[1], + party_type=x[2], + party=x[3], + voucher_type=x[4], + voucher_no=x[5], + gle_remarks=x[6], + ) + ) + return entries + + def execute(): if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"): @@ -34,6 +57,8 @@ def execute(): .run(as_dict=True) ) + pl_entries = remove_duplicate_entries(pl_entries) + if pl_entries: # split into multiple batches, update and commit for each batch batch_size = 1000 From 73742ff56566170bcf37058fc315d94eccae1eff Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 5 Oct 2022 10:58:21 +0530 Subject: [PATCH 254/898] fix: TooManyWritesError during reposting of stock (cherry picked from commit aaabba9b1e93ab90fd2afa9fb7b404b10fe8a3a4) --- .../doctype/repost_item_valuation/repost_item_valuation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index c4705246b3cc..d6f9bae5da2d 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -128,6 +128,9 @@ def repost(doc): if not frappe.db.exists("Repost Item Valuation", doc.name): return + # This is to avoid TooManyWritesError in case of large reposts + frappe.db.MAX_WRITES_PER_TRANSACTION *= 4 + doc.set_status("In Progress") if not frappe.flags.in_test: frappe.db.commit() From 17dd1abb34af9ee6a01080e15491faa7596cd923 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 3 Oct 2022 19:31:20 +0530 Subject: [PATCH 255/898] feat(JE): trigger account field when fetched from template Closes #32409 (cherry picked from commit c35adcf5a179e35df2542d4507dc165d13b6f67b) --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 7af41f398adf..763e2e6992cd 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -173,8 +173,8 @@ frappe.ui.form.on("Journal Entry", { var update_jv_details = function(doc, r) { $.each(r, function(i, d) { var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); - row.account = d.account; - row.balance = d.balance; + frappe.model.set_value(row.doctype, row.name, "account", d.account) + frappe.model.set_value(row.doctype, row.name, "balance", d.balance) }); refresh_field("accounts"); } From 3caf90501967157307e75c0baf2ac4b92fc55067 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 5 Oct 2022 14:49:44 +0200 Subject: [PATCH 256/898] chore: update fr translation (#32385) (cherry picked from commit 0e4017cbe51394986046818946d8f6ac39798182) --- erpnext/translations/fr.csv | 61 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index b2074618a6fc..7989bf7ebce6 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -785,7 +785,7 @@ 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 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., @@ -838,7 +838,7 @@ Difference Account,Compte d’Écart, "Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry","Le Compte d’Écart doit être un compte de type Actif / Passif, puisque cette Réconciliation de Stock est une écriture d'à-nouveau", Difference Amount,Écart de Montant, Difference Amount must be zero,L’Écart de Montant doit être égal à zéro, -Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM.,Différentes UDM pour les articles conduira à un Poids Net (Total) incorrect . Assurez-vous que le Poids Net de chaque article a la même unité de mesure ., +Different UOM for items will lead to incorrect (Total) Net Weight value. Make sure that Net Weight of each item is in the same UOM.,Différentes UdM pour les articles conduira à un Poids Net (Total) incorrect . Assurez-vous que le Poids Net de chaque article a la même unité de mesure ., Direct Expenses,Charges Directes, Direct Income,Revenu direct, Disable,Désactiver, @@ -1409,7 +1409,7 @@ Lab Test,Test de laboratoire, Lab Test Report,Rapport de test de laboratoire, Lab Test Sample,Échantillon de test de laboratoire, Lab Test Template,Modèle de test de laboratoire, -Lab Test UOM,UDM de test de laboratoire, +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, @@ -2806,7 +2806,7 @@ Stock Received But Not Billed,Stock Reçus Mais Non Facturés, Stock Reports,Rapports de stock, Stock Summary,Résumé du Stock, Stock Transactions,Transactions du Stock, -Stock UOM,UDM du Stock, +Stock UOM,UdM du Stock, Stock Value,Valeur du Stock, Stock balance in Batch {0} will become negative {1} for Item {2} at Warehouse {3},Solde du stock dans le Lot {0} deviendra négatif {1} pour l'Article {2} à l'Entrepôt {3}, Stock cannot be updated against Delivery Note {0},Stock ne peut pas être mis à jour pour le Bon de Livraison {0}, @@ -3161,9 +3161,9 @@ Trial Period End Date Cannot be before Trial Period Start Date,La date de fin de Trialling,Essai, Type of Business,Type de commerce, 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}, +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 DocType {0},Impossible de trouver le DocType {0}, 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, @@ -3294,7 +3294,7 @@ Wednesday,Mercredi, Week,Semaine, Weekdays,Jours de la semaine, Weekly,Hebdomadaire, -"Weight is mentioned,\nPlease mention ""Weight UOM"" too","Poids est mentionné,\nVeuillez aussi mentionner ""UDM de Poids""", +"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 ?, @@ -4938,12 +4938,15 @@ Is Cumulative,Est cumulatif, Coupon Code Based,Code de coupon basé, Discount on Other Item,Remise sur un autre article, Apply Rule On Other,Appliquer la règle sur autre, -Party Information,Informations sur la fête, +Party Information,Informations sur le tier, Quantity and Amount,Quantité et montant, Min Qty,Qté Min, Max Qty,Qté Max, -Min Amt,Min Amt, -Max Amt,Max Amt, +Min Amt,Montant Min, +Max Amt,Montant Max, +"If rate is zero them item will be treated as ""Free Item""",Si le prix est à 0 alors l'article sera traité comme article gratuit +Is Recursive,Est récursif +"Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",La remise sera appliquée séquentiellement telque : acheter 1 => recupérer 1, acheter 2 => recupérer 2, acheter 3 => recupérer 3, etc... Period Settings,Paramètres de période, Margin,Marge, Margin Type,Type de Marge, @@ -5053,7 +5056,7 @@ Quantity and Rate,Quantité et Prix, Received Qty,Qté Reçue, Accepted Qty,Quantité acceptée, Rejected Qty,Qté Rejetée, -UOM Conversion Factor,Facteur de Conversion de l'UDM, +UOM Conversion Factor,Facteur de Conversion de l'UdM, Discount on Price List Rate (%),Remise sur la Liste des Prix (%), Price List Rate (Company Currency),Taux de la Liste de Prix (Devise Société), Rate (Company Currency),Prix (Devise Société), @@ -5085,7 +5088,7 @@ Purchase Receipt Detail,Détail du reçu d'achat, Item Weight Details,Détails du poids de l'article, Weight Per Unit,Poids par unité, Total Weight,Poids total, -Weight UOM,UDM de Poids, +Weight UOM,UdM de Poids, Page Break,Saut de Page, Consider Tax or Charge for,Tenir Compte de la Taxe et des Frais pour, Valuation and Total,Valorisation et Total, @@ -5153,7 +5156,7 @@ Advance amount,Montant de l'Avance, Sales Invoice Item,Article de la Facture de Vente, Customer's Item Code,Code de l'Article du Client, Brand Name,Nom de la Marque, -Qty as per Stock UOM,Qté par UDM du Stock, +Qty as per Stock UOM,Qté par UdM du Stock, Discount and Margin,Remise et Marge, Rate With Margin,Prix Avec Marge, Discount (%) on Price List Rate with Margin,Remise (%) sur le prix de la Liste de Prix avec la Marge, @@ -5501,7 +5504,7 @@ Blanket Order Rate,Prix unitaire de commande avec limites, Returned Qty,Qté Retournée, Purchase Order Item Supplied,Article Fourni depuis la Commande d'Achat, BOM Detail No,N° de Détail de la nomenclature, -Stock Uom,UDM du Stock, +Stock Uom,UdM du Stock, Raw Material Item Code,Code d’Article de Matière Première, Supplied Qty,Qté Fournie, Purchase Receipt Item Supplied,Articles Fournis du Reçus d’Achat, @@ -6149,7 +6152,7 @@ Drug Name / Description,Nom / description du médicament, Dosage,Dosage, Dosage by Time Interval,Dosage par intervalle de temps, Interval,Intervalle, -Interval UOM,UDM d'Intervalle, +Interval UOM,UdM d'Intervalle, Hour,Heure, Update Schedule,Mettre à Jour le Calendrier, Exercise,Exercice, @@ -7023,7 +7026,7 @@ Petrol,Essence, Diesel,Diesel, Natural Gas,Gaz Naturel, Electric,Électrique, -Fuel UOM,UDM Carburant, +Fuel UOM,UdM Carburant, Last Carbon Check,Dernière Vérification Carbone, Wheels,Roues, Doors,Portes, @@ -7182,7 +7185,7 @@ Item to be manufactured or repacked,Article à produire ou à réemballer, Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Quantité d'article obtenue après production / reconditionnement des quantités données de matières premières, Set rate of sub-assembly item based on BOM,Définir le prix des articles de sous-assemblage en fonction de la nomenclature, Allow Alternative Item,Autoriser un article alternatif, -Item UOM,UDM de l'Article, +Item UOM,UdM de l'Article, Conversion Rate,Taux de Conversion, Rate Of Materials Based On,Prix des Matériaux Basé sur, With Operations,Avec des Opérations, @@ -7926,7 +7929,7 @@ Territory Manager,Responsable Régional, For reference,Pour référence, Territory Targets,Objectifs Régionaux, Set Item Group-wise budgets on this Territory. You can also include seasonality by setting the Distribution.,Définir des budgets par Groupes d'Articles sur ce Territoire. Vous pouvez également inclure de la saisonnalité en définissant la Répartition., -UOM Name,Nom UDM, +UOM Name,Nom UdM, Check this to disallow fractions. (for Nos),Cochez cette case pour interdire les fractions. (Pour les numéros), Website Item Group,Groupe d'Articles du Site Web, Cross Listing of Item in multiple groups,Liste Croisée d'Articles dans plusieurs groupes, @@ -8198,10 +8201,10 @@ To Package No.,Au N° de Paquet, If more than one package of the same type (for print),Si plus d'un paquet du même type (pour l'impression), Package Weight Details,Détails du Poids du Paquet, The net weight of this package. (calculated automatically as sum of net weight of items),Le poids net de ce paquet. (Calculé automatiquement comme la somme du poids net des articles), -Net Weight UOM,UDM Poids Net, +Net Weight UOM,UdM Poids Net, Gross Weight,Poids Brut, The gross weight of the package. Usually net weight + packaging material weight. (for print),Le poids brut du colis. Habituellement poids net + poids du matériau d'emballage. (Pour l'impression), -Gross Weight UOM,UDM du Poids Brut, +Gross Weight UOM,UdM du Poids Brut, Packing Slip Item,Article Emballé, DN Detail,Détail du Bon de Livraison, STO-PICK-.YYYY.-,STO-PICK-.YYYY.-, @@ -8215,7 +8218,7 @@ Pick List Item,Élément de la liste de choix, Picked Qty,Quantité choisie, Price List Master,Données de Base des Listes de Prix, Price List Name,Nom de la Liste de Prix, -Price Not UOM Dependent,Prix non dépendant de l'UOM, +Price Not UOM Dependent,Prix non dépendant de l'UdM, Applicable for Countries,Applicable pour les Pays, Price List Country,Pays de la Liste des Prix, MAT-PRE-.YYYY.-,MAT-PRE-YYYY.-, @@ -8294,7 +8297,7 @@ Purchase Receipt No,N° du Reçu d'Achat, Inspection Required,Inspection obligatoire, From BOM,Depuis la nomenclature, For Quantity,Pour la Quantité, -As per Stock UOM,Selon UDM du Stock, +As per Stock UOM,Selon UdM du Stock, Including items for sub assemblies,Incluant les articles pour des sous-ensembles, Default Source Warehouse,Entrepôt Source par Défaut, Source Warehouse Address,Adresse de l'entrepôt source, @@ -8309,7 +8312,7 @@ Total Additional Costs,Total des Coûts Additionnels, Customer or Supplier Details,Détails du Client ou du Fournisseur, Per Transferred,Par transféré, Stock Entry Detail,Détails de l'Écriture de Stock, -Basic Rate (as per Stock UOM),Prix de base (comme l’UDM du Stock), +Basic Rate (as per Stock UOM),Prix de base (comme l’UdM du Stock), Basic Amount,Montant de Base, Additional Cost,Frais Supplémentaire, Serial No / Batch,N° de Série / Lot, @@ -8339,7 +8342,7 @@ Quantity Difference,Différence de Quantité, Amount Difference,Différence de Montant, Item Naming By,Nomenclature d'Article Par, Default Item Group,Groupe d'Éléments par Défaut, -Default Stock UOM,UDM par Défaut des Articles, +Default Stock UOM,UdM par Défaut des Articles, Sample Retention Warehouse,Entrepôt de stockage des échantillons, Default Valuation Method,Méthode de Valorisation par Défaut, Show Barcode Field,Afficher Champ Code Barre, @@ -8353,8 +8356,8 @@ Stock Frozen Upto,Stock Gelé Jusqu'au, Batch Identification,Identification par lots, Use Naming Series,Utiliser la série de noms, Naming Series Prefix,Préfix du nom de série, -UOM Category,Catégorie d'unité de mesure (UDM), -UOM Conversion Detail,Détails de Conversion de l'UDM, +UOM Category,Catégorie d'unité de mesure (UdM), +UOM Conversion Detail,Détails de Conversion de l'UdM, Variant Field,Champ de Variante, A logical Warehouse against which stock entries are made.,Un Entrepôt logique dans lequel les entrées en stock sont faites., Warehouse Detail,Détail de l'Entrepôt, @@ -9869,8 +9872,8 @@ Allowed Items,Articles autorisés Party Specific Item,Restriction d'article disponible Restrict Items Based On,Type de critére de restriction Based On Value,critére de restriction -Unit of Measure (UOM),Unité de mesure (UDM), -Unit Of Measure (UOM),Unité de mesure (UDM), +Unit of Measure (UOM),Unité de mesure (UdM), +Unit Of Measure (UOM),Unité de mesure (UdM), CRM Settings,Paramètres CRM Do Not Explode,Ne pas décomposer Quick Access, Accés rapides From 2f1b8af13c2b5f155225859ea65770b7e5a9ba69 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Oct 2022 11:37:43 +0530 Subject: [PATCH 257/898] fix: single column indexes (backport #32425) (#32513) fix: single column indexes (#32425) refactor: move single column indexes to doctypes (cherry picked from commit 8d1db0ea3db008d753048ca53356846af44de775) Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 5 +++-- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ---- erpnext/e_commerce/doctype/website_item/website_item.json | 8 +++++--- erpnext/e_commerce/doctype/website_item/website_item.py | 3 --- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 6f8b3822c2e1..eedaaaf338b7 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -343,7 +343,8 @@ "no_copy": 1, "options": "POS Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "default": "0", @@ -1553,7 +1554,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-09-27 13:00:24.166684", + "modified": "2022-09-30 03:49:50.455199", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index fbe0ef39f839..54a3e934b2dc 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -743,7 +743,3 @@ def append_payment(payment_mode): ]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) append_payment(payment_mode[0]) - - -def on_doctype_update(): - frappe.db.add_index("POS Invoice", ["return_against"]) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index c5775ee9075a..6556eabf4abf 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -188,7 +188,8 @@ "in_list_view": 1, "label": "Item Group", "options": "Item Group", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "default": "1", @@ -234,7 +235,8 @@ "fieldname": "brand", "fieldtype": "Link", "label": "Brand", - "options": "Brand" + "options": "Brand", + "search_index": 1 }, { "collapsible": 1, @@ -346,7 +348,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2022-09-13 04:05:11.614087", + "modified": "2022-09-30 04:01:52.090732", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index c0f8c79283db..3e5d5f768fa0 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -403,9 +403,6 @@ def on_doctype_update(): # since route is a Text column, it needs a length for indexing frappe.db.add_index("Website Item", ["route(500)"]) - frappe.db.add_index("Website Item", ["item_group"]) - frappe.db.add_index("Website Item", ["brand"]) - def check_if_user_is_customer(user=None): from frappe.contacts.doctype.contact.contact import get_contact_name From 5f6bbd49294b7d68cc43afbdd4df41a1fb8fc6ab Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Oct 2022 13:03:29 +0530 Subject: [PATCH 258/898] chore: drop stale demo page (backport #32515) (#32516) chore: drop stale demo page (#32515) (cherry picked from commit 07c4a7483869a06c1e00a2a71c1909fcf5cd9ed6) Co-authored-by: Ankush Menat --- erpnext/templates/pages/demo.html | 77 ------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 erpnext/templates/pages/demo.html diff --git a/erpnext/templates/pages/demo.html b/erpnext/templates/pages/demo.html deleted file mode 100644 index f9934a33f32d..000000000000 --- a/erpnext/templates/pages/demo.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends "templates/web.html" %} - -{% block script %} - -{% endblock %} - -{% block style %} - -{% endblock %} - -{% block title %} -{{ _("ERPNext Demo") }} -{% endblock %} - -{% block page_content %} -
- -
- - {{ _("ERPNext Demo") }} -
- -

Some functionality is disabled for the demo and the data will be cleared regularly.

-
-
- - -

Start a free 14-day trial -

- -{% endblock %} From d5f693806b4e7699b07594ea1565b24809b1d82e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Oct 2022 14:04:36 +0530 Subject: [PATCH 259/898] fix: Tax withholding related fixes (cherry picked from commit abf5b6be3ea13f0d00664e25e7b1742429d4f5e2) --- .../tax_withholding_category/tax_withholding_category.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 84c2c9a3c3eb..61f52a18aab6 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -425,7 +425,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): ): # Get net total again as TDS is calculated on net total # Grand is used to just check for threshold breach - net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0 + net_total = 0 + if vouchers: + net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") + net_total += inv.net_total supp_credit_amt = net_total - cumulative_threshold From bd8e61b2dc0bee580b19925c5ecb20dc4f837118 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Oct 2022 14:22:40 +0530 Subject: [PATCH 260/898] fix: Do not add tax withheld vouchers post tax withheding in one document (cherry picked from commit 781d160c684f9c04b1c799449e484abff3e07d8a) --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 3 ++- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3020e6dc6e35..1e477776e2d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1430,6 +1430,7 @@ "fieldname": "tax_withheld_vouchers", "fieldtype": "Table", "label": "Tax Withheld Vouchers", + "no_copy": 1, "options": "Tax Withheld Vouchers", "read_only": 1 } @@ -1438,7 +1439,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-09-27 11:07:55.766844", + "modified": "2022-10-07 14:19:14.214157", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 61f52a18aab6..7eddd81ee031 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -249,6 +249,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N ) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + + # once tds is deducted, not need to add vouchers in the invoice + voucher_wise_amount = {} else: tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) From a226fcf898c70af4717f4604b9a24d4e4c571214 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Oct 2022 14:04:36 +0530 Subject: [PATCH 261/898] fix: Tax withholding related fixes (cherry picked from commit abf5b6be3ea13f0d00664e25e7b1742429d4f5e2) --- .../tax_withholding_category/tax_withholding_category.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 84c2c9a3c3eb..61f52a18aab6 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -425,7 +425,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): ): # Get net total again as TDS is calculated on net total # Grand is used to just check for threshold breach - net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0 + net_total = 0 + if vouchers: + net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") + net_total += inv.net_total supp_credit_amt = net_total - cumulative_threshold From 6060072d2203cc7c10cde7109f8599c81fd5def3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Oct 2022 14:22:40 +0530 Subject: [PATCH 262/898] fix: Do not add tax withheld vouchers post tax withheding in one document (cherry picked from commit 781d160c684f9c04b1c799449e484abff3e07d8a) --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 3 ++- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3020e6dc6e35..1e477776e2d3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1430,6 +1430,7 @@ "fieldname": "tax_withheld_vouchers", "fieldtype": "Table", "label": "Tax Withheld Vouchers", + "no_copy": 1, "options": "Tax Withheld Vouchers", "read_only": 1 } @@ -1438,7 +1439,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-09-27 11:07:55.766844", + "modified": "2022-10-07 14:19:14.214157", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 61f52a18aab6..7eddd81ee031 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -249,6 +249,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N ) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + + # once tds is deducted, not need to add vouchers in the invoice + voucher_wise_amount = {} else: tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) From f70103dac635a55fbb86e477fe52cb9b06aa2a36 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Fri, 7 Oct 2022 12:40:22 +0000 Subject: [PATCH 263/898] chore(release): Bumped to Version 14.2.3 ## [14.2.3](https://github.com/frappe/erpnext/compare/v14.2.2...v14.2.3) (2022-10-07) ### Bug Fixes * Do not add tax withheld vouchers post tax withheding in one document ([6060072](https://github.com/frappe/erpnext/commit/6060072d2203cc7c10cde7109f8599c81fd5def3)) * Tax withholding related fixes ([a226fcf](https://github.com/frappe/erpnext/commit/a226fcf898c70af4717f4604b9a24d4e4c571214)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6aabfd334884..7a447fef30e6 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ import frappe -__version__ = "14.2.2" +__version__ = "14.2.3" def get_default_company(user=None): From bda25c4d5d2b6e30f6c4e29897a5404d31a41100 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 8 Oct 2022 21:25:11 +0530 Subject: [PATCH 264/898] fix: PO cancel post advance payment cancel against PO (cherry picked from commit d806e32030ab611583fc74aa4c559518a62c76fb) --- erpnext/buying/doctype/purchase_order/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index cd58d25136a1..bcedd4d0a118 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -349,7 +349,7 @@ def on_submit(self): update_linked_doc(self.doctype, self.name, self.inter_company_order_reference) def on_cancel(self): - self.ignore_linked_doctypes = "Payment Ledger Entry" + self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry") super(PurchaseOrder, self).on_cancel() if self.is_against_so(): From d0f3818060de365df90b6eda24d3d217af9a616f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 28 Sep 2022 12:53:18 +0530 Subject: [PATCH 265/898] feat: provision to return non consumed components against the work order (cherry picked from commit d59ed24e6ca2a1ff62963c282882a2d52691b7c6) --- .../doctype/work_order/test_work_order.py | 72 ++++++++++++++ .../doctype/work_order/work_order.js | 36 ++++++- .../doctype/work_order/work_order.py | 93 ++++++++++++++++--- .../work_order_item/work_order_item.json | 13 ++- .../work_order_consumed_materials.js | 2 +- .../work_order_consumed_materials.py | 38 +++++++- .../doctype/stock_entry/stock_entry.json | 25 ++--- .../stock/doctype/stock_entry/stock_entry.py | 26 ++++-- .../doctype/stock_entry/stock_entry_list.js | 9 +- 9 files changed, 274 insertions(+), 40 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index a53c42c5ec83..804f03dc519c 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -17,6 +17,7 @@ close_work_order, make_job_card, make_stock_entry, + make_stock_return_entry, stop_unstop, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order @@ -1408,6 +1409,77 @@ def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self): ) self.assertEqual(manufacture_ste_doc2.items[1].qty, 1) + def test_non_consumed_material_return_against_work_order(self): + frappe.db.set_value( + "Manufacturing Settings", + None, + "backflush_raw_materials_based_on", + "Material Transferred for Manufacture", + ) + + item = make_item( + "Test FG Item To Test Return Case", + { + "is_stock_item": 1, + }, + ) + + item_code = item.name + bom_doc = make_bom( + item=item_code, + source_warehouse="Stores - _TC", + raw_materials=["Test Batch MCC Keyboard", "Test Serial No BTT Headphone"], + ) + + # Create a work order + wo_doc = make_wo_order_test_record(production_item=item_code, qty=5) + wo_doc.save() + + self.assertEqual(wo_doc.bom_no, bom_doc.name) + + # Transfer material for manufacture + ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 5)) + for row in ste_doc.items: + row.qty += 2 + row.transfer_qty += 2 + nste_doc = test_stock_entry.make_stock_entry( + item_code=row.item_code, target="Stores - _TC", qty=row.qty, basic_rate=100 + ) + + row.batch_no = nste_doc.items[0].batch_no + row.serial_no = nste_doc.items[0].serial_no + + ste_doc.save() + ste_doc.submit() + ste_doc.load_from_db() + + # Create a stock entry to manufacture the item + ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 5)) + for row in ste_doc.items: + if row.s_warehouse and not row.t_warehouse: + row.qty -= 2 + row.transfer_qty -= 2 + + if row.serial_no: + serial_nos = get_serial_nos(row.serial_no) + row.serial_no = "\n".join(serial_nos[0:5]) + + ste_doc.save() + ste_doc.submit() + + wo_doc.load_from_db() + for row in wo_doc.required_items: + self.assertEqual(row.transferred_qty, 7) + self.assertEqual(row.consumed_qty, 5) + + self.assertEqual(wo_doc.status, "Completed") + return_ste_doc = make_stock_return_entry(wo_doc.name) + return_ste_doc.save() + + self.assertTrue(return_ste_doc.is_return) + for row in return_ste_doc.items: + self.assertEqual(row.qty, 2) + def prepare_data_for_backflush_based_on_materials_transferred(): batch_item_doc = make_item( diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index f3640b93b22a..4aab3fa37344 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -180,6 +180,37 @@ frappe.ui.form.on("Work Order", { frm.trigger("make_bom"); }); } + + frm.trigger("add_custom_button_to_return_components"); + }, + + add_custom_button_to_return_components: function(frm) { + if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) { + let non_consumed_items = frm.doc.required_items.filter(d =>{ + return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty) + }); + + if (non_consumed_items && non_consumed_items.length) { + frm.add_custom_button(__("Return Components"), function() { + frm.trigger("create_stock_return_entry"); + }).addClass("btn-primary"); + } + } + }, + + create_stock_return_entry: function(frm) { + frappe.call({ + method: "erpnext.manufacturing.doctype.work_order.work_order.make_stock_return_entry", + args: { + "work_order": frm.doc.name, + }, + callback: function(r) { + if(!r.exc) { + let doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + } + }); }, make_job_card: function(frm) { @@ -517,7 +548,8 @@ frappe.ui.form.on("Work Order Operation", { erpnext.work_order = { set_custom_buttons: function(frm) { var doc = frm.doc; - if (doc.docstatus === 1 && doc.status != "Closed") { + + if (doc.status !== "Closed") { frm.add_custom_button(__('Close'), function() { frappe.confirm(__("Once the Work Order is Closed. It can't be resumed."), () => { @@ -525,7 +557,9 @@ erpnext.work_order = { } ); }, __("Status")); + } + if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) { if (doc.status != 'Stopped' && doc.status != 'Completed') { frm.add_custom_button(__('Stop'), function() { erpnext.work_order.change_work_order_status(frm, "Stopped"); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 7b8625372a8e..1e6d982fc91c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -20,6 +20,7 @@ nowdate, time_diff_in_hours, ) +from pypika import functions as fn from erpnext.manufacturing.doctype.bom.bom import ( get_bom_item_rate, @@ -859,6 +860,7 @@ def update_required_items(self): if self.docstatus == 1: # calculate transferred qty based on submitted stock entries self.update_transferred_qty_for_required_items() + self.update_returned_qty() # update in bin self.update_reserved_qty_for_production() @@ -930,23 +932,62 @@ def set_required_items(self, reset_only_qty=False): self.set_available_qty() def update_transferred_qty_for_required_items(self): - """update transferred qty from submitted stock entries for that item against - the work order""" + ste = frappe.qb.DocType("Stock Entry") + ste_child = frappe.qb.DocType("Stock Entry Detail") + + query = ( + frappe.qb.from_(ste) + .inner_join(ste_child) + .on((ste_child.parent == ste.name)) + .select( + ste_child.item_code, + ste_child.original_item, + fn.Sum(ste_child.qty).as_("qty"), + ) + .where( + (ste.docstatus == 1) + & (ste.work_order == self.name) + & (ste.purpose == "Material Transfer for Manufacture") + & (ste.is_return == 0) + ) + .groupby(ste_child.item_code) + ) - for d in self.required_items: - transferred_qty = frappe.db.sql( - """select sum(qty) - from `tabStock Entry` entry, `tabStock Entry Detail` detail - where - entry.work_order = %(name)s - and entry.purpose = 'Material Transfer for Manufacture' - and entry.docstatus = 1 - and detail.parent = entry.name - and (detail.item_code = %(item)s or detail.original_item = %(item)s)""", - {"name": self.name, "item": d.item_code}, - )[0][0] + data = query.run(as_dict=1) or [] + transferred_items = frappe._dict({d.original_item or d.item_code: d.qty for d in data}) + + for row in self.required_items: + row.db_set( + "transferred_qty", (transferred_items.get(row.item_code) or 0.0), update_modified=False + ) + + def update_returned_qty(self): + ste = frappe.qb.DocType("Stock Entry") + ste_child = frappe.qb.DocType("Stock Entry Detail") + + query = ( + frappe.qb.from_(ste) + .inner_join(ste_child) + .on((ste_child.parent == ste.name)) + .select( + ste_child.item_code, + ste_child.original_item, + fn.Sum(ste_child.qty).as_("qty"), + ) + .where( + (ste.docstatus == 1) + & (ste.work_order == self.name) + & (ste.purpose == "Material Transfer for Manufacture") + & (ste.is_return == 1) + ) + .groupby(ste_child.item_code) + ) - d.db_set("transferred_qty", flt(transferred_qty), update_modified=False) + data = query.run(as_dict=1) or [] + returned_dict = frappe._dict({d.original_item or d.item_code: d.qty for d in data}) + + for row in self.required_items: + row.db_set("returned_qty", (returned_dict.get(row.item_code) or 0.0), update_modified=False) def update_consumed_qty_for_required_items(self): """ @@ -1470,3 +1511,25 @@ def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float: ) ) ).run()[0][0] or 0.0 + + +@frappe.whitelist() +def make_stock_return_entry(work_order): + from erpnext.stock.doctype.stock_entry.stock_entry import get_available_materials + + non_consumed_items = get_available_materials(work_order) + if not non_consumed_items: + return + + wo_doc = frappe.get_cached_doc("Work Order", work_order) + + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.from_bom = 1 + stock_entry.is_return = 1 + stock_entry.work_order = work_order + stock_entry.purpose = "Material Transfer for Manufacture" + stock_entry.bom_no = wo_doc.bom_no + stock_entry.add_transfered_raw_materials_in_items() + stock_entry.set_stock_entry_type() + + return stock_entry diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json index 3acf5727d19b..f354d45381c4 100644 --- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json +++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json @@ -20,6 +20,7 @@ "column_break_11", "transferred_qty", "consumed_qty", + "returned_qty", "available_qty_at_source_warehouse", "available_qty_at_wip_warehouse" ], @@ -97,6 +98,7 @@ "fieldtype": "Column Break" }, { + "columns": 1, "depends_on": "eval:!parent.skip_transfer", "fieldname": "consumed_qty", "fieldtype": "Float", @@ -127,11 +129,19 @@ "fieldtype": "Currency", "label": "Amount", "read_only": 1 + }, + { + "columns": 1, + "fieldname": "returned_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Returned Qty ", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2020-04-13 18:46:32.966416", + "modified": "2022-09-28 10:50:43.512562", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Item", @@ -140,5 +150,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js index b2428e85b741..2fb4ec679132 100644 --- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js @@ -50,7 +50,7 @@ frappe.query_reports["Work Order Consumed Materials"] = { label: __("Status"), fieldname: "status", fieldtype: "Select", - options: ["In Process", "Completed", "Stopped"] + options: ["", "In Process", "Completed", "Stopped"] }, { label: __("Excess Materials Consumed"), diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py index 8158bc9a02be..14e97d3dd781 100644 --- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py @@ -1,6 +1,8 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from collections import defaultdict + import frappe from frappe import _ @@ -18,7 +20,11 @@ def get_data(report_filters): filters = get_filter_condition(report_filters) wo_items = {} - for d in frappe.get_all("Work Order", filters=filters, fields=fields): + + work_orders = frappe.get_all("Work Order", filters=filters, fields=fields) + returned_materials = get_returned_materials(work_orders) + + for d in work_orders: d.extra_consumed_qty = 0.0 if d.consumed_qty and d.consumed_qty > d.required_qty: d.extra_consumed_qty = d.consumed_qty - d.required_qty @@ -39,6 +45,28 @@ def get_data(report_filters): return data +def get_returned_materials(work_orders): + raw_materials_qty = defaultdict(float) + + raw_materials = frappe.get_all( + "Stock Entry", + fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"], + filters=[ + ["Stock Entry", "is_return", "=", 1], + ["Stock Entry Detail", "docstatus", "=", 1], + ["Stock Entry", "work_order", "in", [d.name for d in work_orders]], + ], + ) + + for d in raw_materials: + raw_materials_qty[d.item_code] += d.qty + + for row in work_orders: + row.returned_qty = 0.0 + if raw_materials_qty.get(row.raw_material_item_code): + row.returned_qty = raw_materials_qty.get(row.raw_material_item_code) + + def get_fields(): return [ "`tabWork Order Item`.`parent`", @@ -65,7 +93,7 @@ def get_filter_condition(report_filters): for field in ["name", "production_item", "company", "status"]: value = report_filters.get(field) if value: - key = f"`{field}`" + key = f"{field}" filters.update({key: value}) return filters @@ -112,4 +140,10 @@ def get_columns(): "fieldtype": "Float", "width": 100, }, + { + "label": _("Returned Qty"), + "fieldname": "returned_qty", + "fieldtype": "Float", + "width": 100, + }, ] diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index abe98e2933e6..7e9420d50350 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -148,19 +148,19 @@ "search_index": 1 }, { - "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", - "fieldname": "purchase_order", - "fieldtype": "Link", - "label": "Purchase Order", - "options": "Purchase Order" + "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order" }, { - "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", - "fieldname": "subcontracting_order", - "fieldtype": "Link", - "label": "Subcontracting Order", - "options": "Subcontracting Order" - }, + "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", + "fieldname": "subcontracting_order", + "fieldtype": "Link", + "label": "Subcontracting Order", + "options": "Subcontracting Order" + }, { "depends_on": "eval:doc.purpose==\"Sales Return\"", "fieldname": "delivery_note_no", @@ -616,6 +616,7 @@ "fieldname": "is_return", "fieldtype": "Check", "hidden": 1, + "in_list_view": 1, "label": "Is Return", "no_copy": 1, "print_hide": 1, @@ -627,7 +628,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-05-02 05:21:39.060501", + "modified": "2022-10-07 14:39:51.943770", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8bcd772d9099..b1167351c489 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1212,13 +1212,19 @@ def get_gl_entries(self, warehouse_account): def update_work_order(self): def _validate_work_order(pro_doc): + msg, title = "", "" if flt(pro_doc.docstatus) != 1: - frappe.throw(_("Work Order {0} must be submitted").format(self.work_order)) + msg = f"Work Order {self.work_order} must be submitted" if pro_doc.status == "Stopped": - frappe.throw( - _("Transaction not allowed against stopped Work Order {0}").format(self.work_order) - ) + msg = f"Transaction not allowed against stopped Work Order {self.work_order}" + + if self.is_return and pro_doc.status not in ["Completed", "Closed"]: + title = _("Stock Return") + msg = f"Work Order {self.work_order} must be completed or closed" + + if msg: + frappe.throw(_(msg), title=title) if self.job_card: job_doc = frappe.get_doc("Job Card", self.job_card) @@ -1754,10 +1760,12 @@ def add_transfered_raw_materials_in_items(self) -> None: for key, row in available_materials.items(): remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty) - if remaining_qty_to_produce <= 0: + if remaining_qty_to_produce <= 0 and not self.is_return: continue - qty = (flt(row.qty) * flt(self.fg_completed_qty)) / remaining_qty_to_produce + qty = flt(row.qty) + if not self.is_return: + qty = (flt(row.qty) * flt(self.fg_completed_qty)) / remaining_qty_to_produce item = row.item_details if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")): @@ -1781,6 +1789,9 @@ def add_transfered_raw_materials_in_items(self) -> None: self.update_item_in_stock_entry_detail(row, item, qty) def update_item_in_stock_entry_detail(self, row, item, qty) -> None: + if not qty: + return + ste_item_details = { "from_warehouse": item.warehouse, "to_warehouse": "", @@ -1794,6 +1805,9 @@ def update_item_in_stock_entry_detail(self, row, item, qty) -> None: "original_item": item.original_item, } + if self.is_return: + ste_item_details["to_warehouse"] = item.s_warehouse + if row.serial_nos: serial_nos = row.serial_nos if item.batch_no: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_list.js b/erpnext/stock/doctype/stock_entry/stock_entry_list.js index cbc3491eba4f..4eb0da11d2f9 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_list.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry_list.js @@ -1,8 +1,13 @@ frappe.listview_settings['Stock Entry'] = { add_fields: ["`tabStock Entry`.`from_warehouse`", "`tabStock Entry`.`to_warehouse`", - "`tabStock Entry`.`purpose`", "`tabStock Entry`.`work_order`", "`tabStock Entry`.`bom_no`"], + "`tabStock Entry`.`purpose`", "`tabStock Entry`.`work_order`", "`tabStock Entry`.`bom_no`", + "`tabStock Entry`.`is_return`"], get_indicator: function (doc) { - if (doc.docstatus === 0) { + debugger + if(doc.is_return===1 && doc.purpose === "Material Transfer for Manufacture") { + return [__("Material Returned from WIP"), "orange", + "is_return,=,1|purpose,=,Material Transfer for Manufacture|docstatus,<,2"]; + } else if (doc.docstatus === 0) { return [__("Draft"), "red", "docstatus,=,0"]; } else if (doc.purpose === 'Send to Warehouse' && doc.per_transferred < 100) { From 8a4c07a7e8d85c2e27496c7e6bad4a2e16d943b9 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 4 Oct 2022 22:01:50 +0530 Subject: [PATCH 266/898] chore: add `Manual Inspection` field in Quality Inspection DocType (cherry picked from commit 39707757a6a175a3f89a44e1d13ffde9a66420b0) --- .../quality_inspection/quality_inspection.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index edfe7e98b2e8..db9322f32632 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -10,6 +10,7 @@ "naming_series", "report_date", "status", + "manual_inspection", "column_break_4", "inspection_type", "reference_type", @@ -231,6 +232,12 @@ "label": "Status", "options": "\nAccepted\nRejected", "reqd": 1 + }, + { + "default": "0", + "fieldname": "manual_inspection", + "fieldtype": "Check", + "label": "Manual Inspection" } ], "icon": "fa fa-search", @@ -238,10 +245,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-18 19:59:55.710300", + "modified": "2022-10-04 22:00:13.995221", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -262,5 +270,6 @@ "search_fields": "item_code, report_date, reference_name", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file From 50d790c919b4b653844e93797e3f7ab3b4aee5c2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 6 Oct 2022 22:36:29 +0530 Subject: [PATCH 267/898] fix: make readings status mandatory in Quality Inspection (cherry picked from commit d7c3b7633a4951102c36dffb811f364bb3b45aae) --- .../doctype/quality_inspection/quality_inspection.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 13abfad4557f..d0820004342b 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -30,6 +30,9 @@ def validate(self): if self.readings: self.inspect_and_set_status() + def before_submit(self): + self.validate_readings_status_mandatory() + @frappe.whitelist() def get_item_specification_details(self): if not self.quality_inspection_template: @@ -65,6 +68,11 @@ def on_submit(self): def on_cancel(self): self.update_qc_reference() + def validate_readings_status_mandatory(self): + for reading in self.readings: + if not reading.status: + frappe.throw(_("Row #{0}: Status is mandatory").format(reading.idx)) + def update_qc_reference(self): quality_inspection = self.name if self.docstatus == 1 else "" From e25db8b1b5d4cd118c4e6a3fe49f5262373e2aea Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 6 Oct 2022 22:22:52 +0530 Subject: [PATCH 268/898] fix: set Quality Inspection status based on readings status (cherry picked from commit 2657ece2cdeba321426637c864ce7171e2cf4427) --- .../doctype/quality_inspection/quality_inspection.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index d0820004342b..8ffd3f2ad104 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -132,6 +132,16 @@ def inspect_and_set_status(self): # if not formula based check acceptance values set self.set_status_based_on_acceptance_values(reading) + if not self.manual_inspection: + self.status = "Accepted" + for reading in self.readings: + if reading.status == "Rejected": + self.status = "Rejected" + frappe.msgprint( + _("Status set to rejected as there are one or more rejected readings."), alert=True + ) + break + def set_status_based_on_acceptance_values(self, reading): if not cint(reading.numeric): result = reading.get("reading_value") == reading.get("value") From 91b0e7ddbd9629cd936c6ca028933e6cdf50c5c0 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 4 Oct 2022 23:11:50 +0530 Subject: [PATCH 269/898] test: add test cases for Quality Inspection status (cherry picked from commit fcc1272d428c8e02b540b9b567698a1ec730fad3) --- .../test_quality_inspection.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 144f13880b16..581aa1eff407 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -184,6 +184,38 @@ def test_rejected_qi_validation(self): se.cancel() frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop") + def test_qi_status(self): + make_stock_entry( + item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100 + ) + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, status="Accepted", do_not_save=True + ) + qa.readings[0].manual_inspection = 1 + qa.save() + + # Case - 1: When there are one or more readings with rejected status and parent manual inspection is unchecked, then parent status should be set to rejected. + qa.status = "Accepted" + qa.manual_inspection = 0 + qa.readings[0].status = "Rejected" + qa.save() + self.assertEqual(qa.status, "Rejected") + + # Case - 2: When all readings have accepted status and parent manual inspection is unchecked, then parent status should be set to accepted. + qa.status = "Rejected" + qa.manual_inspection = 0 + qa.readings[0].status = "Accepted" + qa.save() + self.assertEqual(qa.status, "Accepted") + + # Case - 3: When parent manual inspection is checked, then parent status should not be changed. + qa.status = "Accepted" + qa.manual_inspection = 1 + qa.readings[0].status = "Rejected" + qa.save() + self.assertEqual(qa.status, "Accepted") + def create_quality_inspection(**args): args = frappe._dict(args) From 943e6192cc4da77ffa4672a0fc5c67adb59684c7 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 4 Oct 2022 23:13:01 +0530 Subject: [PATCH 270/898] fix(test): `test_rejected_qi_validation` (cherry picked from commit 4992e4a2b8ad4a9510485ccb214bb4484c3c8b82) --- .../stock/doctype/quality_inspection/test_quality_inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 581aa1eff407..4f19643ad526 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -160,7 +160,7 @@ def test_rejected_qi_validation(self): ) readings = [ - {"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "0.4"} + {"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "1.0"} ] qa = create_quality_inspection( From 8ba503bf0e2a6ffe9f5b3e5210c3a9dc9ec4942b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Oct 2022 10:17:19 +0530 Subject: [PATCH 271/898] fix: unlink payment on invoice cancellation (cherry picked from commit 537d953f4cb2c31ca225a3487a0ce6e62b6bacb8) --- erpnext/accounts/utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9ede67848dd7..95ba3d86ceff 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -648,6 +648,16 @@ def unlink_ref_doc_from_payment_entries(ref_doc): (now(), frappe.session.user, ref_doc.doctype, ref_doc.name), ) + ple = qb.DocType("Payment Ledger Entry") + + qb.update(ple).set(ple.against_voucher_type, ple.voucher_type).set( + ple.against_voucher_no, ple.voucher_no + ).set(ple.modified, now()).set(ple.modified_by, frappe.session.user).where( + (ple.against_voucher_type == ref_doc.doctype) + & (ple.against_voucher_no == ref_doc.name) + & (ple.delinked == 0) + ).run() + if ref_doc.doctype in ("Sales Invoice", "Purchase Invoice"): ref_doc.set("advances", []) From 998367cedc1dc5f93120a62fc8f0e102f7b223b5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 10 Oct 2022 11:34:31 +0530 Subject: [PATCH 272/898] test: update ple on payment unlink for SI's and SO's (cherry picked from commit 143f9058382852d8b2f0813d4af09e89dd817391) --- .../test_payment_ledger_entry.py | 108 +++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py index a71b19e09221..fc6dbba7e7f2 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py @@ -3,12 +3,13 @@ import frappe from frappe import qb -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import nowdate from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item @@ -127,6 +128,25 @@ def create_payment_entry(self, amount=100, posting_date=nowdate()): payment.posting_date = posting_date return payment + def create_sales_order( + self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False + ): + so = make_sales_order( + company=self.company, + transaction_date=posting_date, + customer=self.customer, + item_code=self.item, + cost_center=self.cost_center, + warehouse=self.warehouse, + debit_to=self.debit_to, + currency="INR", + qty=qty, + rate=100, + do_not_save=do_not_save, + do_not_submit=do_not_submit, + ) + return so + def clear_old_entries(self): doctype_list = [ "GL Entry", @@ -406,3 +426,89 @@ def test_je_against_inv_and_note(self): ] self.assertEqual(pl_entries_for_crnote[0], expected_values[0]) self.assertEqual(pl_entries_for_crnote[1], expected_values[1]) + + @change_settings( + "Accounts Settings", + {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + ) + def test_multi_payment_unlink_on_invoice_cancellation(self): + transaction_date = nowdate() + amount = 100 + si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date) + + for amt in [40, 40, 20]: + # payment 1 + pe = get_payment_entry(si.doctype, si.name) + pe.paid_amount = amt + pe.get("references")[0].allocated_amount = amt + pe = pe.save().submit() + + si.reload() + si.cancel() + + entries = frappe.db.get_list( + "Payment Ledger Entry", + filters={"against_voucher_type": si.doctype, "against_voucher_no": si.name, "delinked": 0}, + ) + self.assertEqual(entries, []) + + # with references removed, deletion should be possible + si.delete() + self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name) + + @change_settings( + "Accounts Settings", + {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + ) + def test_multi_je_unlink_on_invoice_cancellation(self): + transaction_date = nowdate() + amount = 100 + si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date) + + # multiple JE's against invoice + for amt in [40, 40, 20]: + je1 = self.create_journal_entry( + self.income_account, self.debit_to, amt, posting_date=transaction_date + ) + je1.get("accounts")[1].party_type = "Customer" + je1.get("accounts")[1].party = self.customer + je1.get("accounts")[1].reference_type = si.doctype + je1.get("accounts")[1].reference_name = si.name + je1 = je1.save().submit() + + si.reload() + si.cancel() + + entries = frappe.db.get_list( + "Payment Ledger Entry", + filters={"against_voucher_type": si.doctype, "against_voucher_no": si.name, "delinked": 0}, + ) + self.assertEqual(entries, []) + + # with references removed, deletion should be possible + si.delete() + self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name) + + @change_settings( + "Accounts Settings", + {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + ) + def test_advance_payment_unlink_on_order_cancellation(self): + transaction_date = nowdate() + amount = 100 + so = self.create_sales_order(qty=1, rate=amount, posting_date=transaction_date).save().submit() + + pe = get_payment_entry(so.doctype, so.name).save().submit() + + so.reload() + so.cancel() + + entries = frappe.db.get_list( + "Payment Ledger Entry", + filters={"against_voucher_type": so.doctype, "against_voucher_no": so.name, "delinked": 0}, + ) + self.assertEqual(entries, []) + + # with references removed, deletion should be possible + so.delete() + self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, so.doctype, so.name) From 0a24859c9c00e8f0ef857e4859d1e483f94150b6 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Mon, 10 Oct 2022 23:04:34 +0530 Subject: [PATCH 273/898] fix: use naming_series in budget (cherry picked from commit e4d7d8c42de1c087da36ac334a0518af0cb4a48b) --- erpnext/accounts/doctype/budget/budget.json | 16 +++++++++++++++- erpnext/accounts/doctype/budget/budget.py | 9 +++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.json b/erpnext/accounts/doctype/budget/budget.json index fc4dd200ea21..f0566f443686 100644 --- a/erpnext/accounts/doctype/budget/budget.json +++ b/erpnext/accounts/doctype/budget/budget.json @@ -1,6 +1,7 @@ { "actions": [], "allow_import": 1, + "autoname": "naming_series:", "creation": "2016-05-16 11:42:29.632528", "doctype": "DocType", "editable_grid": 1, @@ -9,6 +10,7 @@ "budget_against", "company", "cost_center", + "naming_series", "project", "fiscal_year", "column_break_3", @@ -190,15 +192,26 @@ "label": "Budget Accounts", "options": "Budget Account", "reqd": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Data", + "hidden": 1, + "label": "Series", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "set_only_once": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-06 15:13:54.055854", + "modified": "2022-10-10 22:14:36.361509", "modified_by": "Administrator", "module": "Accounts", "name": "Budget", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -220,5 +233,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 5527f9fb99fb..6ac3350c3b0b 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -5,7 +5,6 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.model.naming import make_autoname from frappe.utils import add_months, flt, fmt_money, get_last_day, getdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -23,11 +22,6 @@ class DuplicateBudgetError(frappe.ValidationError): class Budget(Document): - def autoname(self): - self.name = make_autoname( - self.get(frappe.scrub(self.budget_against)) + "/" + self.fiscal_year + "/.###" - ) - def validate(self): if not self.get(frappe.scrub(self.budget_against)): frappe.throw(_("{0} is mandatory").format(self.budget_against)) @@ -109,6 +103,9 @@ def validate_applicable_for(self): ): self.applicable_on_booking_actual_expenses = 1 + def before_naming(self): + self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###" + def validate_expense_against_budget(args): args = frappe._dict(args) From c9002526b9d5d8921a4bb508591ae900e0ae2113 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Oct 2022 12:33:51 +0530 Subject: [PATCH 274/898] chore: update codeowners --- CODEOWNERS | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index ecbae86d96b4..e0a0fb75f8e7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -11,22 +11,18 @@ erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar erpnext/support/ @nextchamp-saqib @deepeshgarg007 pos* @nextchamp-saqib -erpnext/buying/ @marination @rohitwaghchaure @s-aga-r -erpnext/e_commerce/ @marination -erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r -erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r -erpnext/portal/ @marination -erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r -erpnext/shopping_cart/ @marination -erpnext/stock/ @marination @rohitwaghchaure @s-aga-r +erpnext/buying/ @rohitwaghchaure @s-aga-r +erpnext/maintenance/ @rohitwaghchaure @s-aga-r +erpnext/manufacturing/ @rohitwaghchaure @s-aga-r +erpnext/quality_management/ @rohitwaghchaure @s-aga-r +erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/crm/ @NagariaHussain erpnext/education/ @rutwikhdev erpnext/projects/ @ruchamahabal -erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination -erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination -erpnext/public/ @nextchamp-saqib @marination +erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure +erpnext/patches/ @deepeshgarg007 @nextchamp-saqib .github/ @ankush -pyproject.toml @gavindsouza @ankush +pyproject.toml @ankush From 4b65dd7f2a55076ff9922e7113e704d1bfaf089a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Oct 2022 08:14:15 +0530 Subject: [PATCH 275/898] fix: value error on pos submit (cherry picked from commit 4b908ebcd6f01459bbf29db3a0863963dac45fac) --- erpnext/stock/doctype/serial_no/serial_no.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 6042ed4ac5dd..a2748d0d09a7 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -846,16 +846,15 @@ def get_pos_reserved_serial_nos(filters): pos_transacted_sr_nos = query.run(as_dict=True) - reserved_sr_nos = [] - returned_sr_nos = [] + reserved_sr_nos = set() + returned_sr_nos = set() for d in pos_transacted_sr_nos: if d.is_return == 0: - reserved_sr_nos += get_serial_nos(d.serial_no) + [reserved_sr_nos.add(x) for x in get_serial_nos(d.serial_no)] elif d.is_return == 1: - returned_sr_nos += get_serial_nos(d.serial_no) + [returned_sr_nos.add(x) for x in get_serial_nos(d.serial_no)] - for sr_no in returned_sr_nos: - reserved_sr_nos.remove(sr_no) + reserved_sr_nos = list(reserved_sr_nos - returned_sr_nos) return reserved_sr_nos From 6fa1b892f75d75c97641c444a17c0f12f5136c6a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Oct 2022 11:17:35 +0530 Subject: [PATCH 276/898] test: value error on serial no validation on pos (cherry picked from commit 9e2bd10d032f49ed65896d7456cbc7c176b5570e) --- .../doctype/pos_invoice/test_pos_invoice.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 70f128e0e39a..3132fdd259a0 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -495,6 +495,67 @@ def test_invalid_serial_no_validation(self): self.assertRaises(frappe.ValidationError, pos.submit) + def test_value_error_on_serial_no_validation(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + + se = make_serialized_item( + company="_Test Company", + target_warehouse="Stores - _TC", + cost_center="Main - _TC", + expense_account="Cost of Goods Sold - _TC", + ) + serial_nos = se.get("items")[0].serial_no + + # make a pos invoice + pos = create_pos_invoice( + company="_Test Company", + debit_to="Debtors - _TC", + account_for_change_amount="Cash - _TC", + warehouse="Stores - _TC", + income_account="Sales - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + item=se.get("items")[0].item_code, + rate=1000, + qty=1, + do_not_save=1, + ) + pos.get("items")[0].has_serial_no = 1 + pos.get("items")[0].serial_no = serial_nos.split("\n")[0] + pos.set("payments", []) + pos.append( + "payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1} + ) + pos = pos.save().submit() + + # make a return + pos_return = make_sales_return(pos.name) + pos_return.paid_amount = pos_return.grand_total + pos_return.save() + pos_return.submit() + + # set docstatus to 2 for pos to trigger this issue + frappe.db.set_value("POS Invoice", pos.name, "docstatus", 2) + + pos2 = create_pos_invoice( + company="_Test Company", + debit_to="Debtors - _TC", + account_for_change_amount="Cash - _TC", + warehouse="Stores - _TC", + income_account="Sales - _TC", + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + item=se.get("items")[0].item_code, + rate=1000, + qty=1, + do_not_save=1, + ) + + pos2.get("items")[0].has_serial_no = 1 + pos2.get("items")[0].serial_no = serial_nos.split("\n")[0] + # Value error should not be triggered on validation + pos2.save() + def test_loyalty_points(self): from erpnext.accounts.doctype.loyalty_program.loyalty_program import ( get_loyalty_program_details_with_points, From 31cd96bc89f9f1b0ca29359ceb9ae542e32289ff Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 27 Sep 2022 14:19:29 +0530 Subject: [PATCH 277/898] feat: Tab Break in Sales Order, Delivery Note, Sales Invoice and Purchase Order (cherry picked from commit f6613e1e4c53821bd0924539936b8a70d513773f) --- .../doctype/sales_invoice/sales_invoice.json | 354 +++++++++++------- .../purchase_order/purchase_order.json | 135 ++++--- .../doctype/sales_order/sales_order.json | 231 ++++++++---- .../doctype/delivery_note/delivery_note.json | 278 ++++++++------ 4 files changed, 629 insertions(+), 369 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 2da515737a97..d8b413d9ff34 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -12,44 +12,29 @@ "customer", "customer_name", "tax_id", - "pos_profile", - "is_pos", - "is_consolidated", - "is_return", - "is_debit_note", - "update_billed_amount_in_sales_order", "column_break1", - "company", - "company_tax_id", "posting_date", "posting_time", "set_posting_time", "due_date", + "column_break_14", + "company", + "company_tax_id", + "is_pos", + "pos_profile", + "is_consolidated", + "is_return", "return_against", + "update_billed_amount_in_sales_order", + "is_debit_note", "amended_from", "accounting_dimensions_section", - "project", - "dimension_col_break", "cost_center", - "customer_po_details", - "po_no", - "column_break_23", - "po_date", - "address_and_contact", - "customer_address", - "address_display", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "territory", - "col_break4", - "shipping_address_name", - "shipping_address", - "company_address", - "company_address_display", - "dispatch_address_name", - "dispatch_address", + "dimension_col_break", + "project", + "column_break_27", + "campaign", + "source", "currency_and_price_list", "currency", "conversion_rate", @@ -58,60 +43,35 @@ "price_list_currency", "plc_conversion_rate", "ignore_pricing_rule", - "sec_warehouse", - "set_warehouse", - "column_break_55", - "set_target_warehouse", "items_section", - "update_stock", "scan_barcode", + "update_stock", + "column_break_39", + "set_warehouse", + "set_target_warehouse", + "section_break_42", "items", - "pricing_rule_details", - "pricing_rules", - "packing_list", - "packed_items", - "product_bundle_help", - "time_sheet_list", - "timesheets", - "total_billing_amount", - "total_billing_hours", "section_break_30", "total_qty", + "total_net_weight", + "column_break_32", "base_total", "base_net_total", - "column_break_32", - "total_net_weight", + "column_break_52", "total", "net_total", "taxes_section", "taxes_and_charges", "column_break_38", "shipping_rule", + "column_break_55", "tax_category", "section_break_40", "taxes", - "sec_tax_breakup", - "other_charges_calculation", "section_break_43", "base_total_taxes_and_charges", "column_break_47", "total_taxes_and_charges", - "loyalty_points_redemption", - "loyalty_points", - "loyalty_amount", - "redeem_loyalty_points", - "column_break_77", - "loyalty_program", - "loyalty_redemption_account", - "loyalty_redemption_cost_center", - "section_break_49", - "apply_discount_on", - "is_cash_or_non_trade_discount", - "base_discount_amount", - "additional_discount_account", - "column_break_51", - "additional_discount_percentage", - "discount_amount", "totals", "base_grand_total", "base_rounding_adjustment", @@ -125,21 +85,28 @@ "total_advance", "outstanding_amount", "disable_rounded_total", - "column_break4", - "write_off_amount", - "base_write_off_amount", - "write_off_outstanding_amount_automatically", - "column_break_74", - "write_off_account", - "write_off_cost_center", - "advances_section", - "allocate_advances_automatically", - "get_advances", - "advances", - "payment_schedule_section", - "ignore_default_payment_terms_template", - "payment_terms_template", - "payment_schedule", + "section_break_49", + "apply_discount_on", + "is_cash_or_non_trade_discount", + "base_discount_amount", + "additional_discount_account", + "column_break_51", + "additional_discount_percentage", + "discount_amount", + "sec_tax_breakup", + "other_charges_calculation", + "pricing_rule_details", + "pricing_rules", + "packing_list", + "packed_items", + "product_bundle_help", + "time_sheet_list", + "timesheets", + "section_break_104", + "total_billing_hours", + "column_break_106", + "total_billing_amount", + "payments_tab", "payments_section", "cash_bank_account", "payments", @@ -152,47 +119,95 @@ "column_break_90", "change_amount", "account_for_change_amount", + "advances_section", + "allocate_advances_automatically", + "get_advances", + "advances", + "write_off_section", + "write_off_amount", + "base_write_off_amount", + "write_off_outstanding_amount_automatically", + "column_break_74", + "write_off_account", + "write_off_cost_center", + "loyalty_points_redemption", + "redeem_loyalty_points", + "loyalty_points", + "loyalty_amount", + "column_break_77", + "loyalty_program", + "loyalty_redemption_account", + "loyalty_redemption_cost_center", + "contact_and_address_tab", + "address_and_contact", + "customer_address", + "address_display", + "col_break4", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "territory", + "shipping_address_section", + "shipping_address_name", + "shipping_address", + "shipping_addr_col_break", + "dispatch_address_name", + "dispatch_address", + "company_address_section", + "company_address", + "company_addr_col_break", + "company_address_display", + "terms_tab", + "payment_schedule_section", + "ignore_default_payment_terms_template", + "payment_terms_template", + "payment_schedule", "terms_section_break", "tc_name", "terms", - "edit_printing_settings", - "letter_head", - "group_same_items", - "select_print_heading", - "column_break_84", - "language", - "more_information", - "status", - "inter_company_invoice_reference", - "represents_company", - "customer_group", - "campaign", - "col_break23", - "is_internal_customer", - "is_discounted", - "source", + "more_info_tab", + "customer_po_details", + "po_no", + "column_break_23", + "po_date", "more_info", "debit_to", "party_account_currency", "is_opening", "column_break8", "unrealized_profit_loss_account", - "remarks", + "against_income_account", "sales_team_section_break", "sales_partner", - "column_break10", "amount_eligible_for_commission", + "column_break10", "commission_rate", "total_commission", "section_break2", "sales_team", + "edit_printing_settings", + "letter_head", + "group_same_items", + "column_break_84", + "select_print_heading", + "language", "subscription_section", "from_date", - "to_date", - "column_break_140", "auto_repeat", + "column_break_140", + "to_date", "update_auto_repeat_reference", - "against_income_account" + "more_information", + "status", + "inter_company_invoice_reference", + "represents_company", + "customer_group", + "col_break23", + "is_internal_customer", + "is_discounted", + "remarks", + "connections_tab" ], "fields": [ { @@ -453,12 +468,11 @@ "label": "Customer's Purchase Order Date" }, { - "collapsible": 1, "fieldname": "address_and_contact", "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Address and Contact" + "label": "Billing Address" }, { "fieldname": "customer_address", @@ -560,7 +574,6 @@ { "fieldname": "company_address_display", "fieldtype": "Small Text", - "hidden": 1, "hide_days": 1, "hide_seconds": 1, "label": "Company Address", @@ -651,13 +664,6 @@ "label": "Ignore Pricing Rule", "print_hide": 1 }, - { - "fieldname": "sec_warehouse", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Warehouse" - }, { "depends_on": "update_stock", "fieldname": "set_warehouse", @@ -671,6 +677,7 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, "hide_seconds": 1, "label": "Items", @@ -702,7 +709,6 @@ "fieldtype": "Table", "hide_days": 1, "hide_seconds": 1, - "label": "Items", "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Sales Invoice Item", @@ -755,9 +761,10 @@ { "collapsible": 1, "collapsible_depends_on": "eval:doc.total_billing_amount > 0", - "depends_on": "eval: !doc.is_return", + "depends_on": "eval:!doc.is_return", "fieldname": "time_sheet_list", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, "hide_seconds": 1, "label": "Time Sheet List" @@ -856,8 +863,10 @@ { "fieldname": "taxes_section", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, "hide_seconds": 1, + "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" }, @@ -900,6 +909,7 @@ { "fieldname": "section_break_40", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, "hide_seconds": 1 }, @@ -908,7 +918,6 @@ "fieldtype": "Table", "hide_days": 1, "hide_seconds": 1, - "label": "Sales Taxes and Charges", "oldfieldname": "other_charges", "oldfieldtype": "Table", "options": "Sales Taxes and Charges" @@ -1314,6 +1323,8 @@ "print_hide": 1 }, { + "collapsible": 1, + "collapsible_depends_on": "eval:!doc.is_pos", "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", @@ -1383,10 +1394,13 @@ "read_only": 1 }, { + "collapsible": 1, + "depends_on": "is_pos", "fieldname": "section_break_88", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Changes" }, { "depends_on": "is_pos", @@ -1427,17 +1441,6 @@ "options": "Account", "print_hide": 1 }, - { - "collapsible": 1, - "collapsible_depends_on": "write_off_amount", - "depends_on": "grand_total", - "fieldname": "column_break4", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Write Off", - "width": "50%" - }, { "fieldname": "write_off_amount", "fieldtype": "Currency", @@ -1530,7 +1533,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Printing Settings" + "label": "Print Settings" }, { "allow_on_submit": 1, @@ -1591,7 +1594,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "More Information" + "label": "Additional Info" }, { "fieldname": "inter_company_invoice_reference", @@ -1814,7 +1817,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Subscription Section" + "label": "Subscription" }, { "allow_on_submit": 1, @@ -1935,10 +1938,6 @@ "options": "Company", "read_only": 1 }, - { - "fieldname": "column_break_55", - "fieldtype": "Column Break" - }, { "depends_on": "eval: doc.is_internal_customer && doc.update_stock", "fieldname": "set_target_warehouse", @@ -2009,6 +2008,97 @@ "fieldname": "is_cash_or_non_trade_discount", "fieldtype": "Check", "label": "Is Cash or Non Trade Discount" + }, + { + "fieldname": "contact_and_address_tab", + "fieldtype": "Tab Break", + "label": "Contact & Address" + }, + { + "fieldname": "payments_tab", + "fieldtype": "Tab Break", + "label": "Payments" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_42", + "fieldtype": "Section Break", + "hide_border": 1, + "hide_days": 1, + "hide_seconds": 1 + }, + { + "fieldname": "column_break_55", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" + }, + { + "fieldname": "company_address_section", + "fieldtype": "Section Break", + "label": "Company Address" + }, + { + "fieldname": "shipping_addr_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_addr_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_52", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:(!doc.is_return && doc.total_billing_amount > 0)", + "fieldname": "section_break_104", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_106", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "collapsible_depends_on": "write_off_amount", + "depends_on": "grand_total", + "fieldname": "write_off_section", + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, + "label": "Write Off", + "width": "50%" } ], "icon": "fa fa-file-text", @@ -2021,7 +2111,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-09-16 17:44:22.227332", + "modified": "2022-09-27 14:15:35.293825", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index fb8f25a0dfc4..7f0fc5797415 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -12,43 +12,24 @@ "title", "naming_series", "supplier", - "get_items_from_open_material_requests", "supplier_name", - "apply_tds", - "tax_withholding_category", - "is_subcontracted", - "supplier_warehouse", - "column_break1", - "company", + "get_items_from_open_material_requests", + "column_break_7", "transaction_date", "schedule_date", "order_confirmation_no", "order_confirmation_date", + "column_break1", + "company", + "apply_tds", + "tax_withholding_category", + "is_subcontracted", + "supplier_warehouse", "amended_from", "accounting_dimensions_section", "cost_center", "dimension_col_break", "project", - "drop_ship", - "customer", - "customer_name", - "column_break_19", - "customer_contact_person", - "customer_contact_display", - "customer_contact_mobile", - "customer_contact_email", - "section_addresses", - "supplier_address", - "address_display", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "col_break_address", - "shipping_address", - "shipping_address_display", - "billing_address", - "billing_address_display", "currency_and_price_list", "currency", "conversion_rate", @@ -57,7 +38,6 @@ "price_list_currency", "plc_conversion_rate", "ignore_pricing_rule", - "section_break_45", "before_items_section", "scan_barcode", "set_from_warehouse", @@ -67,10 +47,11 @@ "items", "sb_last_purchase", "total_qty", + "total_net_weight", + "column_break_40", "base_total", "base_net_total", "column_break_26", - "total_net_weight", "total", "net_total", "section_break_48", @@ -79,14 +60,13 @@ "set_reserve_warehouse", "supplied_items", "taxes_section", + "taxes_and_charges", + "column_break_53", "tax_category", "column_break_50", "shipping_rule", "section_break_52", - "taxes_and_charges", "taxes", - "sec_tax_breakup", - "other_charges_calculation", "totals", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", @@ -95,12 +75,6 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", - "discount_section", - "apply_discount_on", - "base_discount_amount", - "column_break_45", - "additional_discount_percentage", - "discount_amount", "totals_section", "base_grand_total", "base_rounding_adjustment", @@ -113,17 +87,48 @@ "disable_rounded_total", "in_words", "advance_paid", + "discount_section", + "apply_discount_on", + "base_discount_amount", + "column_break_45", + "additional_discount_percentage", + "discount_amount", + "sec_tax_breakup", + "other_charges_calculation", + "contacts_and_addresses_tab", + "section_addresses", + "supplier_address", + "address_display", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "col_break_address", + "shipping_address", + "shipping_address_display", + "billing_address", + "billing_address_display", + "terms_tab", + "terms_section_break", + "tc_name", + "terms", "payment_schedule_section", "payment_terms_template", "payment_schedule", + "drop_ship", + "customer", + "customer_name", + "column_break_19", + "customer_contact_person", + "customer_contact_display", + "customer_contact_mobile", + "customer_contact_email", + "more_info_tab", "tracking_section", "status", "column_break_75", "per_billed", "per_received", - "terms_section_break", - "tc_name", - "terms", "column_break5", "letter_head", "select_print_heading", @@ -143,7 +148,8 @@ "is_internal_supplier", "represents_company", "inter_company_order_reference", - "is_old_subcontracting_flow" + "is_old_subcontracting_flow", + "dashboard" ], "fields": [ { @@ -483,7 +489,6 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", - "label": "Items", "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Purchase Order Item", @@ -579,6 +584,8 @@ { "fieldname": "taxes_section", "fieldtype": "Section Break", + "hide_border": 1, + "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" }, @@ -633,7 +640,6 @@ { "fieldname": "totals", "fieldtype": "Section Break", - "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" }, @@ -1098,7 +1104,9 @@ }, { "fieldname": "before_items_section", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1, + "label": "Items" }, { "fieldname": "items_col_break", @@ -1133,10 +1141,6 @@ "label": "Tax Withholding Category", "options": "Tax Withholding Category" }, - { - "fieldname": "section_break_45", - "fieldtype": "Section Break" - }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", @@ -1173,6 +1177,39 @@ "fieldtype": "Link", "label": "Set From Warehouse", "options": "Warehouse" + }, + { + "fieldname": "contacts_and_addresses_tab", + "fieldtype": "Tab Break", + "label": "Contacts & Addresses" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "dashboard", + "fieldtype": "Tab Break", + "label": "Dashboard", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_40", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_53", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index ff269d0e6840..5013d51a8a03 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -15,37 +15,25 @@ "naming_series", "customer", "customer_name", + "tax_id", "order_type", - "skip_delivery_note", - "column_break1", - "amended_from", - "company", + "column_break_7", "transaction_date", "delivery_date", + "column_break1", "po_no", "po_date", - "tax_id", + "company", + "skip_delivery_note", + "amended_from", "accounting_dimensions_section", "cost_center", "dimension_col_break", "project", - "contact_info", - "customer_address", - "address_display", - "contact_person", - "contact_display", - "contact_phone", - "contact_mobile", - "contact_email", - "company_address", - "company_address_display", - "col_break46", - "shipping_address_name", - "shipping_address", - "dispatch_address_name", - "dispatch_address", - "customer_group", - "territory", + "column_break_77", + "source", + "campaign", + "custom_dimensions_section", "currency_and_price_list", "currency", "conversion_rate", @@ -55,46 +43,32 @@ "plc_conversion_rate", "ignore_pricing_rule", "sec_warehouse", + "scan_barcode", + "column_break_28", "set_warehouse", "items_section", - "scan_barcode", "items", - "packing_list", - "packed_items", - "pricing_rule_details", - "pricing_rules", "section_break_31", - "column_break_33a", "total_qty", + "total_net_weight", + "column_break_33", "base_total", "base_net_total", - "column_break_33", - "total_net_weight", + "column_break_33a", "total", "net_total", "taxes_section", - "tax_category", + "taxes_and_charges", "column_break_38", + "tax_category", + "column_break_49", "shipping_rule", "section_break_40", - "taxes_and_charges", "taxes", - "sec_tax_breakup", - "other_charges_calculation", "section_break_43", "base_total_taxes_and_charges", "column_break_46", "total_taxes_and_charges", - "loyalty_points_redemption", - "loyalty_points", - "loyalty_amount", - "section_break_48", - "coupon_code", - "apply_discount_on", - "base_discount_amount", - "column_break_50", - "additional_discount_percentage", - "discount_amount", "totals", "base_grand_total", "base_rounding_adjustment", @@ -107,26 +81,49 @@ "in_words", "advance_paid", "disable_rounded_total", + "section_break_48", + "coupon_code", + "apply_discount_on", + "base_discount_amount", + "column_break_50", + "additional_discount_percentage", + "discount_amount", + "sec_tax_breakup", + "other_charges_calculation", + "packing_list", + "packed_items", + "pricing_rule_details", + "pricing_rules", + "contact_info", + "billing_address_column", + "customer_address", + "address_display", + "customer_group", + "territory", + "column_break_84", + "contact_person", + "contact_display", + "contact_phone", + "contact_mobile", + "contact_email", + "shipping_address_column", + "shipping_address_name", + "shipping_address", + "column_break_93", + "dispatch_address_name", + "dispatch_address", + "col_break46", + "company_address", + "column_break_92", + "company_address_display", "payment_schedule_section", + "payment_terms_section", "payment_terms_template", "payment_schedule", "terms_section_break", "tc_name", "terms", "more_info", - "is_internal_customer", - "represents_company", - "inter_company_order_reference", - "party_account_currency", - "column_break_77", - "source", - "campaign", - "printing_details", - "language", - "letter_head", - "column_break4", - "select_print_heading", - "group_same_items", "section_break_78", "status", "delivery_status", @@ -143,12 +140,29 @@ "total_commission", "section_break1", "sales_team", + "loyalty_points_redemption", + "loyalty_points", + "column_break_116", + "loyalty_amount", "subscription_section", "from_date", "to_date", "column_break_108", "auto_repeat", - "update_auto_repeat_reference" + "update_auto_repeat_reference", + "printing_details", + "language", + "letter_head", + "group_same_items", + "column_break4", + "select_print_heading", + "additional_info_section", + "is_internal_customer", + "represents_company", + "column_break_152", + "inter_company_order_reference", + "party_account_currency", + "connections_tab" ], "fields": [ { @@ -334,10 +348,10 @@ "collapsible": 1, "depends_on": "customer", "fieldname": "contact_info", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "hide_days": 1, "hide_seconds": 1, - "label": "Address and Contact", + "label": "Address & Contact", "options": "fa fa-bullhorn" }, { @@ -414,9 +428,10 @@ }, { "fieldname": "col_break46", - "fieldtype": "Column Break", + "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, + "label": "Company Address", "width": "50%" }, { @@ -550,8 +565,10 @@ { "fieldname": "sec_warehouse", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Items" }, { "fieldname": "set_warehouse", @@ -565,6 +582,7 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, "hide_seconds": 1, "oldfieldtype": "Section Break", @@ -688,7 +706,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Taxes and Charges", + "label": "Taxes", "oldfieldtype": "Section Break", "options": "fa fa-money" }, @@ -720,6 +738,7 @@ { "fieldname": "section_break_40", "fieldtype": "Section Break", + "hide_border": 1, "hide_days": 1, "hide_seconds": 1 }, @@ -804,7 +823,7 @@ "hidden": 1, "hide_days": 1, "hide_seconds": 1, - "label": "Loyalty Points Redemption", + "label": "Loyalty Points", "print_hide": 1 }, { @@ -833,7 +852,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Additional Discount and Coupon Code" + "label": "Additional Discount" }, { "fieldname": "coupon_code", @@ -891,6 +910,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, + "label": "Totals", "oldfieldtype": "Section Break", "options": "fa fa-money", "print_hide": 1 @@ -1045,10 +1065,10 @@ }, { "fieldname": "payment_schedule_section", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "hide_days": 1, "hide_seconds": 1, - "label": "Payment Terms" + "label": "Terms" }, { "fieldname": "payment_terms_template", @@ -1070,13 +1090,12 @@ "print_hide": 1 }, { - "collapsible": 1, "collapsible_depends_on": "terms", "fieldname": "terms_section_break", "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Terms and Conditions", + "label": "Terms & Conditions", "oldfieldtype": "Section Break", "options": "fa fa-legal" }, @@ -1104,10 +1123,10 @@ "collapsible": 1, "collapsible_depends_on": "project", "fieldname": "more_info", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "hide_days": 1, "hide_seconds": 1, - "label": "More Information", + "label": "More Info", "oldfieldtype": "Section Break", "options": "fa fa-file-text", "print_hide": 1 @@ -1122,7 +1141,6 @@ "read_only": 1 }, { - "description": "Track this Sales Order against any Project", "fieldname": "project", "fieldtype": "Link", "hide_days": 1, @@ -1240,7 +1258,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Billing and Delivery Status", + "label": "Status", "oldfieldtype": "Column Break", "print_hide": 1, "width": "50%" @@ -1410,7 +1428,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Auto Repeat Section", + "label": "Auto Repeat", "no_copy": 1, "print_hide": 1, "read_only": 1 @@ -1542,13 +1560,76 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_49", + "fieldtype": "Column Break" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "payment_terms_section", + "fieldtype": "Section Break", + "label": "Payment Terms" + }, + { + "fieldname": "column_break_116", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_address_column", + "fieldtype": "Section Break", + "label": "Billing Address" + }, + { + "fieldname": "shipping_address_column", + "fieldtype": "Section Break", + "label": "Shipping Address" + }, + { + "fieldname": "column_break_93", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_84", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_92", + "fieldtype": "Column Break" + }, + { + "fieldname": "custom_dimensions_section", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "additional_info_section", + "fieldtype": "Section Break", + "label": "Additional Info" + }, + { + "fieldname": "column_break_152", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-16 17:43:57.007441", + "modified": "2022-09-27 12:38:57.007441", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index a8f907ed7113..519301c304db 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -8,46 +8,29 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "delivery_to_section", - "column_break0", "title", "naming_series", "customer", + "tax_id", "customer_name", "column_break1", - "amended_from", - "company", "posting_date", "posting_time", "set_posting_time", + "column_break_10", + "company", + "amended_from", "is_return", "issue_credit_note", "return_against", "accounting_dimensions_section", "cost_center", - "dimension_col_break", + "column_break_18", "project", - "customer_po_details", - "po_no", - "column_break_17", - "po_date", - "section_break_18", - "pick_list", - "contact_info", - "shipping_address_name", - "shipping_address", - "dispatch_address_name", - "dispatch_address", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "col_break21", - "customer_address", - "tax_id", - "address_display", - "company_address", - "company_address_display", + "dimension_col_break", + "campaign", + "source", + "custom_dimensions_section", "currency_and_price_list", "currency", "conversion_rate", @@ -56,45 +39,36 @@ "price_list_currency", "plc_conversion_rate", "ignore_pricing_rule", - "sec_warehouse", - "set_warehouse", - "col_break_warehouse", - "set_target_warehouse", "items_section", "scan_barcode", + "column_break_25", + "pick_list", + "col_break_warehouse", + "set_warehouse", + "set_target_warehouse", + "section_break_30", "items", - "pricing_rule_details", - "pricing_rules", - "packing_list", - "packed_items", - "product_bundle_help", "section_break_31", "total_qty", + "total_net_weight", + "column_break_35", "base_total", "base_net_total", "column_break_33", - "total_net_weight", "total", "net_total", "taxes_section", + "taxes_and_charges", + "column_break_43", "tax_category", "column_break_39", "shipping_rule", "section_break_41", - "taxes_and_charges", "taxes", - "sec_tax_breakup", - "other_charges_calculation", "section_break_44", "base_total_taxes_and_charges", "column_break_47", "total_taxes_and_charges", - "section_break_49", - "apply_discount_on", - "base_discount_amount", - "column_break_51", - "additional_discount_percentage", - "discount_amount", "totals", "base_grand_total", "base_rounding_adjustment", @@ -106,9 +80,50 @@ "rounded_total", "in_words", "disable_rounded_total", - "terms_section_break", + "section_break_49", + "apply_discount_on", + "base_discount_amount", + "column_break_51", + "additional_discount_percentage", + "discount_amount", + "sec_tax_breakup", + "other_charges_calculation", + "packing_list", + "packed_items", + "product_bundle_help", + "pricing_rule_details", + "pricing_rules", + "address_and_contact_tab", + "contact_info", + "customer_address", + "address_display", + "col_break21", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "shipping_address_section", + "shipping_address_name", + "shipping_address", + "column_break_95", + "dispatch_address_name", + "dispatch_address", + "company_address_section", + "company_address", + "column_break_101", + "company_address_display", + "terms_tab", "tc_name", "terms", + "more_info_tab", + "section_break_83", + "per_billed", + "status", + "column_break_112", + "per_installed", + "installation_status", + "column_break_89", + "per_returned", "transporter_info", "transporter", "driver", @@ -118,56 +133,39 @@ "transporter_name", "driver_name", "lr_date", - "more_info", - "campaign", - "source", - "column_break5", - "is_internal_customer", - "represents_company", - "inter_company_reference", - "per_billed", - "customer_group", - "territory", + "customer_po_details", + "po_no", + "column_break_17", + "po_date", + "sales_team_section_break", + "sales_partner", + "amount_eligible_for_commission", + "column_break7", + "commission_rate", + "total_commission", + "section_break1", + "sales_team", + "subscription_section", + "auto_repeat", "printing_details", "letter_head", "select_print_heading", - "language", "column_break_88", + "language", "print_without_amount", "group_same_items", - "section_break_83", - "status", - "per_installed", - "installation_status", - "column_break_89", - "per_returned", + "more_info", + "is_internal_customer", + "represents_company", + "inter_company_reference", + "customer_group", + "territory", + "column_break5", "excise_page", "instructions", - "subscription_section", - "auto_repeat", - "sales_team_section_break", - "sales_partner", - "column_break7", - "amount_eligible_for_commission", - "commission_rate", - "total_commission", - "section_break1", - "sales_team" + "connections_tab" ], "fields": [ - { - "fieldname": "delivery_to_section", - "fieldtype": "Section Break", - "label": "Delivery To", - "options": "fa fa-user" - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "print_width": "50%", - "width": "50%" - }, { "allow_on_submit": 1, "default": "{customer_name}", @@ -337,11 +335,10 @@ "width": "100px" }, { - "collapsible": 1, "depends_on": "customer", "fieldname": "contact_info", "fieldtype": "Section Break", - "label": "Address and Contact", + "label": "Billing Address", "options": "fa fa-bullhorn" }, { @@ -493,10 +490,6 @@ "permlevel": 1, "print_hide": 1 }, - { - "fieldname": "sec_warehouse", - "fieldtype": "Section Break" - }, { "fieldname": "set_warehouse", "fieldtype": "Link", @@ -511,6 +504,8 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, + "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -524,7 +519,6 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", - "label": "Items", "oldfieldname": "delivery_note_details", "oldfieldtype": "Table", "options": "Delivery Note Item", @@ -628,6 +622,7 @@ { "fieldname": "taxes_section", "fieldtype": "Section Break", + "hide_border": 1, "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" @@ -653,10 +648,10 @@ }, { "fieldname": "section_break_41", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { - "description": "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.", "fieldname": "taxes_and_charges", "fieldtype": "Link", "label": "Sales Taxes and Charges Template", @@ -668,7 +663,6 @@ { "fieldname": "taxes", "fieldtype": "Table", - "label": "Sales Taxes and Charges", "oldfieldname": "other_charges", "oldfieldtype": "Table", "options": "Sales Taxes and Charges" @@ -759,6 +753,7 @@ { "fieldname": "totals", "fieldtype": "Section Break", + "label": "Totals", "oldfieldtype": "Section Break", "options": "fa fa-money" }, @@ -863,15 +858,6 @@ "read_only": 1, "width": "150px" }, - { - "collapsible": 1, - "collapsible_depends_on": "terms", - "fieldname": "terms_section_break", - "fieldtype": "Section Break", - "label": "Terms and Conditions", - "oldfieldtype": "Section Break", - "options": "fa fa-legal" - }, { "fieldname": "tc_name", "fieldtype": "Link", @@ -965,13 +951,12 @@ "collapsible": 1, "fieldname": "more_info", "fieldtype": "Section Break", - "label": "More Information", + "label": "Additional Info", "oldfieldtype": "Section Break", "options": "fa fa-file-text", "print_hide": 1 }, { - "description": "Track this Delivery Note against any Project", "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -1110,7 +1095,6 @@ }, { "depends_on": "eval:!doc.__islocal", - "description": "% of materials delivered against this Delivery Note", "fieldname": "per_installed", "fieldtype": "Percent", "in_list_view": 1, @@ -1237,10 +1221,6 @@ "options": "Pick List", "read_only": 1 }, - { - "fieldname": "section_break_18", - "fieldtype": "Section Break" - }, { "default": "0", "fetch_from": "customer.is_internal_customer", @@ -1329,13 +1309,85 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_30", + "fieldtype": "Section Break", + "hide_border": 1 + }, + { + "fieldname": "column_break_35", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_43", + "fieldtype": "Column Break" + }, + { + "fieldname": "custom_dimensions_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" + }, + { + "fieldname": "column_break_95", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_address_section", + "fieldtype": "Section Break", + "label": "Company Address" + }, + { + "fieldname": "column_break_101", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_112", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-09-16 17:46:17.701904", + "modified": "2022-09-27 12:49:21.640004", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From b0a9f79e181826eef48a62fc28a17d73abdbb129 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 27 Sep 2022 15:16:27 +0530 Subject: [PATCH 278/898] feat: Tab Break in Quotation (cherry picked from commit 754656213982096f5b108635ac33c7e9f212746c) --- .../selling/doctype/quotation/quotation.json | 225 ++++++++++++------ .../doctype/sales_order/sales_order.json | 3 +- .../doctype/delivery_note/delivery_note.json | 3 +- 3 files changed, 151 insertions(+), 80 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index c58a46ba513a..78b739cedb8c 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -15,26 +15,13 @@ "quotation_to", "party_name", "customer_name", - "column_break1", - "amended_from", - "company", + "column_break_7", "transaction_date", "valid_till", + "column_break1", "order_type", - "contact_section", - "customer_address", - "address_display", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "col_break98", - "shipping_address_name", - "shipping_address", - "company_address", - "company_address_display", - "customer_group", - "territory", + "company", + "amended_from", "currency_and_price_list", "currency", "conversion_rate", @@ -43,59 +30,79 @@ "price_list_currency", "plc_conversion_rate", "ignore_pricing_rule", - "section_break_33", - "scan_barcode", "items_section", + "scan_barcode", "items", - "bundle_items_section", - "packed_items", - "pricing_rule_details", - "pricing_rules", "sec_break23", "total_qty", + "total_net_weight", + "column_break_28", "base_total", "base_net_total", - "column_break_28", + "column_break_31", "total", "net_total", - "total_net_weight", "taxes_section", + "taxes_and_charges", + "column_break_36", "tax_category", "column_break_34", "shipping_rule", "section_break_36", - "taxes_and_charges", "taxes", - "sec_tax_breakup", - "other_charges_calculation", "section_break_39", "base_total_taxes_and_charges", "column_break_42", "total_taxes_and_charges", - "section_break_44", - "coupon_code", - "referral_sales_partner", - "apply_discount_on", - "base_discount_amount", - "column_break_46", - "additional_discount_percentage", - "discount_amount", "totals", "base_grand_total", "base_rounding_adjustment", - "base_in_words", "base_rounded_total", + "base_in_words", "column_break3", "grand_total", "rounding_adjustment", "rounded_total", "in_words", + "section_break_44", + "apply_discount_on", + "base_discount_amount", + "coupon_code", + "column_break_46", + "additional_discount_percentage", + "discount_amount", + "referral_sales_partner", + "sec_tax_breakup", + "other_charges_calculation", + "bundle_items_section", + "packed_items", + "pricing_rule_details", + "pricing_rules", + "address_and_contact_tab", + "billing_address_section", + "customer_address", + "address_display", + "col_break98", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "shipping_address_section", + "shipping_address_name", + "column_break_81", + "shipping_address", + "company_address_section", + "company_address", + "column_break_87", + "company_address_display", + "terms_tab", "payment_schedule_section", "payment_terms_template", "payment_schedule", "terms_section_break", "tc_name", "terms", + "more_info_tab", "print_settings", "letter_head", "group_same_items", @@ -105,17 +112,23 @@ "subscription_section", "auto_repeat", "update_auto_repeat_reference", - "more_info", + "additional_info_section", + "status", + "customer_group", + "territory", + "column_break_108", "campaign", "source", - "order_lost_reason", "column_break4", - "status", - "enq_det", - "supplier_quotation", "opportunity", + "supplier_quotation", + "enq_det", + "lost_reasons_section", "lost_reasons", - "competitors" + "competitors", + "column_break_117", + "order_lost_reason", + "connections_tab" ], "fields": [ { @@ -241,14 +254,6 @@ "print_hide": 1, "reqd": 1 }, - { - "collapsible": 1, - "depends_on": "party_name", - "fieldname": "contact_section", - "fieldtype": "Section Break", - "label": "Address and Contact", - "options": "fa fa-bullhorn" - }, { "fieldname": "customer_address", "fieldtype": "Link", @@ -408,6 +413,8 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, + "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -415,7 +422,6 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", - "label": "Items", "oldfieldname": "quotation_details", "oldfieldtype": "Table", "options": "Quotation Item", @@ -423,6 +429,7 @@ "width": "40px" }, { + "collapsible": 1, "fieldname": "pricing_rule_details", "fieldtype": "Section Break", "label": "Pricing Rules" @@ -492,6 +499,7 @@ { "fieldname": "taxes_section", "fieldtype": "Section Break", + "hide_border": 1, "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" @@ -517,7 +525,8 @@ }, { "fieldname": "section_break_36", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "taxes_and_charges", @@ -579,10 +588,9 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", - "label": "Additional Discount and Coupon Code" + "label": "Additional Discount" }, { "fieldname": "coupon_code", @@ -632,6 +640,7 @@ { "fieldname": "totals", "fieldtype": "Section Break", + "label": "Totals", "oldfieldtype": "Section Break", "options": "fa fa-money", "print_hide": 1 @@ -657,7 +666,6 @@ "read_only": 1 }, { - "description": "In Words will be visible once you save the Quotation.", "fieldname": "base_in_words", "fieldtype": "Data", "label": "In Words (Company Currency)", @@ -665,8 +673,7 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "width": "200px" + "read_only": 1 }, { "fieldname": "base_rounded_total", @@ -749,7 +756,6 @@ "print_hide": 1 }, { - "collapsible": 1, "collapsible_depends_on": "terms", "fieldname": "terms_section_break", "fieldtype": "Section Break", @@ -824,7 +830,7 @@ { "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Auto Repeat Section" + "label": "Auto Repeat" }, { "fieldname": "auto_repeat", @@ -842,15 +848,6 @@ "fieldtype": "Button", "label": "Update Auto Repeat Reference" }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "print_hide": 1 - }, { "fieldname": "campaign", "fieldtype": "Link", @@ -943,7 +940,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "packed_items", "depends_on": "packed_items", "fieldname": "bundle_items_section", "fieldtype": "Section Break", @@ -970,22 +966,99 @@ "label": "Company Address", "read_only": 1 }, - { - "fieldname": "section_break_33", - "fieldtype": "Section Break" - }, { "fieldname": "scan_barcode", "fieldtype": "Data", "label": "Scan Barcode", "options": "Barcode" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address and Contact" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_address_section", + "fieldtype": "Section Break", + "label": "Billing Address", + "options": "fa fa-bullhorn" + }, + { + "fieldname": "shipping_address_section", + "fieldtype": "Section Break", + "label": "Shipping Address" + }, + { + "fieldname": "column_break_81", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_address_section", + "fieldtype": "Section Break", + "label": "Company Address" + }, + { + "fieldname": "column_break_87", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "depends_on": "eval:(doc.lost_reasons.length>0 || doc.order_lost_reason)", + "fieldname": "lost_reasons_section", + "fieldtype": "Section Break", + "label": "Lost Reasons" + }, + { + "fieldname": "column_break_117", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "additional_info_section", + "fieldtype": "Section Break", + "label": "Additional Info", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text", + "print_hide": 1 + }, + { + "fieldname": "column_break_108", + "fieldtype": "Column Break" } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-09-16 17:44:43.221804", + "modified": "2022-09-27 15:14:27.693005", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", @@ -1083,4 +1156,4 @@ "states": [], "timeline_field": "party_name", "title_field": "title" -} +} \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 5013d51a8a03..c792c30249c1 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -847,7 +847,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_48", "fieldtype": "Section Break", "hide_days": 1, @@ -1629,7 +1628,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-27 12:38:57.007441", + "modified": "2022-09-27 14:51:09.128426", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 519301c304db..2ac1b1c40663 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -712,7 +712,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_49", "fieldtype": "Section Break", "label": "Additional Discount" @@ -1387,7 +1386,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-09-27 12:49:21.640004", + "modified": "2022-09-27 14:51:31.964085", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 5a8329add1cc8457108ed2ff7d2821bf07a4efa6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 27 Sep 2022 17:47:37 +0530 Subject: [PATCH 279/898] feat: Tab Break in Purchase Order (cherry picked from commit 10a25603ac7e318d3ce735ba0b031bde34cb4b21) --- .../purchase_order/purchase_order.json | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 7f0fc5797415..9d955d9a80f4 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -13,12 +13,12 @@ "naming_series", "supplier", "supplier_name", + "order_confirmation_no", + "order_confirmation_date", "get_items_from_open_material_requests", "column_break_7", "transaction_date", "schedule_date", - "order_confirmation_no", - "order_confirmation_date", "column_break1", "company", "apply_tds", @@ -95,26 +95,21 @@ "discount_amount", "sec_tax_breakup", "other_charges_calculation", - "contacts_and_addresses_tab", + "address_and_contact_tab", "section_addresses", "supplier_address", "address_display", + "col_break_address", "contact_person", "contact_display", "contact_mobile", "contact_email", - "col_break_address", + "company_shipping_address_section", "shipping_address", "shipping_address_display", + "company_billing_address_section", "billing_address", "billing_address_display", - "terms_tab", - "terms_section_break", - "tc_name", - "terms", - "payment_schedule_section", - "payment_terms_template", - "payment_schedule", "drop_ship", "customer", "customer_name", @@ -123,6 +118,13 @@ "customer_contact_display", "customer_contact_mobile", "customer_contact_email", + "terms_tab", + "payment_schedule_section", + "payment_terms_template", + "payment_schedule", + "terms_section_break", + "tc_name", + "terms", "more_info_tab", "tracking_section", "status", @@ -141,12 +143,12 @@ "column_break_97", "auto_repeat", "update_auto_repeat_reference", - "more_info", + "additional_info_section", + "is_internal_supplier", + "represents_company", "ref_sq", "column_break_74", "party_account_currency", - "is_internal_supplier", - "represents_company", "inter_company_order_reference", "is_old_subcontracting_flow", "dashboard" @@ -273,8 +275,9 @@ "read_only": 1 }, { + "depends_on": "eval:doc.customer", "fieldname": "drop_ship", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Drop Ship" }, { @@ -305,7 +308,6 @@ { "fieldname": "customer_contact_display", "fieldtype": "Small Text", - "hidden": 1, "label": "Customer Contact", "print_hide": 1 }, @@ -325,10 +327,9 @@ "print_hide": 1 }, { - "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Supplier Address" }, { "fieldname": "supplier_address", @@ -714,7 +715,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "apply_discount_on", "fieldname": "discount_section", "fieldtype": "Section Break", "label": "Additional Discount" @@ -857,7 +857,6 @@ "read_only": 1 }, { - "collapsible": 1, "fieldname": "payment_schedule_section", "fieldtype": "Section Break", "label": "Payment Terms" @@ -877,11 +876,10 @@ "print_hide": 1 }, { - "collapsible": 1, "collapsible_depends_on": "terms", "fieldname": "terms_section_break", "fieldtype": "Section Break", - "label": "Terms and Conditions", + "label": "Terms & Conditions", "oldfieldtype": "Section Break", "options": "fa fa-legal" }, @@ -901,13 +899,6 @@ "oldfieldname": "terms", "oldfieldtype": "Text Editor" }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break" - }, { "default": "Draft", "fieldname": "status", @@ -1029,7 +1020,7 @@ "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Subscription Section" + "label": "Auto Repeat" }, { "allow_on_submit": 1, @@ -1178,11 +1169,6 @@ "label": "Set From Warehouse", "options": "Warehouse" }, - { - "fieldname": "contacts_and_addresses_tab", - "fieldtype": "Tab Break", - "label": "Contacts & Addresses" - }, { "fieldname": "terms_tab", "fieldtype": "Tab Break", @@ -1210,13 +1196,35 @@ { "fieldname": "column_break_53", "fieldtype": "Column Break" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "company_shipping_address_section", + "fieldtype": "Section Break", + "label": "Company Shipping Address" + }, + { + "fieldname": "company_billing_address_section", + "fieldtype": "Section Break", + "label": "Company Billing Address" + }, + { + "collapsible": 1, + "fieldname": "additional_info_section", + "fieldtype": "Section Break", + "label": "Additional Info", + "oldfieldtype": "Section Break" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-16 17:45:04.954055", + "modified": "2022-09-27 17:28:35.652609", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", From 79151be8ce1442cc232ab6eb2a74e84659dd684d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 27 Sep 2022 17:58:43 +0530 Subject: [PATCH 280/898] feat: Tab Break in Material Request (cherry picked from commit 2172c5034a984d648c872629c7fb72e93bfd377d) --- .../material_request/material_request.json | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 35931307af83..413c373e0e40 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -11,34 +11,40 @@ "naming_series", "title", "material_request_type", - "transfer_status", "customer", - "status", + "company", "column_break_2", "transaction_date", "schedule_date", - "company", "amended_from", "warehouse_section", + "scan_barcode", + "column_break_13", "set_from_warehouse", "column_break5", "set_warehouse", "items_section", - "scan_barcode", "items", - "more_info", + "terms_tab", + "terms_section_break", + "tc_name", + "terms", + "more_info_tab", + "status_section", + "status", "per_ordered", "column_break2", + "transfer_status", "per_received", "printing_details", "letter_head", + "column_break_31", "select_print_heading", - "terms_section_break", - "tc_name", - "terms", "reference", "job_card", - "work_order" + "column_break_35", + "work_order", + "connections_tab" ], "fields": [ { @@ -147,14 +153,6 @@ "options": "Material Request Item", "reqd": 1 }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text" - }, { "default": "Today", "fieldname": "transaction_date", @@ -239,7 +237,6 @@ "collapsible_depends_on": "terms", "fieldname": "terms_section_break", "fieldtype": "Section Break", - "label": "Terms and Conditions", "oldfieldtype": "Section Break", "options": "fa fa-legal" }, @@ -277,10 +274,10 @@ { "fieldname": "warehouse_section", "fieldtype": "Section Break", - "label": "Warehouse" + "hide_border": 1, + "label": "Items" }, { - "description": "Sets 'Target Warehouse' in each row of the Items table.", "fieldname": "set_warehouse", "fieldtype": "Link", "in_list_view": 1, @@ -296,7 +293,6 @@ }, { "depends_on": "eval:doc.material_request_type == 'Material Transfer'", - "description": "Sets 'Source Warehouse' in each row of the Items table.", "fieldname": "set_from_warehouse", "fieldtype": "Link", "label": "Set Source Warehouse", @@ -317,13 +313,49 @@ "label": "Work Order", "options": "Work Order", "read_only": 1 + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "collapsible": 1, + "fieldname": "status_section", + "fieldtype": "Section Break", + "label": "Status", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_35", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, "links": [], - "modified": "2022-08-25 11:49:28.155048", + "modified": "2022-09-27 17:58:26.366469", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", From 22ad3ad6f39a2c717d44fea2d039bbe76a750aaf Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 28 Sep 2022 11:34:53 +0530 Subject: [PATCH 281/898] feat: Tab Break in Supplier Quotation (cherry picked from commit 3d9263bf866ed3836f29d6c81cab55486e8bd4f1) --- .../supplier_quotation.json | 118 ++++++++++-------- 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 8d1939a101be..fc853a4b82a9 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -13,19 +13,13 @@ "naming_series", "supplier", "supplier_name", - "column_break1", "company", + "column_break1", + "status", "transaction_date", "valid_till", "quotation_number", "amended_from", - "address_section", - "supplier_address", - "contact_person", - "address_display", - "contact_display", - "contact_mobile", - "contact_email", "currency_and_price_list", "currency", "conversion_rate", @@ -36,25 +30,23 @@ "ignore_pricing_rule", "items_section", "items", - "pricing_rule_details", - "pricing_rules", "section_break_22", "total_qty", + "total_net_weight", + "column_break_26", "base_total", "base_net_total", "column_break_24", "total", "net_total", - "total_net_weight", "taxes_section", + "taxes_and_charges", + "column_break_34", "tax_category", "column_break_36", "shipping_rule", "section_break_38", - "taxes_and_charges", "taxes", - "tax_breakup", - "other_charges_calculation", "totals", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", @@ -80,24 +72,36 @@ "rounded_total", "in_words", "disable_rounded_total", - "terms_section_break", + "tax_breakup", + "other_charges_calculation", + "pricing_rule_details", + "pricing_rules", + "address_and_contact_tab", + "supplier_address", + "address_display", + "column_break_72", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "terms_tab", "tc_name", "terms", + "more_info_tab", "printing_settings", "select_print_heading", "group_same_items", - "column_break_72", + "column_break_85", "letter_head", "language", "subscription_section", "auto_repeat", "update_auto_repeat_reference", "more_info", - "status", - "column_break_57", "is_subcontracted", - "reference", - "opportunity" + "column_break_57", + "opportunity", + "connections_tab" ], "fields": [ { @@ -146,7 +150,7 @@ "fieldname": "supplier_name", "fieldtype": "Data", "in_global_search": 1, - "label": "Name", + "label": "Supplier Name", "read_only": 1 }, { @@ -193,12 +197,6 @@ "reqd": 1, "search_index": 1 }, - { - "collapsible": 1, - "fieldname": "address_section", - "fieldtype": "Section Break", - "label": "Address and Contact" - }, { "fieldname": "supplier_address", "fieldtype": "Link", @@ -309,6 +307,8 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, + "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -316,7 +316,6 @@ "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", - "label": "Items", "oldfieldname": "po_details", "oldfieldtype": "Table", "options": "Supplier Quotation Item", @@ -394,6 +393,7 @@ { "fieldname": "taxes_section", "fieldtype": "Section Break", + "hide_border": 1, "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" @@ -417,7 +417,8 @@ }, { "fieldname": "section_break_38", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "taxes_and_charges", @@ -523,7 +524,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_41", "fieldtype": "Section Break", "label": "Additional Discount" @@ -563,7 +563,8 @@ }, { "fieldname": "section_break_46", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Totals" }, { "fieldname": "base_grand_total", @@ -654,19 +655,10 @@ "fieldtype": "Check", "label": "Disable Rounded Total" }, - { - "collapsible": 1, - "collapsible_depends_on": "terms", - "fieldname": "terms_section_break", - "fieldtype": "Section Break", - "label": "Terms and Conditions", - "oldfieldtype": "Section Break", - "options": "fa fa-legal" - }, { "fieldname": "tc_name", "fieldtype": "Link", - "label": "Terms", + "label": "Terms Template", "oldfieldname": "tc_name", "oldfieldtype": "Link", "options": "Terms and Conditions", @@ -729,7 +721,7 @@ { "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Auto Repeat Section" + "label": "Auto Repeat" }, { "fieldname": "auto_repeat", @@ -751,7 +743,7 @@ "collapsible": 1, "fieldname": "more_info", "fieldtype": "Section Break", - "label": "More Information", + "label": "Additional Info", "oldfieldtype": "Section Break", "options": "fa fa-file-text" }, @@ -779,11 +771,6 @@ "label": "Is Subcontracted", "print_hide": 1 }, - { - "fieldname": "reference", - "fieldtype": "Section Break", - "label": "Reference" - }, { "fieldname": "opportunity", "fieldtype": "Link", @@ -803,6 +790,39 @@ "fieldname": "quotation_number", "fieldtype": "Data", "label": "Quotation Number" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_85", + "fieldtype": "Column Break" } ], "icon": "fa fa-shopping-cart", @@ -810,7 +830,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-03-14 16:13:20.284572", + "modified": "2022-09-27 18:13:09.462037", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", From b092791ac803c7f229ea5c8202b04cd1e5ffe06d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 28 Sep 2022 14:07:26 +0530 Subject: [PATCH 282/898] feat: Tab Break in Purchase Receipt (cherry picked from commit 35f836c4b72eaca83b7120dade976fe40ba477ff) --- .../purchase_receipt/purchase_receipt.json | 227 ++++++++++++------ 1 file changed, 147 insertions(+), 80 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index acaac920c9e3..16b51d973a37 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -17,10 +17,11 @@ "supplier_name", "supplier_delivery_note", "column_break1", - "company", "posting_date", "posting_time", "set_posting_time", + "column_break_12", + "company", "apply_putaway_rule", "is_return", "return_against", @@ -28,18 +29,6 @@ "cost_center", "dimension_col_break", "project", - "section_addresses", - "supplier_address", - "contact_person", - "address_display", - "contact_display", - "contact_mobile", - "contact_email", - "col_break_address", - "shipping_address", - "shipping_address_display", - "billing_address", - "billing_address_display", "currency_and_price_list", "currency", "conversion_rate", @@ -49,37 +38,33 @@ "plc_conversion_rate", "ignore_pricing_rule", "sec_warehouse", + "scan_barcode", + "column_break_31", "set_warehouse", - "rejected_warehouse", - "col_break_warehouse", "set_from_warehouse", + "col_break_warehouse", + "rejected_warehouse", "is_subcontracted", "supplier_warehouse", "items_section", - "scan_barcode", "items", "section_break0", "total_qty", + "total_net_weight", + "column_break_43", "base_total", "base_net_total", "column_break_27", - "total_net_weight", "total", "net_total", - "pricing_rule_details", - "pricing_rules", - "raw_material_details", - "get_current_stock", - "supplied_items", "taxes_charges_section", - "tax_category", + "taxes_and_charges", "shipping_col", + "tax_category", + "column_break_53", "shipping_rule", "taxes_section", - "taxes_and_charges", "taxes", - "sec_tax_breakup", - "other_charges_calculation", "totals", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", @@ -88,53 +73,81 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", - "section_break_42", - "apply_discount_on", - "base_discount_amount", - "column_break_44", - "additional_discount_percentage", - "discount_amount", "section_break_46", "base_grand_total", "base_rounding_adjustment", - "base_in_words", "base_rounded_total", + "base_in_words", "column_break_50", "grand_total", "rounding_adjustment", "rounded_total", "in_words", "disable_rounded_total", - "terms_section_break", + "section_break_42", + "apply_discount_on", + "base_discount_amount", + "column_break_44", + "additional_discount_percentage", + "discount_amount", + "sec_tax_breakup", + "other_charges_calculation", + "pricing_rule_details", + "pricing_rules", + "raw_material_details", + "get_current_stock", + "supplied_items", + "address_and_contact_tab", + "section_addresses", + "supplier_address", + "address_display", + "col_break_address", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "section_break_98", + "shipping_address", + "column_break_100", + "shipping_address_display", + "billing_address_section", + "billing_address", + "column_break_104", + "billing_address_display", + "terms_tab", "tc_name", "terms", - "more_info", + "more_info_tab", + "status_section", "status", - "amended_from", - "range", "column_break4", "per_billed", "per_returned", - "is_internal_supplier", - "inter_company_reference", - "represents_company", "subscription_detail", "auto_repeat", "printing_settings", "letter_head", "language", - "instructions", "column_break_97", "select_print_heading", - "other_details", - "remarks", "group_same_items", "transporter_info", "transporter_name", "column_break5", "lr_no", "lr_date", - "is_old_subcontracting_flow" + "additional_info_section", + "instructions", + "is_internal_supplier", + "represents_company", + "inter_company_reference", + "column_break_131", + "remarks", + "range", + "amended_from", + "is_old_subcontracting_flow", + "other_details", + "connections_tab" ], "fields": [ { @@ -223,7 +236,6 @@ "width": "100px" }, { - "description": "Time at which materials were received", "fieldname": "posting_time", "fieldtype": "Time", "label": "Posting Time", @@ -277,15 +289,14 @@ "read_only": 1 }, { - "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Supplier Address" }, { "fieldname": "supplier_address", "fieldtype": "Link", - "label": "Select Supplier Address", + "label": "Supplier Address", "options": "Address", "print_hide": 1 }, @@ -330,7 +341,7 @@ { "fieldname": "shipping_address", "fieldtype": "Link", - "label": "Select Shipping Address", + "label": "Shipping Address Template", "options": "Address", "print_hide": 1 }, @@ -410,10 +421,11 @@ }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1, + "label": "Items" }, { - "description": "Sets 'Accepted Warehouse' in each row of the items table.", "fieldname": "set_warehouse", "fieldtype": "Link", "label": "Accepted Warehouse", @@ -421,7 +433,6 @@ "print_hide": 1 }, { - "description": "Sets 'Rejected Warehouse' in each row of the items table.", "fieldname": "rejected_warehouse", "fieldtype": "Link", "label": "Rejected Warehouse", @@ -461,6 +472,7 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -578,11 +590,11 @@ "read_only": 1 }, { - "description": "Add / Edit Taxes and Charges", "fieldname": "taxes_charges_section", "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "options": "fa fa-money" + "hide_border": 1, + "label": "Taxes and Charges", + "oldfieldtype": "Section Break" }, { "fieldname": "tax_category", @@ -603,7 +615,8 @@ }, { "fieldname": "taxes_section", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "taxes_and_charges", @@ -709,7 +722,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_42", "fieldtype": "Section Break", "label": "Additional Discount" @@ -749,7 +761,8 @@ }, { "fieldname": "section_break_46", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Totals" }, { "fieldname": "base_grand_total", @@ -841,15 +854,6 @@ "fieldtype": "Check", "label": "Disable Rounded Total" }, - { - "collapsible": 1, - "collapsible_depends_on": "terms", - "fieldname": "terms_section_break", - "fieldtype": "Section Break", - "label": "Terms and Conditions", - "oldfieldtype": "Section Break", - "options": "fa fa-legal" - }, { "fieldname": "tc_name", "fieldtype": "Link", @@ -866,14 +870,6 @@ "oldfieldname": "terms", "oldfieldtype": "Text Editor" }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text" - }, { "default": "Draft", "fieldname": "status", @@ -941,7 +937,7 @@ { "fieldname": "subscription_detail", "fieldtype": "Section Break", - "label": "Auto Repeat Detail" + "label": "Auto Repeat" }, { "fieldname": "auto_repeat", @@ -1025,7 +1021,7 @@ "collapsible_depends_on": "transporter_name", "fieldname": "transporter_info", "fieldtype": "Section Break", - "label": "Transporter Details", + "label": "Transporter", "options": "fa fa-truck" }, { @@ -1087,7 +1083,7 @@ { "fieldname": "billing_address", "fieldtype": "Link", - "label": "Select Billing Address", + "label": "Billing Address", "options": "Address" }, { @@ -1113,7 +1109,6 @@ }, { "depends_on": "eval: doc.is_internal_supplier", - "description": "Sets 'From Warehouse' in each row of the items table.", "fieldname": "set_from_warehouse", "fieldtype": "Link", "label": "Set From Warehouse", @@ -1151,13 +1146,85 @@ "hidden": 1, "label": "Is Old Subcontracting Flow", "read_only": 1 + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address & Contact" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_43", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_53", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_98", + "fieldtype": "Section Break", + "label": "Company Shipping Address" + }, + { + "fieldname": "billing_address_section", + "fieldtype": "Section Break", + "label": "Company Billing Address" + }, + { + "collapsible": 1, + "fieldname": "status_section", + "fieldtype": "Section Break", + "label": "Status", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text" + }, + { + "fieldname": "additional_info_section", + "fieldtype": "Section Break", + "label": "Additional Info" + }, + { + "fieldname": "column_break_131", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_100", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_104", + "fieldtype": "Column Break" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-09-16 17:45:58.430132", + "modified": "2022-09-28 13:07:37.482663", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 89314d4171e7ef736ab1c77c199aa17034f80eae Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 28 Sep 2022 15:44:58 +0530 Subject: [PATCH 283/898] feat: Tab Break in Purchase Invoice (cherry picked from commit aaf3c2b329984e6aa2c0ceacd48ac37b6da85310) --- .../purchase_invoice/purchase_invoice.json | 279 +++++++++++------- 1 file changed, 179 insertions(+), 100 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 1e477776e2d3..d994262dff68 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -12,39 +12,23 @@ "supplier", "supplier_name", "tax_id", - "due_date", - "tax_withholding_category", - "column_break1", "company", + "column_break_6", "posting_date", "posting_time", "set_posting_time", + "due_date", + "column_break1", "is_paid", "is_return", + "return_against", "apply_tds", + "tax_withholding_category", "amended_from", "accounting_dimensions_section", "cost_center", "dimension_col_break", "project", - "supplier_invoice_details", - "bill_no", - "column_break_15", - "bill_date", - "returns", - "return_against", - "section_addresses", - "supplier_address", - "address_display", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "col_break_address", - "shipping_address", - "shipping_address_display", - "billing_address", - "billing_address_display", "currency_and_price_list", "currency", "conversion_rate", @@ -54,39 +38,34 @@ "plc_conversion_rate", "ignore_pricing_rule", "sec_warehouse", + "scan_barcode", + "update_stock", + "column_break_38", "set_warehouse", - "rejected_warehouse", - "col_break_warehouse", "set_from_warehouse", - "supplier_warehouse", + "col_break_warehouse", + "rejected_warehouse", "is_subcontracted", + "supplier_warehouse", "items_section", - "update_stock", - "scan_barcode", "items", - "pricing_rule_details", - "pricing_rules", - "raw_materials_supplied", - "supplied_items", "section_break_26", "total_qty", + "total_net_weight", + "column_break_50", "base_total", "base_net_total", "column_break_28", - "total_net_weight", "total", "net_total", "taxes_section", + "taxes_and_charges", + "column_break_58", "tax_category", "column_break_49", "shipping_rule", "section_break_51", - "taxes_and_charges", "taxes", - "tax_withheld_vouchers_section", - "tax_withheld_vouchers", - "sec_tax_breakup", - "other_charges_calculation", "totals", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted", @@ -95,12 +74,6 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", - "section_break_44", - "apply_discount_on", - "base_discount_amount", - "column_break_46", - "additional_discount_percentage", - "discount_amount", "section_break_49", "base_grand_total", "base_rounding_adjustment", @@ -114,24 +87,58 @@ "total_advance", "outstanding_amount", "disable_rounded_total", + "section_break_44", + "apply_discount_on", + "base_discount_amount", + "additional_discount_account", + "column_break_46", + "additional_discount_percentage", + "discount_amount", + "tax_withheld_vouchers_section", + "tax_withheld_vouchers", + "sec_tax_breakup", + "other_charges_calculation", + "pricing_rule_details", + "pricing_rules", + "raw_materials_supplied", + "supplied_items", + "payments_tab", "payments_section", "mode_of_payment", - "cash_bank_account", + "base_paid_amount", "clearance_date", "col_br_payments", + "cash_bank_account", "paid_amount", - "base_paid_amount", + "advances_section", + "allocate_advances_automatically", + "get_advances", + "advances", + "advance_tax", "write_off", "write_off_amount", "base_write_off_amount", "column_break_61", "write_off_account", "write_off_cost_center", - "advances_section", - "allocate_advances_automatically", - "get_advances", - "advances", - "advance_tax", + "address_and_contact_tab", + "section_addresses", + "supplier_address", + "address_display", + "col_break_address", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "company_shipping_address_section", + "shipping_address", + "column_break_126", + "shipping_address_display", + "company_billing_address_section", + "billing_address", + "column_break_130", + "billing_address_display", + "terms_tab", "payment_schedule_section", "payment_terms_template", "ignore_default_payment_terms_template", @@ -139,6 +146,28 @@ "terms_section_break", "tc_name", "terms", + "more_info_tab", + "status_section", + "status", + "column_break_177", + "per_received", + "supplier_invoice_details", + "bill_no", + "column_break_15", + "bill_date", + "accounting_details_section", + "credit_to", + "party_account_currency", + "is_opening", + "against_expense_account", + "column_break_63", + "unrealized_profit_loss_account", + "subscription_section", + "auto_repeat", + "update_auto_repeat_reference", + "column_break_114", + "from_date", + "to_date", "printing_settings", "letter_head", "select_print_heading", @@ -150,28 +179,14 @@ "release_date", "cb_17", "hold_comment", - "more_info", - "status", - "inter_company_invoice_reference", + "additional_info_section", + "is_internal_supplier", "represents_company", "column_break_147", - "is_internal_supplier", - "accounting_details_section", - "credit_to", - "party_account_currency", - "is_opening", - "against_expense_account", - "column_break_63", - "unrealized_profit_loss_account", + "inter_company_invoice_reference", + "is_old_subcontracting_flow", "remarks", - "subscription_section", - "from_date", - "to_date", - "column_break_114", - "auto_repeat", - "update_auto_repeat_reference", - "per_received", - "is_old_subcontracting_flow" + "connections_tab" ], "fields": [ { @@ -354,7 +369,7 @@ "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details" + "label": "Supplier Invoice" }, { "fieldname": "bill_no", @@ -377,12 +392,6 @@ "oldfieldtype": "Date", "print_hide": 1 }, - { - "depends_on": "return_against", - "fieldname": "returns", - "fieldtype": "Section Break", - "label": "Returns" - }, { "depends_on": "return_against", "fieldname": "return_against", @@ -394,10 +403,9 @@ "read_only": 1 }, { - "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Supplier Address" }, { "fieldname": "supplier_address", @@ -518,11 +526,12 @@ }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1, + "label": "Items" }, { "depends_on": "update_stock", - "description": "Sets 'Accepted Warehouse' in each row of the items table.", "fieldname": "set_warehouse", "fieldtype": "Link", "label": "Set Accepted Warehouse", @@ -531,7 +540,6 @@ }, { "depends_on": "update_stock", - "description": "Warehouse where you are maintaining stock of rejected items", "fieldname": "rejected_warehouse", "fieldtype": "Link", "label": "Rejected Warehouse", @@ -554,6 +562,7 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -581,6 +590,7 @@ "reqd": 1 }, { + "collapsible": 1, "fieldname": "pricing_rule_details", "fieldtype": "Section Break", "label": "Pricing Rules" @@ -593,6 +603,7 @@ "read_only": 1 }, { + "collapsible": 1, "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", @@ -665,6 +676,8 @@ { "fieldname": "taxes_section", "fieldtype": "Section Break", + "hide_border": 1, + "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" }, @@ -688,7 +701,8 @@ }, { "fieldname": "section_break_51", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "taxes_and_charges", @@ -832,7 +846,8 @@ }, { "fieldname": "section_break_49", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Totals" }, { "fieldname": "base_grand_total", @@ -1003,8 +1018,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "write_off_amount", - "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", "label": "Write Off" @@ -1081,7 +1094,6 @@ "print_hide": 1 }, { - "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", @@ -1102,8 +1114,6 @@ "print_hide": 1 }, { - "collapsible": 1, - "collapsible_depends_on": "terms", "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", @@ -1119,13 +1129,13 @@ { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1" + "label": "Terms and Conditions" }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings" + "label": "Print Settings" }, { "allow_on_submit": 1, @@ -1166,15 +1176,6 @@ "print_hide": 1, "read_only": 1 }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "print_hide": 1 - }, { "default": "0", "fetch_from": "supplier.is_internal_supplier", @@ -1260,7 +1261,7 @@ "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", - "label": "Subscription Section", + "label": "Subscription", "print_hide": 1 }, { @@ -1356,7 +1357,6 @@ }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", - "description": "Sets 'From Warehouse' in each row of the items table.", "fieldname": "set_from_warehouse", "fieldtype": "Link", "label": "Set From Warehouse", @@ -1422,6 +1422,7 @@ "read_only": 1 }, { + "collapsible_depends_on": "tax_withheld_vouchers", "fieldname": "tax_withheld_vouchers_section", "fieldtype": "Section Break", "label": "Tax Withheld Vouchers" @@ -1433,6 +1434,84 @@ "no_copy": 1, "options": "Tax Withheld Vouchers", "read_only": 1 + }, + { + "fieldname": "payments_tab", + "fieldtype": "Tab Break", + "label": "Payments" + }, + { + "fieldname": "address_and_contact_tab", + "fieldtype": "Tab Break", + "label": "Address and Contact" + }, + { + "fieldname": "terms_tab", + "fieldtype": "Tab Break", + "label": "Terms" + }, + { + "fieldname": "more_info_tab", + "fieldtype": "Tab Break", + "label": "More Info" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_50", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_58", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_shipping_address_section", + "fieldtype": "Section Break", + "label": "Company Shipping Address" + }, + { + "fieldname": "column_break_126", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_billing_address_section", + "fieldtype": "Section Break", + "label": "Company Billing Address" + }, + { + "fieldname": "column_break_130", + "fieldtype": "Column Break" + }, + { + "fieldname": "status_section", + "fieldtype": "Section Break", + "label": "Status" + }, + { + "fieldname": "column_break_177", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "additional_info_section", + "fieldtype": "Section Break", + "label": "Additional Info", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text", + "print_hide": 1 } ], "icon": "fa fa-file-text", From db4fbc9e58e193439024ca309210b0f2fbdf1d71 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 28 Sep 2022 18:56:29 +0530 Subject: [PATCH 284/898] fix: more fields reordering related to Tab Break (cherry picked from commit 5389a357986a323714f718b242a93729d6eebe9a) --- .../purchase_invoice/purchase_invoice.json | 7 ++++--- .../doctype/sales_invoice/sales_invoice.json | 17 +++++++--------- .../purchase_order/purchase_order.json | 16 ++++++++++++--- .../supplier_quotation.json | 6 +++--- .../regional/united_arab_emirates/setup.py | 2 +- .../selling/doctype/quotation/quotation.json | 20 +++++++++---------- .../doctype/sales_order/sales_order.json | 4 ++-- .../doctype/delivery_note/delivery_note.json | 8 ++++---- .../purchase_receipt/purchase_receipt.json | 6 +++--- 9 files changed, 47 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d994262dff68..69e2f96000c2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -170,9 +170,9 @@ "to_date", "printing_settings", "letter_head", - "select_print_heading", - "column_break_112", "group_same_items", + "column_break_112", + "select_print_heading", "language", "sb_14", "on_hold", @@ -1443,7 +1443,7 @@ { "fieldname": "address_and_contact_tab", "fieldtype": "Tab Break", - "label": "Address and Contact" + "label": "Address & Contact" }, { "fieldname": "terms_tab", @@ -1496,6 +1496,7 @@ "fieldtype": "Column Break" }, { + "collapsible": 1, "fieldname": "status_section", "fieldtype": "Section Break", "label": "Status" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index d8b413d9ff34..4353c6f41e73 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -12,14 +12,14 @@ "customer", "customer_name", "tax_id", + "company", + "company_tax_id", "column_break1", "posting_date", "posting_time", "set_posting_time", "due_date", "column_break_14", - "company", - "company_tax_id", "is_pos", "pos_profile", "is_consolidated", @@ -87,8 +87,8 @@ "disable_rounded_total", "section_break_49", "apply_discount_on", - "is_cash_or_non_trade_discount", "base_discount_amount", + "is_cash_or_non_trade_discount", "additional_discount_account", "column_break_51", "additional_discount_percentage", @@ -1055,7 +1055,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_49", "fieldtype": "Section Break", "hide_days": 1, @@ -1111,6 +1110,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, + "label": "Totals", "oldfieldtype": "Section Break", "options": "fa fa-money", "print_hide": 1 @@ -1292,8 +1292,6 @@ "print_hide": 1 }, { - "collapsible": 1, - "collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", "hide_days": 1, @@ -1325,7 +1323,7 @@ { "collapsible": 1, "collapsible_depends_on": "eval:!doc.is_pos", - "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)", + "depends_on": "eval:doc.is_pos===1", "fieldname": "payments_section", "fieldtype": "Section Break", "hide_days": 1, @@ -1363,6 +1361,7 @@ "hide_seconds": 1 }, { + "depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points", "fieldname": "base_paid_amount", "fieldtype": "Currency", "hide_days": 1, @@ -1498,8 +1497,6 @@ "print_hide": 1 }, { - "collapsible": 1, - "collapsible_depends_on": "terms", "fieldname": "terms_section_break", "fieldtype": "Section Break", "hide_days": 1, @@ -2111,7 +2108,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-09-27 14:15:35.293825", + "modified": "2022-09-28 19:23:38.265409", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 9d955d9a80f4..a2f96375f5fc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -106,9 +106,11 @@ "contact_email", "company_shipping_address_section", "shipping_address", + "column_break_99", "shipping_address_display", "company_billing_address_section", "billing_address", + "column_break_103", "billing_address_display", "drop_ship", "customer", @@ -133,10 +135,10 @@ "per_received", "column_break5", "letter_head", - "select_print_heading", + "group_same_items", "column_break_86", + "select_print_heading", "language", - "group_same_items", "subscription_section", "from_date", "to_date", @@ -1218,13 +1220,21 @@ "fieldtype": "Section Break", "label": "Additional Info", "oldfieldtype": "Section Break" + }, + { + "fieldname": "column_break_99", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_103", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-27 17:28:35.652609", + "modified": "2022-09-28 18:20:49.494279", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index fc853a4b82a9..16365618ca98 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -89,10 +89,10 @@ "terms", "more_info_tab", "printing_settings", - "select_print_heading", + "letter_head", "group_same_items", "column_break_85", - "letter_head", + "select_print_heading", "language", "subscription_section", "auto_repeat", @@ -830,7 +830,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-09-27 18:13:09.462037", + "modified": "2022-09-27 18:20:09.462037", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index be621bcdd1d8..36a079546e56 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -37,7 +37,7 @@ def make_custom_fields(): fieldname="vat_section", label="VAT Details", fieldtype="Section Break", - insert_after="group_same_items", + insert_after="language", print_hide=1, collapsible=1, ), diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 78b739cedb8c..f858e26fcb3b 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -103,15 +103,20 @@ "tc_name", "terms", "more_info_tab", + "subscription_section", + "auto_repeat", + "update_auto_repeat_reference", "print_settings", "letter_head", "group_same_items", "column_break_73", "select_print_heading", "language", - "subscription_section", - "auto_repeat", - "update_auto_repeat_reference", + "lost_reasons_section", + "lost_reasons", + "competitors", + "column_break_117", + "order_lost_reason", "additional_info_section", "status", "customer_group", @@ -123,11 +128,6 @@ "opportunity", "supplier_quotation", "enq_det", - "lost_reasons_section", - "lost_reasons", - "competitors", - "column_break_117", - "order_lost_reason", "connections_tab" ], "fields": [ @@ -975,7 +975,7 @@ { "fieldname": "address_and_contact_tab", "fieldtype": "Tab Break", - "label": "Address and Contact" + "label": "Address & Contact" }, { "fieldname": "terms_tab", @@ -1058,7 +1058,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-09-27 15:14:27.693005", + "modified": "2022-09-27 15:18:27.693005", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index c792c30249c1..1f3af0cc0f40 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -151,11 +151,11 @@ "auto_repeat", "update_auto_repeat_reference", "printing_details", - "language", "letter_head", "group_same_items", "column_break4", "select_print_heading", + "language", "additional_info_section", "is_internal_customer", "represents_company", @@ -1628,7 +1628,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-27 14:51:09.128426", + "modified": "2022-09-28 18:30:34.723896", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 2ac1b1c40663..541f96b19224 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -149,11 +149,11 @@ "auto_repeat", "printing_details", "letter_head", - "select_print_heading", - "column_break_88", - "language", "print_without_amount", "group_same_items", + "column_break_88", + "select_print_heading", + "language", "more_info", "is_internal_customer", "represents_company", @@ -1386,7 +1386,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-09-27 14:51:31.964085", + "modified": "2022-09-27 14:55:31.964085", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 16b51d973a37..426b509b9ef5 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -127,10 +127,10 @@ "auto_repeat", "printing_settings", "letter_head", - "language", + "group_same_items", "column_break_97", "select_print_heading", - "group_same_items", + "language", "transporter_info", "transporter_name", "column_break5", @@ -1224,7 +1224,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-09-28 13:07:37.482663", + "modified": "2022-09-28 13:09:37.482663", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 236a5f37c758652092336888451c788bfddfd75b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 11 Oct 2022 13:17:13 +0530 Subject: [PATCH 285/898] fix: minor cleanup (cherry picked from commit e71c417f7e5dd6015b58c770b8c3d1dcca7d70bf) --- .../doctype/purchase_invoice/purchase_invoice.json | 14 +++++++------- .../doctype/sales_invoice/sales_invoice.json | 3 ++- .../doctype/purchase_order/purchase_order.json | 6 ++---- erpnext/selling/doctype/quotation/quotation.json | 14 +++++++------- .../selling/doctype/sales_order/sales_order.json | 5 +++-- .../stock/doctype/delivery_note/delivery_note.json | 7 ++++--- .../doctype/purchase_receipt/purchase_receipt.json | 4 ++-- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 69e2f96000c2..e73d60233286 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -39,13 +39,12 @@ "ignore_pricing_rule", "sec_warehouse", "scan_barcode", + "col_break_warehouse", "update_stock", - "column_break_38", "set_warehouse", "set_from_warehouse", - "col_break_warehouse", - "rejected_warehouse", "is_subcontracted", + "rejected_warehouse", "supplier_warehouse", "items_section", "items", @@ -186,7 +185,8 @@ "inter_company_invoice_reference", "is_old_subcontracting_flow", "remarks", - "connections_tab" + "connections_tab", + "column_break_38" ], "fields": [ { @@ -667,6 +667,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", @@ -806,7 +807,6 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", "label": "Additional Discount" @@ -1340,7 +1340,7 @@ }, { "depends_on": "eval:doc.is_internal_supplier", - "description": "Unrealized Profit / Loss account for intra-company transfers", + "description": "Unrealized Profit/Loss account for intra-company transfers", "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", @@ -1519,7 +1519,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-10-07 14:19:14.214157", + "modified": "2022-10-11 13:04:44.304389", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 4353c6f41e73..97e5f4017e04 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -852,6 +852,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "hide_days": 1, @@ -2108,7 +2109,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-09-28 19:23:38.265409", + "modified": "2022-10-11 13:07:36.488095", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index a2f96375f5fc..2193985ff23f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -454,7 +454,6 @@ "print_hide": 1 }, { - "description": "Sets 'Warehouse' in each row of the Items table.", "fieldname": "set_warehouse", "fieldtype": "Link", "label": "Set Target Warehouse", @@ -578,6 +577,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", @@ -707,7 +707,6 @@ "read_only": 1 }, { - "depends_on": "total_taxes_and_charges", "fieldname": "total_taxes_and_charges", "fieldtype": "Currency", "label": "Total Taxes and Charges", @@ -781,7 +780,6 @@ "read_only": 1 }, { - "description": "In Words will be visible once you save the Purchase Order.", "fieldname": "base_in_words", "fieldtype": "Data", "label": "In Words (Company Currency)", @@ -1234,7 +1232,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-28 18:20:49.494279", + "modified": "2022-10-11 13:01:41.674352", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index f858e26fcb3b..fa64b1625bb1 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -301,7 +301,7 @@ "read_only": 1 }, { - "depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name", + "depends_on": "eval:(doc.quotation_to=='Customer' && doc.party_name)", "fieldname": "col_break98", "fieldtype": "Column Break", "width": "50%" @@ -321,7 +321,7 @@ "read_only": 1 }, { - "depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name", + "depends_on": "eval:(doc.quotation_to=='Customer' && doc.party_name)", "fieldname": "customer_group", "fieldtype": "Link", "hidden": 1, @@ -490,6 +490,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", @@ -843,7 +844,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval: doc.auto_repeat", + "depends_on": "eval:doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", "label": "Update Auto Repeat Reference" @@ -868,7 +869,7 @@ }, { "allow_on_submit": 1, - "depends_on": "eval:doc.status===\"Lost\"", + "depends_on": "eval:doc.status=='Lost'", "fieldname": "order_lost_reason", "fieldtype": "Small Text", "label": "Detailed Reason", @@ -931,7 +932,6 @@ "read_only": 1 }, { - "depends_on": "packed_items", "fieldname": "packed_items", "fieldtype": "Table", "label": "Bundle Items", @@ -1031,7 +1031,7 @@ }, { "collapsible": 1, - "depends_on": "eval:(doc.lost_reasons.length>0 || doc.order_lost_reason)", + "depends_on": "eval:(doc.lost_reasons || doc.order_lost_reason)", "fieldname": "lost_reasons_section", "fieldtype": "Section Break", "label": "Lost Reasons" @@ -1058,7 +1058,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-09-27 15:18:27.693005", + "modified": "2022-10-11 13:06:33.479650", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 1f3af0cc0f40..e6ff39d8d4a5 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -82,9 +82,9 @@ "advance_paid", "disable_rounded_total", "section_break_48", - "coupon_code", "apply_discount_on", "base_discount_amount", + "coupon_code", "column_break_50", "additional_discount_percentage", "discount_amount", @@ -693,6 +693,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "hide_days": 1, @@ -1628,7 +1629,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-09-28 18:30:34.723896", + "modified": "2022-10-11 13:06:10.469796", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 541f96b19224..0ca3e69f76ca 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -41,7 +41,6 @@ "ignore_pricing_rule", "items_section", "scan_barcode", - "column_break_25", "pick_list", "col_break_warehouse", "set_warehouse", @@ -163,7 +162,8 @@ "column_break5", "excise_page", "instructions", - "connections_tab" + "connections_tab", + "column_break_25" ], "fields": [ { @@ -613,6 +613,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", @@ -1386,7 +1387,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-09-27 14:55:31.964085", + "modified": "2022-10-11 13:06:58.655635", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 426b509b9ef5..3141212ee5dd 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -583,6 +583,7 @@ "read_only": 1 }, { + "depends_on": "total_net_weight", "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", @@ -920,7 +921,6 @@ "width": "50%" }, { - "description": "Track this Purchase Receipt against any Project", "fieldname": "project", "fieldtype": "Link", "label": "Project", @@ -1224,7 +1224,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-09-28 13:09:37.482663", + "modified": "2022-10-11 13:02:31.776256", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 03db8b2f5f97f226439d00732ad953c26d47d1a2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 12:14:48 +0530 Subject: [PATCH 286/898] ci: disable orchestrator (backport #32571) (#32572) * ci: disable orchestrator (#32571) (cherry picked from commit 6ce3ce758c7a62435e3dcdfb29606848e5959cb6) * chore: correct build count * chore: no cov Co-authored-by: Ankush Menat --- .github/workflows/server-tests-mariadb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index c4ae61091ff1..ba6a28356924 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -120,7 +120,7 @@ jobs: FRAPPE_BRANCH: ${{ github.event.inputs.branch }} - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage + run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 3 --build-number ${{ matrix.container }}' env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} From 154904e9609a572f1c6d9f0cbffec220881dbb33 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 12:59:06 +0530 Subject: [PATCH 287/898] fix: Explicitly update modified (backport #32519) (#32575) fix: Explicitly update modified (#32519) * fix: Explicitly update modified required after https://github.com/frappe/frappe/pull/18301 * chore: fix broken translations (cherry picked from commit 8376fbc9826e3fa7dc9a28365d96e92a2f6f52d9) Co-authored-by: Ankush Menat --- erpnext/stock/doctype/bin/bin.py | 11 +++++++---- erpnext/stock/stock_ledger.py | 2 +- erpnext/translations/fr.csv | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 548df318fac7..c28f45aed416 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -37,8 +37,10 @@ def update_reserved_qty_for_production(self): self.set_projected_qty() - self.db_set("reserved_qty_for_production", flt(self.reserved_qty_for_production)) - self.db_set("projected_qty", self.projected_qty) + self.db_set( + "reserved_qty_for_production", flt(self.reserved_qty_for_production), update_modified=True + ) + self.db_set("projected_qty", self.projected_qty, update_modified=True) def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"): # reserved qty @@ -118,9 +120,9 @@ def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontra else: reserved_qty_for_sub_contract = 0 - self.db_set("reserved_qty_for_sub_contract", reserved_qty_for_sub_contract) + self.db_set("reserved_qty_for_sub_contract", reserved_qty_for_sub_contract, update_modified=True) self.set_projected_qty() - self.db_set("projected_qty", self.projected_qty) + self.db_set("projected_qty", self.projected_qty, update_modified=True) def on_doctype_update(): @@ -193,4 +195,5 @@ def update_qty(bin_name, args): "planned_qty": planned_qty, "projected_qty": projected_qty, }, + update_modified=True, ) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 50309647de02..9ca40c3675fb 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1053,7 +1053,7 @@ def update_bin(self): updated_values = {"actual_qty": data.qty_after_transaction, "stock_value": data.stock_value} if data.valuation_rate is not None: updated_values["valuation_rate"] = data.valuation_rate - frappe.db.set_value("Bin", bin_name, updated_values) + frappe.db.set_value("Bin", bin_name, updated_values, update_modified=True) def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 7989bf7ebce6..3ba5ade62993 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -4946,7 +4946,7 @@ Min Amt,Montant Min, Max Amt,Montant Max, "If rate is zero them item will be treated as ""Free Item""",Si le prix est à 0 alors l'article sera traité comme article gratuit Is Recursive,Est récursif -"Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",La remise sera appliquée séquentiellement telque : acheter 1 => recupérer 1, acheter 2 => recupérer 2, acheter 3 => recupérer 3, etc... +"Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on","La remise sera appliquée séquentiellement telque : acheter 1 => recupérer 1, acheter 2 => recupérer 2, acheter 3 => recupérer 3, etc..." Period Settings,Paramètres de période, Margin,Marge, Margin Type,Type de Marge, From 948b231e92755b725785865ec2e0ae385c5efb7f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 11 Oct 2022 15:11:39 +0530 Subject: [PATCH 288/898] fix: consider sales rate as incoming rate for transit warehouse in purchase flow (cherry picked from commit 683a47f7a1676aced82a2abab086510228fcc7b4) --- .../purchase_invoice/purchase_invoice.py | 6 +- .../sales_invoice/test_sales_invoice.py | 1 + .../purchase_receipt/test_purchase_receipt.py | 234 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 62 +++-- 4 files changed, 278 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2b633cb8c34b..5dbe7ebc8633 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -705,6 +705,10 @@ def make_item_gl_entries(self, gl_entries): ) ) + credit_amount = item.base_net_amount + if self.is_internal_supplier and item.valuation_rate: + credit_amount = flt(item.valuation_rate * item.stock_qty) + # Intentionally passed negative debit amount to avoid incorrect GL Entry validation gl_entries.append( self.get_gl_dict( @@ -714,7 +718,7 @@ def make_item_gl_entries(self, gl_entries): "cost_center": item.cost_center, "project": item.project or self.project, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), + "debit": -1 * flt(credit_amount, item.precision("base_net_amount")), }, warehouse_account[item.from_warehouse]["account_currency"], item=item, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ce44ae304b3b..b548ea3c0083 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3306,6 +3306,7 @@ def create_sales_invoice(**args): "item_name": args.item_name or "_Test Item", "description": args.description or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", + "target_warehouse": args.target_warehouse or "_Test Warehouse 1 - _TC", "qty": args.qty or 1, "uom": args.uom or "Nos", "stock_uom": args.uom or "Nos", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 62697244babc..1f807b8249b4 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2,6 +2,9 @@ # License: GNU General Public License v3. See license.txt +from cmath import pi +from turtle import update + import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, today @@ -14,6 +17,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse +from erpnext.stock.get_item_details import update_stock from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction @@ -1199,6 +1203,115 @@ def test_backdated_transaction_for_internal_transfer(self): self.assertEqual(pr1.items[0].rate, 100) pr1.submit() + self.assertEqual(pr1.is_internal_supplier, 1) + + # Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1 + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -2), + warehouse=from_warehouse, + qty=1, + rate=200, + ) + + dn_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(dn_value), 200.00) + + pr_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(pr_value), 200.00) + pr1.load_from_db() + + self.assertEqual(pr1.items[0].valuation_rate, 200) + self.assertEqual(pr1.items[0].rate, 100) + + Gl = frappe.qb.DocType("GL Entry") + + query = ( + frappe.qb.from_(Gl) + .select( + (fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"), + ) + .where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name)) + ).run(as_dict=True) + + self.assertEqual(query[0].value, 0) + + def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_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" + + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + item_doc = create_item("Test Internal Transfer Item") + + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -1), + warehouse=from_warehouse, + qty=1, + rate=100, + ) + + # Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction + for i in range(1, 4): + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -1 * i), + warehouse=target_warehouse, + qty=1, + rate=320 * i, + ) + + dn1 = create_delivery_note( + item_code=item_doc.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=1, + rate=500, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + self.assertEqual(dn1.items[0].rate, 100) + + pr1 = make_inter_company_purchase_receipt(dn1.name) + pr1.items[0].warehouse = to_warehouse + self.assertEqual(pr1.items[0].rate, 100) + pr1.submit() + + stk_ledger = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": target_warehouse}, + ["stock_value_difference", "outgoing_rate"], + as_dict=True, + ) + + self.assertEqual(abs(stk_ledger.stock_value_difference), 100) + self.assertEqual(stk_ledger.outgoing_rate, 100) + # Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1 make_purchase_receipt( item_code=item_doc.name, @@ -1241,6 +1354,127 @@ def test_backdated_transaction_for_internal_transfer(self): self.assertEqual(query[0].value, 0) + def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_invoice( + self, + ): + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( + make_purchase_invoice as make_purchase_invoice_for_si, + ) + from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + make_inter_company_purchase_invoice, + ) + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + prepare_data_for_internal_transfer() + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + item_doc = create_item("Test Internal Transfer Item") + + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + + make_purchase_invoice_for_si( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -1), + warehouse=from_warehouse, + qty=1, + update_stock=1, + expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", + rate=100, + ) + + # Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction + for i in range(1, 4): + make_purchase_invoice_for_si( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -1 * i), + warehouse=target_warehouse, + update_stock=1, + qty=1, + expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", + rate=320 * i, + ) + + si1 = create_sales_invoice( + item_code=item_doc.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + income_account="Sales - TCP1", + qty=1, + rate=500, + update_stock=1, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + self.assertEqual(si1.items[0].rate, 100) + + pi1 = make_inter_company_purchase_invoice(si1.name) + pi1.items[0].warehouse = to_warehouse + self.assertEqual(pi1.items[0].rate, 100) + pi1.update_stock = 1 + pi1.save() + pi1.submit() + + stk_ledger = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": target_warehouse}, + ["stock_value_difference", "outgoing_rate"], + as_dict=True, + ) + + self.assertEqual(abs(stk_ledger.stock_value_difference), 100) + self.assertEqual(stk_ledger.outgoing_rate, 100) + + # Backdated purchase receipt entry, the valuation rate should be updated for si1 and pi1 + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -2), + warehouse=from_warehouse, + qty=1, + rate=200, + ) + + si_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": si1.doctype, "voucher_no": si1.name, "warehouse": target_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(si_value), 200.00) + + pi_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": to_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(pi_value), 200.00) + pi1.load_from_db() + + self.assertEqual(pi1.items[0].valuation_rate, 200) + self.assertEqual(pi1.items[0].rate, 100) + + Gl = frappe.qb.DocType("GL Entry") + + query = ( + frappe.qb.from_(Gl) + .select( + (fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"), + ) + .where((Gl.voucher_type == pi1.doctype) & (Gl.voucher_no == pi1.name)) + ).run(as_dict=True) + + self.assertEqual(query[0].value, 0) + def test_batch_expiry_for_purchase_receipt(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9ca40c3675fb..7ced1da0085b 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -589,6 +589,15 @@ def process_sle(self, sle): sle.stock_queue = json.dumps(self.wh_data.stock_queue) sle.stock_value_difference = stock_value_difference sle.doctype = "Stock Ledger Entry" + + if ( + sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] + and sle.voucher_detail_no + and sle.actual_qty < 0 + and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier") + ): + sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle) + frappe.get_doc(sle).db_update() if not self.args.get("sle_id"): @@ -652,22 +661,7 @@ def get_incoming_outgoing_rate_from_transaction(self, sle): and sle.voucher_detail_no and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier") ): - field = ( - "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item" - ) - doctype = ( - "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item" - ) - refernce_name = frappe.get_cached_value( - sle.voucher_type + " Item", sle.voucher_detail_no, field - ) - - if refernce_name: - rate = frappe.get_cached_value( - doctype, - refernce_name, - "incoming_rate", - ) + rate = get_incoming_rate_for_inter_company_transfer(sle) else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" @@ -748,14 +742,12 @@ def update_rate_on_delivery_and_sales_return(self, sle, outgoing_rate): def update_rate_on_purchase_receipt(self, sle, outgoing_rate): if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): - frappe.db.set_value( - sle.voucher_type + " Item", - sle.voucher_detail_no, - { - "base_net_rate": outgoing_rate, - "valuation_rate": outgoing_rate, - }, - ) + if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and frappe.get_cached_value( + sle.voucher_type, sle.voucher_no, "is_internal_supplier" + ): + frappe.db.set_value( + f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", sle.outgoing_rate + ) else: frappe.db.set_value( "Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate @@ -1546,3 +1538,25 @@ def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool: if item_code and cint(frappe.db.get_value("Item", item_code, "allow_negative_stock", cache=True)): return True return False + + +def get_incoming_rate_for_inter_company_transfer(sle) -> float: + """ + For inter company transfer, incoming rate is the average of the outgoing rate + """ + rate = 0.0 + + field = "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item" + + doctype = "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item" + + reference_name = frappe.get_cached_value(sle.voucher_type + " Item", sle.voucher_detail_no, field) + + if reference_name: + rate = frappe.get_cached_value( + doctype, + reference_name, + "incoming_rate", + ) + + return rate From 423c0190e29a99af1aef223d3f4d8d7e6dbfcf0c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Oct 2022 15:09:50 +0530 Subject: [PATCH 289/898] fix: consider outgoingrate while valuation rate calculate (cherry picked from commit 3266e54e33b0ac65cc8e1b51cf4d65a75c12040f) --- erpnext/stock/stock_ledger.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 7ced1da0085b..cdf6e89fcbb9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -542,6 +542,14 @@ def process_sle(self, sle): if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) + if ( + sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] + and sle.voucher_detail_no + and sle.actual_qty < 0 + and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier") + ): + sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle) + if get_serial_nos(sle.serial_no): self.get_serialized_values(sle) self.wh_data.qty_after_transaction += flt(sle.actual_qty) @@ -590,14 +598,6 @@ def process_sle(self, sle): sle.stock_value_difference = stock_value_difference sle.doctype = "Stock Ledger Entry" - if ( - sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] - and sle.voucher_detail_no - and sle.actual_qty < 0 - and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier") - ): - sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle) - frappe.get_doc(sle).db_update() if not self.args.get("sle_id"): From 0b26131b65e7b68b5ebc7fcde164f2937958c0e7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 Oct 2022 15:45:36 +0530 Subject: [PATCH 290/898] fix: test case (cherry picked from commit 98bf8e13043a5efcf4b92a02d9c0c1e6ae67b3e8) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b548ea3c0083..52e221ca9d5d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3306,7 +3306,7 @@ def create_sales_invoice(**args): "item_name": args.item_name or "_Test Item", "description": args.description or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", - "target_warehouse": args.target_warehouse or "_Test Warehouse 1 - _TC", + "target_warehouse": args.target_warehouse, "qty": args.qty or 1, "uom": args.uom or "Nos", "stock_uom": args.uom or "Nos", From b653f43b70f76e3620948fd483a6f3f7eb201dac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Sep 2022 15:06:06 +0530 Subject: [PATCH 291/898] fix: Incoming rate precision fixes for intra company transfer (cherry picked from commit 083309c056512f86ece9b3276338108d1e5b98bf) --- 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 5e9c069b1d5b..e8e9076975e5 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -442,11 +442,17 @@ def set_incoming_rate(self): # For internal transfers use incoming rate as the valuation rate if self.is_internal_transfer(): if d.doctype == "Packed Item": - incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate")) + incoming_rate = flt( + flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, + d.precision("incoming_rate"), + ) if d.incoming_rate != incoming_rate: d.incoming_rate = incoming_rate else: - rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate")) + rate = flt( + flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, + d.precision("rate"), + ) if d.rate != rate: d.rate = rate frappe.msgprint( From 8eda2dbdf8c26798af0859baaac4458330096ef5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Sep 2022 21:15:57 +0530 Subject: [PATCH 292/898] fix: Hanlde rounding loss for internal transfer (cherry picked from commit 6e47fd54a0c4d4b77ed6bedac896286d591416f8) --- erpnext/controllers/stock_controller.py | 40 ++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 9149b4d8570c..58cfc4fad93c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -142,13 +142,15 @@ def get_gl_entries( warehouse_with_no_account = [] precision = self.get_debit_field_precision() for item_row in voucher_details: - sle_list = sle_map.get(item_row.name) + sle_rounding_diff = 0.0 if sle_list: for sle in sle_list: if warehouse_account.get(sle.warehouse): # from warehouse account + sle_rounding_diff += flt(sle.stock_value_difference, precision) + self.check_expense_account(item_row) # expense account/ target_warehouse / source_warehouse @@ -191,6 +193,42 @@ def get_gl_entries( elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) + if sle_rounding_diff > 0: + expense_account = item_row.get("expense_account") + target_warehouse_account = warehouse_account[item_row.get("target_warehouse")]["account"] + source_warehouse_account = warehouse_account[item_row.get("warehouse")]["account"] + + gl_list.append( + self.get_gl_dict( + { + "account": target_warehouse_account or expense_account, + "against": expense_account, + "cost_center": item_row.cost_center, + "project": item_row.project or self.get("project"), + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": sle_rounding_diff, + "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", + }, + warehouse_account[sle.warehouse]["account_currency"], + item=item_row, + ) + ) + + gl_list.append( + self.get_gl_dict( + { + "account": source_warehouse_account or expense_account, + "against": target_warehouse_account, + "cost_center": item_row.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": -1 * sle_rounding_diff, + "project": item_row.get("project") or self.get("project"), + "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", + }, + item=item_row, + ) + ) + if warehouse_with_no_account: for wh in warehouse_with_no_account: if frappe.db.get_value("Warehouse", wh, "company"): From 09e967565327edbb43335904507669a79bdb18b1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Oct 2022 14:53:26 +0530 Subject: [PATCH 293/898] chore: Increase incoming_rate field precision to 6 (cherry picked from commit b31c3bd35db39a1abdbb30aaebc017a833a5a61f) --- .../doctype/sales_invoice_item/sales_invoice_item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 4f97b63789a8..a307a6c17cdb 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -820,6 +820,7 @@ "label": "Incoming Rate (Costing)", "no_copy": 1, "options": "Company:company:default_currency", + "precision": "6", "print_hide": 1 }, { @@ -875,7 +876,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-09-06 14:17:43.394309", + "modified": "2022-10-10 20:57:38.340026", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", From 49221d47c27b400517a71273856b7b43744b8066 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Oct 2022 14:54:27 +0530 Subject: [PATCH 294/898] test: Internal tranfer precision loss test (cherry picked from commit dc20b21fb5162c7905662c10d954a07b4c30ca54) --- .../sales_invoice/test_sales_invoice.py | 214 +++++++++++------- erpnext/stock/doctype/item/test_item.py | 7 +- .../test_stock_ledger_entry.py | 18 +- .../stock_reconciliation.py | 4 +- 4 files changed, 147 insertions(+), 96 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index ce44ae304b3b..b63b9edab034 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -32,10 +32,20 @@ get_qty_after_transaction, make_stock_entry, ) -from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_stock_reconciliation, +) +from erpnext.stock.utils import get_incoming_rate, get_stock_balance class TestSalesInvoice(unittest.TestCase): + def setUp(self): + from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items + + create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}]) + create_internal_parties() + setup_accounts() + def make(self): w = frappe.copy_doc(test_records[0]) w.is_pos = 0 @@ -1705,7 +1715,7 @@ def test_create_so_with_margin(self): si.save() self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate)) - def test_outstanding_amount_after_advance_jv_cancelation(self): + def test_outstanding_amount_after_advance_jv_cancellation(self): from erpnext.accounts.doctype.journal_entry.test_journal_entry import ( test_records as jv_test_records, ) @@ -1749,7 +1759,7 @@ def test_outstanding_amount_after_advance_jv_cancelation(self): flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")), ) - def test_outstanding_amount_after_advance_payment_entry_cancelation(self): + def test_outstanding_amount_after_advance_payment_entry_cancellation(self): pe = frappe.get_doc( { "doctype": "Payment Entry", @@ -2367,29 +2377,6 @@ def test_fixed_deferred_revenue(self): acc_settings.save() def test_inter_company_transaction(self): - from erpnext.selling.doctype.customer.test_customer import create_internal_customer - - create_internal_customer( - customer_name="_Test Internal Customer", - represents_company="_Test Company 1", - allowed_to_interact_with="Wind Power LLC", - ) - - if not frappe.db.exists("Supplier", "_Test Internal Supplier"): - supplier = frappe.get_doc( - { - "supplier_group": "_Test Supplier Group", - "supplier_name": "_Test Internal Supplier", - "doctype": "Supplier", - "is_internal_supplier": 1, - "represents_company": "Wind Power LLC", - } - ) - - supplier.append("companies", {"company": "_Test Company 1"}) - - supplier.insert() - si = create_sales_invoice( company="Wind Power LLC", customer="_Test Internal Customer", @@ -2440,38 +2427,6 @@ def test_inter_company_transaction_without_default_warehouse(self): "Expenses Included In Valuation - _TC1", ) - if not frappe.db.exists("Customer", "_Test Internal Customer"): - customer = frappe.get_doc( - { - "customer_group": "_Test Customer Group", - "customer_name": "_Test Internal Customer", - "customer_type": "Individual", - "doctype": "Customer", - "territory": "_Test Territory", - "is_internal_customer": 1, - "represents_company": "_Test Company 1", - } - ) - - customer.append("companies", {"company": "Wind Power LLC"}) - - customer.insert() - - if not frappe.db.exists("Supplier", "_Test Internal Supplier"): - supplier = frappe.get_doc( - { - "supplier_group": "_Test Supplier Group", - "supplier_name": "_Test Internal Supplier", - "doctype": "Supplier", - "is_internal_supplier": 1, - "represents_company": "Wind Power LLC", - } - ) - - supplier.append("companies", {"company": "_Test Company 1"}) - - supplier.insert() - # begin test si = create_sales_invoice( company="Wind Power LLC", @@ -2541,34 +2496,9 @@ def test_sle_for_target_warehouse(self): se.cancel() def test_internal_transfer_gl_entry(self): - ## Create internal transfer account - from erpnext.selling.doctype.customer.test_customer import create_internal_customer - - account = create_account( - account_name="Unrealized Profit", - parent_account="Current Liabilities - TCP1", - company="_Test Company with perpetual inventory", - ) - - frappe.db.set_value( - "Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account - ) - - customer = create_internal_customer( - "_Test Internal Customer 2", - "_Test Company with perpetual inventory", - "_Test Company with perpetual inventory", - ) - - create_internal_supplier( - "_Test Internal Supplier 2", - "_Test Company with perpetual inventory", - "_Test Company with perpetual inventory", - ) - si = create_sales_invoice( company="_Test Company with perpetual inventory", - customer=customer, + customer="_Test Internal Customer 2", debit_to="Debtors - TCP1", warehouse="Stores - TCP1", income_account="Sales - TCP1", @@ -2582,7 +2512,7 @@ def test_internal_transfer_gl_entry(self): si.update_stock = 1 si.items[0].target_warehouse = "Work In Progress - TCP1" - # Add stock to stores for succesful stock transfer + # Add stock to stores for successful stock transfer make_stock_entry( target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100 ) @@ -2638,6 +2568,77 @@ def test_internal_transfer_gl_entry(self): check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1)) + def test_internal_transfer_gl_precision_issues(self): + # Make a stock queue of an item with two valuations + + # Remove all existing stock for this + if get_stock_balance("_Test Internal Transfer Item", "Stores - TCP1", "2022-04-10"): + create_stock_reconciliation( + item_code="_Test Internal Transfer Item", + warehouse="Stores - TCP1", + qty=0, + rate=0, + company="_Test Company with perpetual inventory", + expense_account="Stock Adjustment - TCP1" + if frappe.get_all("Stock Ledger Entry") + else "Temporary Opening - TCP1", + posting_date="2020-04-10", + posting_time="14:00", + ) + + make_stock_entry( + item_code="_Test Internal Transfer Item", + target="Stores - TCP1", + qty=9000000, + basic_rate=52.0, + posting_date="2020-04-10", + posting_time="14:00", + ) + make_stock_entry( + item_code="_Test Internal Transfer Item", + target="Stores - TCP1", + qty=60000000, + basic_rate=52.349777, + posting_date="2020-04-10", + posting_time="14:00", + ) + + # Make an internal transfer Sales Invoice Stock in non stock uom to check + # for rounding errors while converting to stock uom + si = create_sales_invoice( + company="_Test Company with perpetual inventory", + customer="_Test Internal Customer 2", + item_code="_Test Internal Transfer Item", + qty=5000000, + uom="Box", + debit_to="Debtors - TCP1", + warehouse="Stores - TCP1", + income_account="Sales - TCP1", + expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", + currency="INR", + do_not_save=1, + ) + + # Check GL Entries with precision + si.update_stock = 1 + si.items[0].target_warehouse = "Work In Progress - TCP1" + si.items[0].conversion_factor = 10 + si.save() + si.submit() + + # Check if adjustment entry is created + self.assertTrue( + frappe.db.exists( + "GL Entry", + { + "voucher_type": "Sales Invoice", + "voucher_no": si.name, + "remarks": "Rounding gain/loss Entry for Stock Transfer", + }, + ) + ) + def test_item_tax_net_range(self): item = create_item("T Shirt") @@ -3077,7 +3078,7 @@ def test_multi_currency_deferred_revenue_via_journal_entry(self): [deferred_account, 2022.47, 0.0, "2019-03-15"], ] - gl_entries = gl_entries = frappe.db.sql( + gl_entries = frappe.db.sql( """select account, debit, credit, posting_date from `tabGL Entry` where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s @@ -3431,6 +3432,34 @@ def get_taxes_and_charges(): ] +def create_internal_parties(): + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + + create_internal_customer( + customer_name="_Test Internal Customer", + represents_company="_Test Company 1", + allowed_to_interact_with="Wind Power LLC", + ) + + create_internal_customer( + customer_name="_Test Internal Customer 2", + represents_company="_Test Company with perpetual inventory", + allowed_to_interact_with="_Test Company with perpetual inventory", + ) + + create_internal_supplier( + supplier_name="_Test Internal Supplier", + represents_company="Wind Power LLC", + allowed_to_interact_with="_Test Company 1", + ) + + create_internal_supplier( + supplier_name="_Test Internal Supplier 2", + represents_company="_Test Company with perpetual inventory", + allowed_to_interact_with="_Test Company with perpetual inventory", + ) + + def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with): if not frappe.db.exists("Supplier", supplier_name): supplier = frappe.get_doc( @@ -3453,6 +3482,19 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter return supplier_name +def setup_accounts(): + ## Create internal transfer account + account = create_account( + account_name="Unrealized Profit", + parent_account="Current Liabilities - TCP1", + company="_Test Company with perpetual inventory", + ) + + frappe.db.set_value( + "Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account + ) + + def add_taxes(doc): doc.append( "taxes", diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e35c8bf335e3..31c9919708f9 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -32,7 +32,7 @@ test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] -def make_item(item_code=None, properties=None): +def make_item(item_code=None, properties=None, uoms=None): if not item_code: item_code = frappe.generate_hash(length=16) @@ -56,6 +56,11 @@ def make_item(item_code=None, properties=None): for item_default in [doc for doc in item.get("item_defaults") if not doc.default_warehouse]: item_default.default_warehouse = "_Test Warehouse - _TC" item_default.company = "_Test Company" + + if uoms: + for uom in uoms: + item.append("uoms", uom) + item.insert() return item diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 1410da56a389..6c341d9e9ec2 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -1323,13 +1323,15 @@ def create_product_bundle_item(new_item_code, packed_items): item.save() -def create_items(): - items = [ - "_Test Item for Reposting", - "_Test Finished Item for Reposting", - "_Test Subcontracted Item for Reposting", - "_Test Bundled Item for Reposting", - ] +def create_items(items=None, uoms=None): + if not items: + items = [ + "_Test Item for Reposting", + "_Test Finished Item for Reposting", + "_Test Subcontracted Item for Reposting", + "_Test Bundled Item for Reposting", + ] + for d in items: properties = {"valuation_method": "FIFO"} if d == "_Test Bundled Item for Reposting": @@ -1337,7 +1339,7 @@ def create_items(): elif d == "_Test Subcontracted Item for Reposting": properties.update({"is_sub_contracted_item": 1}) - make_item(d, properties=properties) + make_item(d, properties=properties, uoms=uoms) return items diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 23e0f1efafaa..d92d0f1686ee 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -132,7 +132,9 @@ def _get_msg(row_num, msg): key.append(row.get(field)) if key in item_warehouse_combinations: - self.validation_messages.append(_get_msg(row_num, _("Duplicate entry"))) + self.validation_messages.append( + _get_msg(row_num, _("Same item and warehouse combination already entered.")) + ) else: item_warehouse_combinations.append(key) From b386b8c6346fc5ae07e6e158e05bc33fc865d2c6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Oct 2022 14:55:09 +0530 Subject: [PATCH 295/898] chore: GL Entries for SLE diff (cherry picked from commit df2a0e265b85010d92b1aab50ccccc531b26c8fe) --- erpnext/controllers/stock_controller.py | 29 +++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 58cfc4fad93c..4629f4911c6e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -149,7 +149,7 @@ def get_gl_entries( if warehouse_account.get(sle.warehouse): # from warehouse account - sle_rounding_diff += flt(sle.stock_value_difference, precision) + sle_rounding_diff += flt(sle.stock_value_difference) self.check_expense_account(item_row) @@ -193,19 +193,24 @@ def get_gl_entries( elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) - if sle_rounding_diff > 0: - expense_account = item_row.get("expense_account") - target_warehouse_account = warehouse_account[item_row.get("target_warehouse")]["account"] - source_warehouse_account = warehouse_account[item_row.get("warehouse")]["account"] + if abs(sle_rounding_diff) > 0.1 and ( + self.get("is_internal_customer") or self.get("is_internal_supplier") + ): + asset_account = "" + if self.get("is_internal_customer"): + asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] + elif self.get("is_internal_supplier"): + asset_account = warehouse_account[item_row.get("from_warehouse")]["account"] + expense_account = item_row.get("expense_account") gl_list.append( self.get_gl_dict( { - "account": target_warehouse_account or expense_account, - "against": expense_account, + "account": expense_account, + "against": asset_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "remarks": _("Rounding gain/loss Entry for Stock Transfer"), "debit": sle_rounding_diff, "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", }, @@ -217,11 +222,11 @@ def get_gl_entries( gl_list.append( self.get_gl_dict( { - "account": source_warehouse_account or expense_account, - "against": target_warehouse_account, + "account": asset_account, + "against": expense_account, "cost_center": item_row.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * sle_rounding_diff, + "remarks": _("Rounding gain/loss Entry for Stock Transfer"), + "credit": sle_rounding_diff, "project": item_row.get("project") or self.get("project"), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", }, From 23b18a5e3832f93c40bc77132a882ce1d2a00f32 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 14:17:55 +0530 Subject: [PATCH 296/898] chore: Increase precision for other doc fields (cherry picked from commit c8d2181498616c8c6757e28255977c66a65a30d9) --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 3 ++- .../stock/doctype/delivery_note_item/delivery_note_item.json | 3 ++- .../doctype/purchase_receipt_item/purchase_receipt_item.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index fca7e3a88732..9de90368871f 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -711,6 +711,7 @@ "label": "Valuation Rate", "no_copy": 1, "options": "Company:company:default_currency", + "precision": "6", "print_hide": 1, "read_only": 1 }, @@ -870,7 +871,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-09-27 10:54:23.980713", + "modified": "2022-10-12 03:37:29.032732", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 0911cdb476ce..0a5cbabab0b0 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -753,6 +753,7 @@ "fieldtype": "Currency", "label": "Incoming Rate", "no_copy": 1, + "precision": "6", "print_hide": 1, "read_only": 1 }, @@ -813,7 +814,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-09-06 14:19:42.876357", + "modified": "2022-10-12 03:36:05.344847", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 39833b5e9105..772736e0adc7 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -744,6 +744,7 @@ "oldfieldname": "valuation_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", + "precision": "6", "print_hide": 1, "print_width": "80px", "read_only": 1, @@ -999,7 +1000,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-07-28 19:27:54.880781", + "modified": "2022-10-12 03:37:59.516609", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 4f3e209009c90683210e932dbe17caec22d4f040 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 14:19:09 +0530 Subject: [PATCH 297/898] chore: Use proper accounts (cherry picked from commit 1c05c004cd1739ecdf724a87edd2472991c16cfe) --- erpnext/controllers/stock_controller.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4629f4911c6e..b32f71750f48 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -193,21 +193,22 @@ def get_gl_entries( elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) - if abs(sle_rounding_diff) > 0.1 and ( + if abs(sle_rounding_diff) < (1.0 / (10**precision)) and ( self.get("is_internal_customer") or self.get("is_internal_supplier") ): - asset_account = "" + warehouse_asset_account = "" if self.get("is_internal_customer"): - asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] + warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] elif self.get("is_internal_supplier"): - asset_account = warehouse_account[item_row.get("from_warehouse")]["account"] + warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] + + expense_account = frappe.db.get_value("Company", self.company, "default_expense_account") - expense_account = item_row.get("expense_account") gl_list.append( self.get_gl_dict( { "account": expense_account, - "against": asset_account, + "against": warehouse_asset_account, "cost_center": item_row.cost_center, "project": item_row.project or self.get("project"), "remarks": _("Rounding gain/loss Entry for Stock Transfer"), @@ -222,7 +223,7 @@ def get_gl_entries( gl_list.append( self.get_gl_dict( { - "account": asset_account, + "account": warehouse_asset_account, "against": expense_account, "cost_center": item_row.cost_center, "remarks": _("Rounding gain/loss Entry for Stock Transfer"), From 108720b9c3ee7674d6bb393b2cbb2e18ea648f43 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 14:57:16 +0530 Subject: [PATCH 298/898] chore: fix precision condition (cherry picked from commit 49601558c6f10beafa64372dc3203645259d9264) --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b32f71750f48..31c480f98d0a 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -193,7 +193,7 @@ def get_gl_entries( elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) - if abs(sle_rounding_diff) < (1.0 / (10**precision)) and ( + if abs(sle_rounding_diff) > (1.0 / (10**precision)) and ( self.get("is_internal_customer") or self.get("is_internal_supplier") ): warehouse_asset_account = "" From 2494ccc0a2b1faa6aa07b40cc08e441e4aae6c6c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 12 Oct 2022 15:53:28 +0530 Subject: [PATCH 299/898] chore: check only for inter-company transfers (cherry picked from commit 9aa5e20ef7a466713e33a35f35d3219b36358b51) --- erpnext/controllers/stock_controller.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 31c480f98d0a..98dc58677b9f 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -193,9 +193,7 @@ def get_gl_entries( elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) - if abs(sle_rounding_diff) > (1.0 / (10**precision)) and ( - self.get("is_internal_customer") or self.get("is_internal_supplier") - ): + if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer(): warehouse_asset_account = "" if self.get("is_internal_customer"): warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"] From 31782d6f71d8066f64f90ce56e29d1832ae32235 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 12 Oct 2022 16:34:48 +0530 Subject: [PATCH 300/898] fix: removed unnecessary imports --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 1f807b8249b4..dc9f2b21177c 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1,10 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -from cmath import pi -from turtle import update - import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, today @@ -17,7 +13,6 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse -from erpnext.stock.get_item_details import update_stock from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction From cb0c4b566455a71577e084fa910e4ce15b114984 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 12 Oct 2022 12:15:07 +0530 Subject: [PATCH 301/898] fix: `Brand Defaults` filters (cherry picked from commit 7da32c7db34688120359703df135dbf302af3421) --- erpnext/setup/doctype/brand/brand.js | 68 ++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/erpnext/setup/doctype/brand/brand.js b/erpnext/setup/doctype/brand/brand.js index 3680906057fa..0abb71a3629b 100644 --- a/erpnext/setup/doctype/brand/brand.js +++ b/erpnext/setup/doctype/brand/brand.js @@ -1,13 +1,71 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +frappe.ui.form.on('Brand', { + setup: (frm) => { + frm.fields_dict["brand_defaults"].grid.get_field("default_warehouse").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { company: row.company } + } + } + frm.fields_dict["brand_defaults"].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { + 'report_type': 'Profit and Loss', + 'company': row.company, + "is_group": 0 + } + }; + } -//--------- ONLOAD ------------- -cur_frm.cscript.onload = function(doc, cdt, cdn) { + frm.fields_dict["brand_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { + "is_group": 0, + "company": row.company + } + } + } -} + frm.fields_dict["brand_defaults"].grid.get_field("expense_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + query: "erpnext.controllers.queries.get_expense_account", + filters: { company: row.company } + } + } -cur_frm.cscript.refresh = function(doc, cdt, cdn) { + frm.fields_dict["brand_defaults"].grid.get_field("default_provisional_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { + "company": row.company, + "root_type": ["in", ["Liability", "Asset"]], + "is_group": 0 + } + }; + } -} + frm.fields_dict["brand_defaults"].grid.get_field("selling_cost_center").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { + "is_group": 0, + "company": row.company + } + } + } + + frm.fields_dict["brand_defaults"].grid.get_field("income_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + query: "erpnext.controllers.queries.get_income_account", + filters: { company: row.company } + } + } + } +}); \ No newline at end of file From 829a0ff827d81beca6bb98caff0c9295da204e2c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:54:22 +0530 Subject: [PATCH 302/898] fix: type-cast while saving an item (backport #32549) (#32578) fix: type-cast while saving an item (#32549) (cherry picked from commit 43037d893d6c31e06df88883f1c529276d28803b) Co-authored-by: Rohan --- erpnext/stock/doctype/item/item.py | 4 ++-- erpnext/stock/doctype/item/test_item.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c8bb1b960eb6..20bc9d9b2c92 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -232,10 +232,10 @@ def validate_retain_sample(self): def clear_retain_sample(self): if not self.has_batch_no: - self.retain_sample = None + self.retain_sample = False if not self.retain_sample: - self.sample_quantity = None + self.sample_quantity = 0 def add_default_uom_in_conversion_factor_table(self): if not self.is_new() and self.has_value_changed("stock_uom"): diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 31c9919708f9..e1ee9389de98 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -722,8 +722,8 @@ def test_retain_sample(self): item.has_batch_no = None item.save() - self.assertEqual(item.retain_sample, None) - self.assertEqual(item.sample_quantity, None) + self.assertEqual(item.retain_sample, False) + self.assertEqual(item.sample_quantity, 0) item.delete() def consume_item_code_with_differet_stock_transactions( From ed85683fea42bd9cb96e9a28a37446db48a38033 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:55:36 +0530 Subject: [PATCH 303/898] fix: don't try to update youtube data if disabled in settings (backport #32588) (#32589) fix: don't try to update youtube data if disabled in settings (#32588) fix: cast value from db [skip ci] (cherry picked from commit e543dca6a0220b3e77560c6c2beddc32c6672f00) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/utilities/doctype/video/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/utilities/doctype/video/video.py b/erpnext/utilities/doctype/video/video.py index 15dbccde89d4..13b7877b21d8 100644 --- a/erpnext/utilities/doctype/video/video.py +++ b/erpnext/utilities/doctype/video/video.py @@ -59,7 +59,7 @@ def update_youtube_data(): "Video Settings", "Video Settings", ["enable_youtube_tracking", "frequency"] ) - if not enable_youtube_tracking: + if not cint(enable_youtube_tracking): return frequency = get_frequency(frequency) From 62cabdf792d98c39bedddb20f6d1e641ed2b40c5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Oct 2022 16:17:56 +0530 Subject: [PATCH 304/898] fix: delete old ple's on item value repost (cherry picked from commit 65992304bc259a44c60198b3b6e3be0f5b107b3e) --- erpnext/accounts/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 95ba3d86ceff..02dcd683fa26 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1171,6 +1171,10 @@ def _delete_gl_entries(voucher_type, voucher_no): where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), ) + ple = qb.DocType("Payment Ledger Entry") + qb.from_(ple).delete().where( + (ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no) + ).run() def sort_stock_vouchers_by_posting_date( From 3c457e2638e5d50dd24848c0c21d8d3852ffa93f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Oct 2022 15:27:28 +0530 Subject: [PATCH 305/898] test: dedeplication on payment ledger upon repost (cherry picked from commit eb819368aa9a8700ee868cbb8375aa893e9de72f) --- .../test_repost_item_valuation.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) 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 edd2553d5d14..e0f247971005 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 @@ -9,6 +9,7 @@ from frappe.utils import nowdate from frappe.utils.data import add_to_date, today +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.utils import repost_gle_for_stock_vouchers from erpnext.controllers.stock_controller import create_item_wise_repost_entries from erpnext.stock.doctype.item.test_item import make_item @@ -272,3 +273,57 @@ def test_gl_complete_gl_reposting(self): [{"credit": 50, "debit": 0}], gle_filters={"account": "Stock In Hand - TCP1"}, ) + + def test_duplicate_ple_on_repost(self): + from erpnext.accounts import utils + + # lower numbers to simplify test + orig_chunk_size = utils.GL_REPOSTING_CHUNK + utils.GL_REPOSTING_CHUNK = 2 + self.addCleanup(setattr, utils, "GL_REPOSTING_CHUNK", orig_chunk_size) + + rate = 100 + item = self.make_item() + item.valuation_rate = 90 + item.allow_negative_stock = 1 + item.save() + + company = "_Test Company with perpetual inventory" + + # consume non-existing stock + sinv = create_sales_invoice( + company=company, + posting_date=today(), + debit_to="Debtors - TCP1", + income_account="Sales - TCP1", + expense_account="Cost of Goods Sold - TCP1", + warehouse="Stores - TCP1", + update_stock=1, + currency="INR", + item_code=item.name, + cost_center="Main - TCP1", + qty=1, + rate=rate, + ) + + # backdated receipt triggers repost + make_stock_entry( + item=item.name, + company=company, + qty=5, + rate=rate, + target="Stores - TCP1", + posting_date=add_to_date(today(), days=-1), + ) + + ple_entries = frappe.db.get_list( + "Payment Ledger Entry", + filters={"voucher_type": sinv.doctype, "voucher_no": sinv.name, "delinked": 0}, + ) + + # assert successful deduplication on PLE + self.assertEqual(len(ple_entries), 1) + + # outstanding should not be affected + sinv.reload() + self.assertEqual(sinv.outstanding_amount, 100) From 6a89cb57b095c54e1756896a15f5c60a5393cf00 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 15:27:17 +0530 Subject: [PATCH 306/898] chore: drop dead code (backport #32595) (#32596) chore: drop dead code (#32595) [skip ci] (cherry picked from commit 50e96989324f475fc386882eb8a8dc1df4a8c975) Co-authored-by: Ankush Menat --- erpnext/hooks.py | 2 - erpnext/startup/report_data_map.py | 327 ----------------------------- 2 files changed, 329 deletions(-) delete mode 100644 erpnext/startup/report_data_map.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index b8f51f839ceb..6bc17a3675a8 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -274,8 +274,6 @@ "Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission", } -dump_report_map = "erpnext.startup.report_data_map.data_map" - before_tests = "erpnext.setup.utils.before_tests" standard_queries = { diff --git a/erpnext/startup/report_data_map.py b/erpnext/startup/report_data_map.py deleted file mode 100644 index f8c1b6cca070..000000000000 --- a/erpnext/startup/report_data_map.py +++ /dev/null @@ -1,327 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -# mappings for table dumps -# "remember to add indexes!" - -data_map = { - "Company": {"columns": ["name"], "conditions": ["docstatus < 2"]}, - "Fiscal Year": { - "columns": ["name", "year_start_date", "year_end_date"], - "conditions": ["docstatus < 2"], - }, - # Accounts - "Account": { - "columns": ["name", "parent_account", "lft", "rgt", "report_type", "company", "is_group"], - "conditions": ["docstatus < 2"], - "order_by": "lft", - "links": { - "company": ["Company", "name"], - }, - }, - "Cost Center": { - "columns": ["name", "lft", "rgt"], - "conditions": ["docstatus < 2"], - "order_by": "lft", - }, - "GL Entry": { - "columns": [ - "name", - "account", - "posting_date", - "cost_center", - "debit", - "credit", - "is_opening", - "company", - "voucher_type", - "voucher_no", - "remarks", - ], - "order_by": "posting_date, account", - "links": { - "account": ["Account", "name"], - "company": ["Company", "name"], - "cost_center": ["Cost Center", "name"], - }, - }, - # Stock - "Item": { - "columns": [ - "name", - "if(item_name=name, '', item_name) as item_name", - "description", - "item_group as parent_item_group", - "stock_uom", - "brand", - "valuation_method", - ], - # "conditions": ["docstatus < 2"], - "order_by": "name", - "links": {"parent_item_group": ["Item Group", "name"], "brand": ["Brand", "name"]}, - }, - "Item Group": { - "columns": ["name", "parent_item_group"], - # "conditions": ["docstatus < 2"], - "order_by": "lft", - }, - "Brand": {"columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name"}, - "Project": {"columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name"}, - "Warehouse": {"columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name"}, - "Stock Ledger Entry": { - "columns": [ - "name", - "posting_date", - "posting_time", - "item_code", - "warehouse", - "actual_qty as qty", - "voucher_type", - "voucher_no", - "project", - "incoming_rate as incoming_rate", - "stock_uom", - "serial_no", - "qty_after_transaction", - "valuation_rate", - ], - "order_by": "posting_date, posting_time, creation", - "links": { - "item_code": ["Item", "name"], - "warehouse": ["Warehouse", "name"], - "project": ["Project", "name"], - }, - "force_index": "posting_sort_index", - }, - "Serial No": { - "columns": ["name", "purchase_rate as incoming_rate"], - "conditions": ["docstatus < 2"], - "order_by": "name", - }, - "Stock Entry": { - "columns": ["name", "purpose"], - "conditions": ["docstatus=1"], - "order_by": "posting_date, posting_time, name", - }, - "Material Request Item": { - "columns": ["item.name as name", "item_code", "warehouse", "(qty - ordered_qty) as qty"], - "from": "`tabMaterial Request Item` item, `tabMaterial Request` main", - "conditions": [ - "item.parent = main.name", - "main.docstatus=1", - "main.status != 'Stopped'", - "ifnull(warehouse, '')!=''", - "qty > ordered_qty", - ], - "links": {"item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"]}, - }, - "Purchase Order Item": { - "columns": [ - "item.name as name", - "item_code", - "warehouse", - "(qty - received_qty)*conversion_factor as qty", - ], - "from": "`tabPurchase Order Item` item, `tabPurchase Order` main", - "conditions": [ - "item.parent = main.name", - "main.docstatus=1", - "main.status != 'Stopped'", - "ifnull(warehouse, '')!=''", - "qty > received_qty", - ], - "links": {"item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"]}, - }, - "Sales Order Item": { - "columns": [ - "item.name as name", - "item_code", - "(qty - delivered_qty)*conversion_factor as qty", - "warehouse", - ], - "from": "`tabSales Order Item` item, `tabSales Order` main", - "conditions": [ - "item.parent = main.name", - "main.docstatus=1", - "main.status != 'Stopped'", - "ifnull(warehouse, '')!=''", - "qty > delivered_qty", - ], - "links": {"item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"]}, - }, - # Sales - "Customer": { - "columns": [ - "name", - "if(customer_name=name, '', customer_name) as customer_name", - "customer_group as parent_customer_group", - "territory as parent_territory", - ], - "conditions": ["docstatus < 2"], - "order_by": "name", - "links": { - "parent_customer_group": ["Customer Group", "name"], - "parent_territory": ["Territory", "name"], - }, - }, - "Customer Group": { - "columns": ["name", "parent_customer_group"], - "conditions": ["docstatus < 2"], - "order_by": "lft", - }, - "Territory": { - "columns": ["name", "parent_territory"], - "conditions": ["docstatus < 2"], - "order_by": "lft", - }, - "Sales Invoice": { - "columns": ["name", "customer", "posting_date", "company"], - "conditions": ["docstatus=1"], - "order_by": "posting_date", - "links": {"customer": ["Customer", "name"], "company": ["Company", "name"]}, - }, - "Sales Invoice Item": { - "columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"], - "conditions": ["docstatus=1", "ifnull(parent, '')!=''"], - "order_by": "parent", - "links": {"parent": ["Sales Invoice", "name"], "item_code": ["Item", "name"]}, - }, - "Sales Order": { - "columns": ["name", "customer", "transaction_date as posting_date", "company"], - "conditions": ["docstatus=1"], - "order_by": "transaction_date", - "links": {"customer": ["Customer", "name"], "company": ["Company", "name"]}, - }, - "Sales Order Item[Sales Analytics]": { - "columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"], - "conditions": ["docstatus=1", "ifnull(parent, '')!=''"], - "order_by": "parent", - "links": {"parent": ["Sales Order", "name"], "item_code": ["Item", "name"]}, - }, - "Delivery Note": { - "columns": ["name", "customer", "posting_date", "company"], - "conditions": ["docstatus=1"], - "order_by": "posting_date", - "links": {"customer": ["Customer", "name"], "company": ["Company", "name"]}, - }, - "Delivery Note Item[Sales Analytics]": { - "columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"], - "conditions": ["docstatus=1", "ifnull(parent, '')!=''"], - "order_by": "parent", - "links": {"parent": ["Delivery Note", "name"], "item_code": ["Item", "name"]}, - }, - "Supplier": { - "columns": [ - "name", - "if(supplier_name=name, '', supplier_name) as supplier_name", - "supplier_group as parent_supplier_group", - ], - "conditions": ["docstatus < 2"], - "order_by": "name", - "links": { - "parent_supplier_group": ["Supplier Group", "name"], - }, - }, - "Supplier Group": { - "columns": ["name", "parent_supplier_group"], - "conditions": ["docstatus < 2"], - "order_by": "name", - }, - "Purchase Invoice": { - "columns": ["name", "supplier", "posting_date", "company"], - "conditions": ["docstatus=1"], - "order_by": "posting_date", - "links": {"supplier": ["Supplier", "name"], "company": ["Company", "name"]}, - }, - "Purchase Invoice Item": { - "columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"], - "conditions": ["docstatus=1", "ifnull(parent, '')!=''"], - "order_by": "parent", - "links": {"parent": ["Purchase Invoice", "name"], "item_code": ["Item", "name"]}, - }, - "Purchase Order": { - "columns": ["name", "supplier", "transaction_date as posting_date", "company"], - "conditions": ["docstatus=1"], - "order_by": "posting_date", - "links": {"supplier": ["Supplier", "name"], "company": ["Company", "name"]}, - }, - "Purchase Order Item[Purchase Analytics]": { - "columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"], - "conditions": ["docstatus=1", "ifnull(parent, '')!=''"], - "order_by": "parent", - "links": {"parent": ["Purchase Order", "name"], "item_code": ["Item", "name"]}, - }, - "Purchase Receipt": { - "columns": ["name", "supplier", "posting_date", "company"], - "conditions": ["docstatus=1"], - "order_by": "posting_date", - "links": {"supplier": ["Supplier", "name"], "company": ["Company", "name"]}, - }, - "Purchase Receipt Item[Purchase Analytics]": { - "columns": ["name", "parent", "item_code", "stock_qty as qty", "base_net_amount"], - "conditions": ["docstatus=1", "ifnull(parent, '')!=''"], - "order_by": "parent", - "links": {"parent": ["Purchase Receipt", "name"], "item_code": ["Item", "name"]}, - }, - # Support - "Issue": { - "columns": ["name", "status", "creation", "resolution_date", "first_responded_on"], - "conditions": ["docstatus < 2"], - "order_by": "creation", - }, - # Manufacturing - "Work Order": { - "columns": [ - "name", - "status", - "creation", - "planned_start_date", - "planned_end_date", - "status", - "actual_start_date", - "actual_end_date", - "modified", - ], - "conditions": ["docstatus = 1"], - "order_by": "creation", - }, - # Medical - "Patient": { - "columns": [ - "name", - "creation", - "owner", - "if(patient_name=name, '', patient_name) as patient_name", - ], - "conditions": ["docstatus < 2"], - "order_by": "name", - "links": {"owner": ["User", "name"]}, - }, - "Patient Appointment": { - "columns": [ - "name", - "appointment_type", - "patient", - "practitioner", - "appointment_date", - "department", - "status", - "company", - ], - "order_by": "name", - "links": { - "practitioner": ["Healthcare Practitioner", "name"], - "appointment_type": ["Appointment Type", "name"], - }, - }, - "Healthcare Practitioner": { - "columns": ["name", "department"], - "order_by": "name", - "links": { - "department": ["Department", "name"], - }, - }, - "Appointment Type": {"columns": ["name"], "order_by": "name"}, - "Medical Department": {"columns": ["name"], "order_by": "name"}, -} From af4dafdc64c53883c55c73620926fea00c021591 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Oct 2022 18:01:43 +0530 Subject: [PATCH 307/898] fix: add project settings to projects workspace (backport #32568) (#32600) Co-authored-by: Anand Baburajan --- .../projects/workspace/projects/projects.json | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 1253649e493f..4bdb1db387d0 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -5,7 +5,7 @@ "label": "Open Projects" } ], - "content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", + "content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", "creation": "2020-03-02 15:46:04.874669", "docstatus": 0, "doctype": "Workspace", @@ -170,9 +170,27 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "link_count": 1, + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Projects Settings", + "link_count": 0, + "link_to": "Projects Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" } ], - "modified": "2022-06-28 12:31:30.167740", + "modified": "2022-10-11 22:39:10.436311", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From eaea8469fc092458454f7859574833efa29211f4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 Oct 2022 15:08:34 +0530 Subject: [PATCH 308/898] fix: Party account for multi-order invoices (cherry picked from commit fd49503ba2dc19f07a3a8dce36812be753587e08) --- erpnext/buying/doctype/purchase_order/purchase_order.py | 3 ++- erpnext/selling/doctype/sales_order/sales_order.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index bcedd4d0a118..c224b611e52f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -18,7 +18,7 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) -from erpnext.accounts.party import get_party_account_currency +from erpnext.accounts.party import get_party_account, get_party_account_currency from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items from erpnext.controllers.buying_controller import BuyingController from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults @@ -558,6 +558,7 @@ def postprocess(source, target): target.set_advances() target.set_payment_schedule() + target.credit_to = get_party_account("Supplier", source.supplier, source.company) def update_item(obj, target, source_parent): target.amount = flt(obj.amount) - flt(obj.billed_amt) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 25806d6ed869..f0e9e4b7d927 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -18,6 +18,7 @@ update_linked_doc, validate_inter_company_party, ) +from erpnext.accounts.party import get_party_account from erpnext.controllers.selling_controller import SellingController from erpnext.manufacturing.doctype.production_plan.production_plan import ( get_items_for_material_requests, @@ -727,6 +728,8 @@ def set_missing_values(source, target): if source.loyalty_points and source.order_type == "Shopping Cart": target.redeem_loyalty_points = 1 + target.debit_to = get_party_account("Customer", source.customer, source.company) + def update_item(source, target, source_parent): target.amount = flt(source.amount) - flt(source.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) From 9e94dd9203396f745ab32740942e169536e1fac7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 13 Oct 2022 12:31:30 +0530 Subject: [PATCH 309/898] fix: Renamed Notes section to Comments (cherry picked from commit 56a4a7739882259ecce97896dbf626adc6177502) --- erpnext/crm/doctype/lead/lead.json | 4 ++-- erpnext/crm/doctype/opportunity/opportunity.json | 4 ++-- erpnext/crm/doctype/prospect/prospect.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 99c00ad6e693..6c8cb2f0800c 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -375,7 +375,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "notes_tab", "fieldtype": "Tab Break", - "label": "Notes" + "label": "Comments" }, { "collapsible": 1, @@ -514,7 +514,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2022-08-09 18:26:17.101521", + "modified": "2022-10-13 12:28:47.241586", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index fed0c7c79a20..36c98dc279f9 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -551,7 +551,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "notes_tab", "fieldtype": "Tab Break", - "label": "Notes" + "label": "Comments" }, { "fieldname": "notes_html", @@ -622,7 +622,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2022-08-09 18:26:37.235964", + "modified": "2022-10-13 12:29:18.233542", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 820a6c72ea10..d32311bc4e1d 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -128,7 +128,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "notes_section", "fieldtype": "Tab Break", - "label": "Notes" + "label": "Comments" }, { "depends_on": "eval: !doc.__islocal", @@ -218,7 +218,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-08-09 18:26:56.950185", + "modified": "2022-10-13 12:29:33.674561", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", From dac5989d72732a83c4e1c45851be635b860f50cb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 13 Oct 2022 12:43:08 +0530 Subject: [PATCH 310/898] fix: Renamed Dashboard tab label to Connections (cherry picked from commit f561d8f6895a07573b9fef134945da6c9739e04f) --- erpnext/crm/doctype/lead/lead.json | 4 ++-- erpnext/crm/doctype/opportunity/opportunity.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 6c8cb2f0800c..8f8a086d99ec 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -506,7 +506,7 @@ { "fieldname": "dashboard_tab", "fieldtype": "Tab Break", - "label": "Dashboard", + "label": "Connections", "show_dashboard": 1 } ], @@ -514,7 +514,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2022-10-13 12:28:47.241586", + "modified": "2022-10-13 12:42:04.277879", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 36c98dc279f9..07641d20c332 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -544,7 +544,7 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "dashboard_tab", "fieldtype": "Tab Break", - "label": "Dashboard", + "label": "Connections", "show_dashboard": 1 }, { @@ -622,7 +622,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2022-10-13 12:29:18.233542", + "modified": "2022-10-13 12:42:21.545636", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", From d9519f24aa8a69ca874792bf856fdce036129a82 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Oct 2022 11:37:50 +0530 Subject: [PATCH 311/898] chore: Remove HRMS related code (#32607) Co-authored-by: Rucha Mahabal (cherry picked from commit dc3fe8592187bf543eae628c639868de84ad9006) --- .../journal_entry_account/journal_entry_account.json | 12 +++++++++--- erpnext/accounts/utils.py | 4 ---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index dff883aef987..a0ea43332c9f 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -202,6 +202,7 @@ "fieldname": "reference_type", "fieldtype": "Select", "label": "Reference Type", + "no_copy": 1, "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement" }, { @@ -209,13 +210,15 @@ "fieldtype": "Dynamic Link", "in_list_view": 1, "label": "Reference Name", + "no_copy": 1, "options": "reference_type" }, { "depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])", "fieldname": "reference_due_date", "fieldtype": "Select", - "label": "Reference Due Date" + "label": "Reference Due Date", + "no_copy": 1 }, { "fieldname": "project", @@ -274,19 +277,22 @@ "fieldname": "reference_detail_no", "fieldtype": "Data", "hidden": 1, - "label": "Reference Detail No" + "label": "Reference Detail No", + "no_copy": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-08-30 21:27:32.200299", + "modified": "2022-10-13 17:07:17.999191", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 02dcd683fa26..67574ca9926c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -460,10 +460,6 @@ def reconcile_against_document(args): # nosemgrep frappe.flags.ignore_party_validation = False - if entry.voucher_type in ("Payment Entry", "Journal Entry"): - if hasattr(doc, "update_expense_claim"): - doc.update_expense_claim() - def check_if_advance_entry_modified(args): """ From faedd85b660fc0647ab877fc99ddd41634809f44 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Oct 2022 12:17:05 +0530 Subject: [PATCH 312/898] fix: group warehouse filter not working for Batchwise Balance history report (cherry picked from commit 6381e75fa5b402a678c94320eba14b19a3c11012) --- .../batch_wise_balance_history.py | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 8a13300dc836..911f4c31673a 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -5,6 +5,8 @@ import frappe from frappe import _ from frappe.utils import cint, flt, getdate +from pypika import functions as fn +from pypika.terms import ExistsCriterion def execute(filters=None): @@ -64,36 +66,61 @@ def get_columns(filters): return columns -def get_conditions(filters): - conditions = "" +# get all details +def get_stock_ledger_entries(filters): if not filters.get("from_date"): frappe.throw(_("'From Date' is required")) - if filters.get("to_date"): - conditions += " and posting_date <= '%s'" % filters["to_date"] + sle = frappe.qb.DocType("Stock Ledger Entry") + query = ( + frappe.qb.from_(sle) + .select( + sle.item_code, + sle.warehouse, + sle.batch_no, + sle.posting_date, + fn.Sum(sle.actual_qty).as_("actual_qty"), + ) + .where( + (sle.docstatus < 2) + & (sle.is_cancelled == 0) + & (sle.batch_no.isnotnull()) + & (sle.batch_no != "") + ) + .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) + .orderby(sle.item_code, sle.warehouse) + ) + + if to_date := filters.get("to_date"): + query = query.where(sle.posting_date <= to_date) else: frappe.throw(_("'To Date' is required")) - for field in ["item_code", "warehouse", "batch_no", "company"]: + query = apply_warehouse_filter(query, sle, filters) + for field in ["item_code", "batch_no", "company"]: if filters.get(field): - conditions += " and {0} = {1}".format(field, frappe.db.escape(filters.get(field))) + query = query.where(sle[field] == filters.get(field)) - return conditions + return query.run(as_dict=True) -# get all details -def get_stock_ledger_entries(filters): - conditions = get_conditions(filters) - return frappe.db.sql( - """ - select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty - from `tabStock Ledger Entry` - where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s - group by voucher_no, batch_no, item_code, warehouse - order by item_code, warehouse""" - % conditions, - as_dict=1, - ) +def apply_warehouse_filter(query, sle, filters): + if warehouse := filters.get("warehouse"): + warehouse_table = frappe.qb.DocType("Warehouse") + + lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) + chilren_subquery = ( + frappe.qb.from_(warehouse_table) + .select(warehouse_table.name) + .where( + (warehouse_table.lft >= lft) + & (warehouse_table.rgt <= rgt) + & (warehouse_table.name == sle.warehouse) + ) + ) + query = query.where(ExistsCriterion(chilren_subquery)) + + return query def get_item_warehouse_batch_map(filters, float_precision): From 1ee0d990e5fc3c3bcb1d9d3e42439009b02995e0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 17 Oct 2022 14:18:55 +0530 Subject: [PATCH 313/898] chore: seperate function to apply filter for warehouse in case of QB (cherry picked from commit 2481574a2808e888acc0b59492642e0ffd5d9376) --- erpnext/stock/doctype/warehouse/warehouse.py | 21 ++++++++++++++++++ .../batch_wise_balance_history.py | 22 ++----------------- .../report/stock_balance/stock_balance.py | 16 +++----------- .../stock/report/stock_ledger/stock_ledger.py | 17 ++------------ 4 files changed, 28 insertions(+), 48 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index ab784ca1070e..6e06d236173b 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -9,6 +9,7 @@ from frappe.contacts.address_and_contact import load_address_and_contact from frappe.utils import cint, flt from frappe.utils.nestedset import NestedSet +from pypika.terms import ExistsCriterion from erpnext.stock import get_warehouse_account @@ -266,3 +267,23 @@ def get_warehouses_based_on_account(account, company=None): frappe.throw(_("Warehouse not found against the account {0}").format(account)) return warehouses + + +# Will be use for frappe.qb +def apply_warehouse_filter(query, sle, filters): + if warehouse := filters.get("warehouse"): + warehouse_table = frappe.qb.DocType("Warehouse") + + lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) + chilren_subquery = ( + frappe.qb.from_(warehouse_table) + .select(warehouse_table.name) + .where( + (warehouse_table.lft >= lft) + & (warehouse_table.rgt <= rgt) + & (warehouse_table.name == sle.warehouse) + ) + ) + query = query.where(ExistsCriterion(chilren_subquery)) + + return query diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 911f4c31673a..291c6b5dab5b 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -6,7 +6,8 @@ from frappe import _ from frappe.utils import cint, flt, getdate from pypika import functions as fn -from pypika.terms import ExistsCriterion + +from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter def execute(filters=None): @@ -104,25 +105,6 @@ def get_stock_ledger_entries(filters): return query.run(as_dict=True) -def apply_warehouse_filter(query, sle, filters): - if warehouse := filters.get("warehouse"): - warehouse_table = frappe.qb.DocType("Warehouse") - - lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - chilren_subquery = ( - frappe.qb.from_(warehouse_table) - .select(warehouse_table.name) - .where( - (warehouse_table.lft >= lft) - & (warehouse_table.rgt <= rgt) - & (warehouse_table.name == sle.warehouse) - ) - ) - query = query.where(ExistsCriterion(chilren_subquery)) - - return query - - def get_item_warehouse_batch_map(filters, float_precision): sle = get_stock_ledger_entries(filters) iwb_map = {} diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 679d234c9f61..0fc642ef207f 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -10,10 +10,10 @@ from frappe.query_builder.functions import CombineDatetime from frappe.utils import cint, date_diff, flt, getdate from frappe.utils.nestedset import get_descendants_of -from pypika.terms import ExistsCriterion import erpnext from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions +from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, get_average_age from erpnext.stock.utils import add_additional_uom_columns, is_reposting_item_valuation_in_progress @@ -270,18 +270,8 @@ def apply_conditions(query, filters): if company := filters.get("company"): query = query.where(sle.company == company) - if warehouse := filters.get("warehouse"): - lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - chilren_subquery = ( - frappe.qb.from_(warehouse_table) - .select(warehouse_table.name) - .where( - (warehouse_table.lft >= lft) - & (warehouse_table.rgt <= rgt) - & (warehouse_table.name == sle.warehouse) - ) - ) - query = query.where(ExistsCriterion(chilren_subquery)) + if filters.get("warehouse"): + query = apply_warehouse_filter(query, sle, filters) elif warehouse_type := filters.get("warehouse_type"): query = ( query.join(warehouse_table) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index e18d4c752292..a95119736a05 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -6,11 +6,11 @@ from frappe import _ from frappe.query_builder.functions import CombineDatetime from frappe.utils import cint, flt -from pypika.terms import ExistsCriterion from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import get_stock_balance_for +from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter from erpnext.stock.utils import ( is_reposting_item_valuation_in_progress, update_included_uom_in_report, @@ -295,20 +295,7 @@ def get_stock_ledger_entries(filters, items): if filters.get(field): query = query.where(sle[field] == filters.get(field)) - if warehouse := filters.get("warehouse"): - lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - - warehouse_table = frappe.qb.DocType("Warehouse") - chilren_subquery = ( - frappe.qb.from_(warehouse_table) - .select(warehouse_table.name) - .where( - (warehouse_table.lft >= lft) - & (warehouse_table.rgt <= rgt) - & (warehouse_table.name == sle.warehouse) - ) - ) - query = query.where(ExistsCriterion(chilren_subquery)) + query = apply_warehouse_filter(query, sle, filters) return query.run(as_dict=True) From 98e7b2dd25286e4ac39f20a6c4c0ddafc6a8e3be Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 17 Oct 2022 16:21:54 +0530 Subject: [PATCH 314/898] test: lead creation and deletion restricted to dummy company (cherry picked from commit ced8d2a5376e5b95e6b798a2b3b8975dbd88f1c5) --- .../test_service_level_agreement.py | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index 4e00138906dc..472f6bc24eb9 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -15,8 +15,30 @@ class TestServiceLevelAgreement(unittest.TestCase): def setUp(self): + self.create_company() frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) - frappe.db.sql("delete from `tabLead`") + lead = frappe.qb.DocType("Lead") + frappe.qb.from_(lead).delete().where(lead.company == self.company).run() + + def create_company(self): + name = "_Test Support SLA" + company = None + if frappe.db.exists("Company", name): + company = frappe.get_doc("Company", name) + else: + company = frappe.get_doc( + { + "doctype": "Company", + "company_name": name, + "country": "India", + "default_currency": "INR", + "create_chart_of_accounts_based_on": "Standard Template", + "chart_of_accounts": "Standard", + } + ) + company = company.save() + + self.company = company.name def test_service_level_agreement(self): # Default Service Level Agreement @@ -205,7 +227,7 @@ def test_sla_application(self): # make lead with default SLA creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=1) + lead = make_lead(creation=creation, index=1, company=self.company) self.assertEqual(lead.service_level_agreement, lead_sla.name) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) @@ -233,7 +255,7 @@ def test_hold_time(self): ) creation = datetime.datetime(2020, 3, 4, 4, 0) - lead = make_lead(creation, index=2) + lead = make_lead(creation, index=2, company=self.company) frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15) lead.reload() @@ -267,7 +289,7 @@ def test_failed_sla_for_response_only(self): ) creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=1) + lead = make_lead(creation=creation, index=1, company=self.company) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) # failed with response time only @@ -294,7 +316,7 @@ def test_fulfilled_sla_for_response_only(self): # fulfilled with response time only creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=2) + lead = make_lead(creation=creation, index=2, company=self.company) self.assertEqual(lead.service_level_agreement, lead_sla.name) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) @@ -321,7 +343,7 @@ def test_service_level_agreement_filters(self): apply_sla_for_resolution=0, ) creation = datetime.datetime(2019, 3, 4, 12, 0) - lead = make_lead(creation=creation, index=4) + lead = make_lead(creation=creation, index=4, company=self.company) applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement") self.assertFalse(applied_sla) @@ -611,7 +633,7 @@ def create_custom_doctype(): return frappe.get_doc("DocType", "Test SLA on Custom Dt") -def make_lead(creation=None, index=0): +def make_lead(creation=None, index=0, company=None): return frappe.get_doc( { "doctype": "Lead", @@ -621,5 +643,6 @@ def make_lead(creation=None, index=0): "creation": creation, "service_level_agreement_creation": creation, "priority": "Medium", + "company": company, } ).insert(ignore_permissions=True) From a88ac4016a411ec5bb8404232788b657f2add01c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 13 Oct 2022 14:13:48 +0530 Subject: [PATCH 315/898] refactor: split ple creation function into two refactor create_payment_ledger_entry function into 2. one for generating ple map and one for DB entry creation (cherry picked from commit 9b50221bf05d1dec20d37dc1e3f4e9f0d4b9945f) --- erpnext/accounts/utils.py | 82 ++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 67574ca9926c..6667454c482c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1368,9 +1368,8 @@ def check_and_delete_linked_reports(report): frappe.delete_doc("Desktop Icon", icon) -def create_payment_ledger_entry( - gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0 -): +def get_payment_ledger_entries(gl_entries, cancel=0): + ple_map = [] if gl_entries: ple = None @@ -1410,44 +1409,57 @@ def get_account_type(account): dr_or_cr *= -1 dr_or_cr_account_currency *= -1 - ple = frappe.get_doc( - { - "doctype": "Payment Ledger Entry", - "posting_date": gle.posting_date, - "company": gle.company, - "account_type": account_type, - "account": gle.account, - "party_type": gle.party_type, - "party": gle.party, - "cost_center": gle.cost_center, - "finance_book": gle.finance_book, - "due_date": gle.due_date, - "voucher_type": gle.voucher_type, - "voucher_no": gle.voucher_no, - "against_voucher_type": gle.against_voucher_type - if gle.against_voucher_type - else gle.voucher_type, - "against_voucher_no": gle.against_voucher if gle.against_voucher else gle.voucher_no, - "account_currency": gle.account_currency, - "amount": dr_or_cr, - "amount_in_account_currency": dr_or_cr_account_currency, - "delinked": True if cancel else False, - "remarks": gle.remarks, - } + ple = frappe._dict( + doctype="Payment Ledger Entry", + posting_date=gle.posting_date, + company=gle.company, + account_type=account_type, + account=gle.account, + party_type=gle.party_type, + party=gle.party, + cost_center=gle.cost_center, + finance_book=gle.finance_book, + due_date=gle.due_date, + voucher_type=gle.voucher_type, + voucher_no=gle.voucher_no, + against_voucher_type=gle.against_voucher_type + if gle.against_voucher_type + else gle.voucher_type, + against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no, + account_currency=gle.account_currency, + amount=dr_or_cr, + amount_in_account_currency=dr_or_cr_account_currency, + delinked=True if cancel else False, + remarks=gle.remarks, ) dimensions_and_defaults = get_dimensions() if dimensions_and_defaults: for dimension in dimensions_and_defaults[0]: - ple.set(dimension.fieldname, gle.get(dimension.fieldname)) + ple[dimension.fieldname] = gle.get(dimension.fieldname) - if cancel: - delink_original_entry(ple) - ple.flags.ignore_permissions = 1 - ple.flags.adv_adj = adv_adj - ple.flags.from_repost = from_repost - ple.flags.update_outstanding = update_outstanding - ple.submit() + ple_map.append(ple) + return ple_map + + +def create_payment_ledger_entry( + gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0 +): + if gl_entries: + ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel) + + for entry in ple_map: + + ple = frappe.get_doc(entry) + + if cancel: + delink_original_entry(ple) + + ple.flags.ignore_permissions = 1 + ple.flags.adv_adj = adv_adj + ple.flags.from_repost = from_repost + ple.flags.update_outstanding = update_outstanding + ple.submit() def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party): From cc938fb028ca26df9f1f764664375885149d0421 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 17 Oct 2022 18:50:54 +0530 Subject: [PATCH 316/898] fix: Ignore linked purchase invoice on cancel (cherry picked from commit faadf78332a813b69ddb075820cf1693c51d0d48) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index ec861a278790..c3a9855ff448 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. super.onload(); // Ignore linked advances - this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry']; + this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice']; if(!this.frm.doc.__islocal) { // show credit_to in print format diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5dbe7ebc8633..3d74b8f139bb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1415,6 +1415,7 @@ def on_cancel(self): "Stock Ledger Entry", "Repost Item Valuation", "Payment Ledger Entry", + "Purchase Invoice", ) self.update_advance_tax_references(cancel=1) From fbb641936edb4f2f71588cf1abb61174a77942a2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 7 Oct 2022 17:23:33 +0530 Subject: [PATCH 317/898] refactor: rewrite `Purchase Order Analysis Report` queries in `QB` (cherry picked from commit a5b3f8cae9b028a9614f66cab6ba43ffc260b365) --- .../purchase_order_analysis.py | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 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 a5c464910de5..e10c0e2fccf8 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -6,6 +6,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull from frappe.utils import date_diff, flt, getdate @@ -16,9 +17,7 @@ def execute(filters=None): validate_filters(filters) columns = get_columns(filters) - conditions = get_conditions(filters) - - data = get_data(conditions, filters) + data = get_data(filters) if not data: return [], [], None, [] @@ -37,60 +36,61 @@ def validate_filters(filters): frappe.throw(_("To Date cannot be before From Date.")) -def get_conditions(filters): - conditions = "" - if filters.get("from_date") and filters.get("to_date"): - conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" +def get_data(filters): + po = frappe.qb.DocType("Purchase Order") + po_item = frappe.qb.DocType("Purchase Order Item") + pi_item = frappe.qb.DocType("Purchase Invoice Item") + + query = ( + frappe.qb.from_(po) + .from_(po_item) + .left_join(pi_item) + .on(pi_item.po_detail == po_item.name) + .select( + po.transaction_date.as_("date"), + po_item.schedule_date.as_("required_date"), + po_item.project, + po.name.as_("purchase_order"), + po.status, + po.supplier, + po_item.item_code, + 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"), + 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"), + (po_item.base_amount - (po_item.billed_amt * IfNull(po.conversion_rate, 1))).as_( + "pending_amount" + ), + po.set_warehouse.as_("warehouse"), + po.company, + po_item.name, + ) + .where( + (po_item.parent == po.name) & (po.status.notin(("Stopped", "Closed"))) & (po.docstatus == 1) + ) + .groupby(po_item.name) + .orderby(po.transaction_date) + ) - for field in ["company", "name"]: + for field in ("company", "name"): if filters.get(field): - conditions += f" and po.{field} = %({field})s" + query = query.where(po[field] == filters.get(field)) + + if filters.get("from_date") and filters.get("to_date"): + query = query.where( + po.transaction_date.between(filters.get("from_date"), filters.get("to_date")) + ) if filters.get("status"): - conditions += " and po.status in %(status)s" + query = query.where(po.status.isin(filters.get("status"))) if filters.get("project"): - conditions += " and poi.project = %(project)s" - - return conditions - - -def get_data(conditions, filters): - data = frappe.db.sql( - """ - SELECT - po.transaction_date as date, - poi.schedule_date as required_date, - poi.project, - po.name as purchase_order, - po.status, po.supplier, poi.item_code, - poi.qty, poi.received_qty, - (poi.qty - poi.received_qty) AS pending_qty, - IFNULL(pii.qty, 0) as billed_qty, - poi.base_amount as amount, - (poi.received_qty * poi.base_rate) as received_qty_amount, - (poi.billed_amt * IFNULL(po.conversion_rate, 1)) as billed_amount, - (poi.base_amount - (poi.billed_amt * IFNULL(po.conversion_rate, 1))) as pending_amount, - po.set_warehouse as warehouse, - po.company, poi.name - FROM - `tabPurchase Order` po, - `tabPurchase Order Item` poi - LEFT JOIN `tabPurchase Invoice Item` pii - ON pii.po_detail = poi.name - WHERE - poi.parent = po.name - and po.status not in ('Stopped', 'Closed') - and po.docstatus = 1 - {0} - GROUP BY poi.name - ORDER BY po.transaction_date ASC - """.format( - conditions - ), - filters, - as_dict=1, - ) + query = query.where(po_item.project == filters.get("project")) + + data = query.run(as_dict=True) return data From 1fbbaa8fcf6105159b9c7b993c3a0eb859ba5bb4 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 7 Oct 2022 18:01:09 +0530 Subject: [PATCH 318/898] refactor: rewrite `Supplier Quotation Comparison Report` queries in `QB` (cherry picked from commit a14b9c7baceb45f0cff43c9b5717896d37d13137) --- .../supplier_quotation_comparison.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 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 3013b6d16072..a72829096181 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -16,8 +16,7 @@ def execute(filters=None): return [], [] columns = get_columns(filters) - conditions = get_conditions(filters) - supplier_quotation_data = get_data(filters, conditions) + supplier_quotation_data = get_data(filters) data, chart_data = prepare_data(supplier_quotation_data, filters) message = get_message() @@ -25,50 +24,51 @@ def execute(filters=None): return columns, data, message, chart_data -def get_conditions(filters): - conditions = "" +def get_data(filters): + sq = frappe.qb.DocType("Supplier Quotation") + sq_item = frappe.qb.DocType("Supplier Quotation Item") + + query = ( + frappe.qb.from_(sq_item) + .from_(sq) + .select( + sq_item.parent, + sq_item.item_code, + sq_item.qty, + sq_item.stock_qty, + sq_item.amount, + sq_item.uom, + sq_item.stock_uom, + sq_item.request_for_quotation, + sq_item.lead_time_days, + sq.supplier.as_("supplier_name"), + sq.valid_till, + ) + .where( + (sq_item.parent == sq.name) + & (sq_item.docstatus < 2) + & (sq.company == filters.get("company")) + & (sq.transaction_date.between(filters.get("from_date"), filters.get("to_date"))) + ) + .orderby(sq.transaction_date, sq_item.item_code) + ) + if filters.get("item_code"): - conditions += " AND sqi.item_code = %(item_code)s" + query = query.where(sq_item.item_code == filters.get("item_code")) if filters.get("supplier_quotation"): - conditions += " AND sqi.parent in %(supplier_quotation)s" + query = query.where(sq_item.parent.isin(filters.get("supplier_quotation"))) if filters.get("request_for_quotation"): - conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s" + query = query.where(sq_item.request_for_quotation == filters.get("request_for_quotation")) if filters.get("supplier"): - conditions += " AND sq.supplier in %(supplier)s" + query = query.where(sq.supplier.isin(filters.get("supplier"))) if not filters.get("include_expired"): - conditions += " AND sq.status != 'Expired'" - - return conditions - - -def get_data(filters, conditions): - supplier_quotation_data = frappe.db.sql( - """ - SELECT - sqi.parent, sqi.item_code, - sqi.qty, sqi.stock_qty, sqi.amount, - sqi.uom, sqi.stock_uom, - sqi.request_for_quotation, - sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till - FROM - `tabSupplier Quotation Item` sqi, - `tabSupplier Quotation` sq - WHERE - sqi.parent = sq.name - AND sqi.docstatus < 2 - AND sq.company = %(company)s - AND sq.transaction_date between %(from_date)s and %(to_date)s - {0} - order by sq.transaction_date, sqi.item_code""".format( - conditions - ), - filters, - as_dict=1, - ) + query = query.where(sq.status != "Expired") + + supplier_quotation_data = query.run(as_dict=True) return supplier_quotation_data From 2191f8a48829315392868c700c21824e73950768 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 7 Oct 2022 18:13:33 +0530 Subject: [PATCH 319/898] refactor: rewrite `Procurement Tracker Report` queries in `QB` (cherry picked from commit e78a70699423eb2552e77b62b3af33e5e22290da) --- .../procurement_tracker.py | 151 +++++++++--------- 1 file changed, 73 insertions(+), 78 deletions(-) diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py index d70ac46ce33a..71019e803775 100644 --- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py @@ -127,32 +127,27 @@ def get_columns(filters): return columns -def get_conditions(filters): - conditions = "" - +def apply_filters_on_query(filters, parent, child, query): if filters.get("company"): - conditions += " AND parent.company=%s" % frappe.db.escape(filters.get("company")) + query = query.where(parent.company == filters.get("company")) if filters.get("cost_center") or filters.get("project"): - conditions += """ - AND (child.`cost_center`=%s OR child.`project`=%s) - """ % ( - frappe.db.escape(filters.get("cost_center")), - frappe.db.escape(filters.get("project")), + query = query.where( + (child.cost_center == filters.get("cost_center")) | (child.project == filters.get("project")) ) if filters.get("from_date"): - conditions += " AND parent.transaction_date>='%s'" % filters.get("from_date") + query = query.where(parent.transaction_date >= filters.get("from_date")) if filters.get("to_date"): - conditions += " AND parent.transaction_date<='%s'" % filters.get("to_date") - return conditions + query = query.where(parent.transaction_date <= filters.get("to_date")) + + return query def get_data(filters): - conditions = get_conditions(filters) - purchase_order_entry = get_po_entries(conditions) - mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions) + purchase_order_entry = get_po_entries(filters) + mr_records, procurement_record_against_mr = get_mapped_mr_details(filters) pr_records = get_mapped_pr_records() pi_records = get_mapped_pi_records() @@ -187,11 +182,15 @@ def get_data(filters): return procurement_record -def get_mapped_mr_details(conditions): +def get_mapped_mr_details(filters): mr_records = {} - mr_details = frappe.db.sql( - """ - SELECT + parent = frappe.qb.DocType("Material Request") + child = frappe.qb.DocType("Material Request Item") + + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( parent.transaction_date, parent.per_ordered, parent.owner, @@ -203,18 +202,13 @@ def get_mapped_mr_details(conditions): child.uom, parent.status, child.project, - child.cost_center - FROM `tabMaterial Request` parent, `tabMaterial Request Item` child - WHERE - parent.per_ordered>=0 - AND parent.name=child.parent - AND parent.docstatus=1 - {conditions} - """.format( - conditions=conditions - ), - as_dict=1, - ) # nosec + child.cost_center, + ) + .where((parent.per_ordered >= 0) & (parent.name == child.parent) & (parent.docstatus == 1)) + ) + query = apply_filters_on_query(filters, parent, child, query) + + mr_details = query.run(as_dict=True) procurement_record_against_mr = [] for record in mr_details: @@ -241,46 +235,49 @@ def get_mapped_mr_details(conditions): def get_mapped_pi_records(): - return frappe._dict( - frappe.db.sql( - """ - SELECT - pi_item.po_detail, - pi_item.base_amount - FROM `tabPurchase Invoice Item` as pi_item - INNER JOIN `tabPurchase Order` as po - ON pi_item.`purchase_order` = po.`name` - WHERE - pi_item.docstatus = 1 - AND po.status not in ('Closed','Completed','Cancelled') - AND pi_item.po_detail IS NOT NULL - """ + po = frappe.qb.DocType("Purchase Order") + pi_item = frappe.qb.DocType("Purchase Invoice Item") + pi_records = ( + frappe.qb.from_(pi_item) + .inner_join(po) + .on(pi_item.purchase_order == po.name) + .select(pi_item.po_detail, pi_item.base_amount) + .where( + (pi_item.docstatus == 1) + & (po.status.notin(("Closed", "Completed", "Cancelled"))) + & (pi_item.po_detail.isnotnull()) ) - ) + ).run() + + return frappe._dict(pi_records) def get_mapped_pr_records(): - return frappe._dict( - frappe.db.sql( - """ - SELECT - pr_item.purchase_order_item, - pr.posting_date - FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - WHERE - pr.docstatus=1 - AND pr.name=pr_item.parent - AND pr_item.purchase_order_item IS NOT NULL - AND pr.status not in ('Closed','Completed','Cancelled') - """ + pr = frappe.qb.DocType("Purchase Receipt") + pr_item = frappe.qb.DocType("Purchase Receipt Item") + pr_records = ( + frappe.qb.from_(pr) + .from_(pr_item) + .select(pr_item.purchase_order_item, pr.posting_date) + .where( + (pr.docstatus == 1) + & (pr.name == pr_item.parent) + & (pr_item.purchase_order_item.isnotnull()) + & (pr.status.notin(("Closed", "Completed", "Cancelled"))) ) - ) + ).run() + + return frappe._dict(pr_records) -def get_po_entries(conditions): - return frappe.db.sql( - """ - SELECT +def get_po_entries(filters): + parent = frappe.qb.DocType("Purchase Order") + child = frappe.qb.DocType("Purchase Order Item") + + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( child.name, child.parent, child.cost_center, @@ -297,17 +294,15 @@ def get_po_entries(conditions): parent.transaction_date, parent.supplier, parent.status, - parent.owner - FROM `tabPurchase Order` parent, `tabPurchase Order Item` child - WHERE - parent.docstatus = 1 - AND parent.name = child.parent - AND parent.status not in ('Closed','Completed','Cancelled') - {conditions} - GROUP BY - parent.name, child.item_code - """.format( - conditions=conditions - ), - as_dict=1, - ) # nosec + parent.owner, + ) + .where( + (parent.docstatus == 1) + & (parent.name == child.parent) + & (parent.status.notin(("Closed", "Completed", "Cancelled"))) + ) + .groupby(parent.name, child.item_code) + ) + query = apply_filters_on_query(filters, parent, child, query) + + return query.run(as_dict=True) From 5a85c2d661e9ff817b945fe58977ca2e1dea0e42 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 23 Sep 2022 13:52:26 +0530 Subject: [PATCH 320/898] refactor: rewrite `Batch Item Expiry Status Report` queries in `QB` (cherry picked from commit 2c9d9577e3fe78b8b5850625e3ee3771a36121cf) --- .../batch_item_expiry_status.py | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py index 3d9b04619777..ef7d6e6816c1 100644 --- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py +++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull from frappe.utils import cint, getdate @@ -54,31 +55,28 @@ def get_columns(filters): return columns -def get_conditions(filters): - conditions = "" +def get_stock_ledger_entries(filters): if not filters.get("from_date"): frappe.throw(_("'From Date' is required")) - if filters.get("to_date"): - conditions += " and posting_date <= '%s'" % filters["to_date"] - else: + if not filters.get("to_date"): frappe.throw(_("'To Date' is required")) - return conditions - - -def get_stock_ledger_entries(filters): - conditions = get_conditions(filters) - return frappe.db.sql( - """select item_code, batch_no, warehouse, - posting_date, actual_qty - from `tabStock Ledger Entry` - where is_cancelled = 0 - and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" - % conditions, - as_dict=1, + sle = frappe.qb.DocType("Stock Ledger Entry") + query = ( + frappe.qb.from_(sle) + .select(sle.item_code, sle.batch_no, sle.warehouse, sle.posting_date, sle.actual_qty) + .where( + (sle.is_cancelled == 0) + & (sle.docstatus < 2) + & (IfNull(sle.batch_no, "") != "") + & (sle.posting_date <= filters["to_date"]) + ) + .orderby(sle.item_code, sle.warehouse) ) + return query.run(as_dict=True) + def get_item_warehouse_batch_map(filters, float_precision): sle = get_stock_ledger_entries(filters) @@ -112,7 +110,7 @@ def get_item_warehouse_batch_map(filters, float_precision): def get_item_details(filters): item_map = {} - for d in frappe.db.sql("select name, item_name, description from tabItem", as_dict=1): + for d in (frappe.qb.from_("Item").select("name", "item_name", "description")).run(as_dict=True): item_map.setdefault(d.name, d) return item_map From 720370537f366459c753d929a8523feb948475bd Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 23 Sep 2022 14:10:07 +0530 Subject: [PATCH 321/898] refactor: rewrite `Batch-Wise Balance History Report` queries in `QB` (cherry picked from commit 1c1f991d2f2d798dba51631abfdd6579e4671b1b) # Conflicts: # erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py --- .../batch_wise_balance_history.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 291c6b5dab5b..6c2f6c113822 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cint, flt, getdate from pypika import functions as fn @@ -72,6 +73,7 @@ def get_stock_ledger_entries(filters): if not filters.get("from_date"): frappe.throw(_("'From Date' is required")) +<<<<<<< HEAD sle = frappe.qb.DocType("Stock Ledger Entry") query = ( frappe.qb.from_(sle) @@ -101,6 +103,29 @@ def get_stock_ledger_entries(filters): for field in ["item_code", "batch_no", "company"]: if filters.get(field): query = query.where(sle[field] == filters.get(field)) +======= + if not filters.get("to_date"): + frappe.throw(_("'To Date' is required")) + + sle = frappe.qb.DocType("Stock Ledger Entry") + query = ( + frappe.qb.from_(sle) + .select( + sle.item_code, + sle.batch_no, + sle.warehouse, + sle.posting_date, + Sum(sle.actual_qty).as_("actual_qty"), + ) + .where((sle.is_cancelled == 0) & (sle.docstatus < 2) & (IfNull(sle.batch_no, "") != "")) + .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) + .orderby(sle.item_code, sle.warehouse) + ) + + for field in ("item_code", "warehouse", "batch_no", "company"): + if filters.get(field): + query = query.where(sle[field] == filters[field]) +>>>>>>> 1c1f991d2f (refactor: rewrite `Batch-Wise Balance History Report` queries in `QB`) return query.run(as_dict=True) @@ -136,7 +161,9 @@ def get_item_warehouse_batch_map(filters, float_precision): def get_item_details(filters): item_map = {} - for d in frappe.db.sql("select name, item_name, description, stock_uom from tabItem", as_dict=1): + for d in (frappe.qb.from_("Item").select("name", "item_name", "description", "stock_uom")).run( + as_dict=1 + ): item_map.setdefault(d.name, d) return item_map From e62fdf4af0fd2d859af4d6dfed0e33fe88359261 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 23 Sep 2022 15:47:26 +0530 Subject: [PATCH 322/898] refactor: rewrite `Delayed Item Report` queries in `QB` (cherry picked from commit bb59346651fb4cd99fb1a23655a9b0a72d682f9f) --- .../delayed_item_report.py | 82 +++++++++---------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/erpnext/stock/report/delayed_item_report/delayed_item_report.py b/erpnext/stock/report/delayed_item_report/delayed_item_report.py index 9df24d65596a..546a834da8ca 100644 --- a/erpnext/stock/report/delayed_item_report/delayed_item_report.py +++ b/erpnext/stock/report/delayed_item_report/delayed_item_report.py @@ -21,56 +21,54 @@ def run(self): return self.get_columns(), self.get_data() or [] def get_data(self, consolidated=False): - conditions = "" - doctype = self.filters.get("based_on") - child_doc = "%s Item" % doctype + sales_order_field = "sales_order" if doctype == "Sales Invoice" else "against_sales_order" + + parent = frappe.qb.DocType(doctype) + child = frappe.qb.DocType(f"{doctype} Item") + + query = ( + frappe.qb.from_(child) + .from_(parent) + .select( + child.item_code, + child.item_name, + child.item_group, + child.qty, + child.rate, + child.amount, + child.so_detail, + child[sales_order_field].as_("sales_order"), + parent.shipping_address_name, + parent.po_no, + parent.customer, + parent.posting_date, + parent.name, + parent.grand_total, + ) + .where( + (child.parent == parent.name) + & (parent.docstatus == 1) + & (parent.posting_date.between(self.filters.get("from_date"), self.filters.get("to_date"))) + & (child[sales_order_field].notnull()) + & (child[sales_order_field] != "") + ) + ) if doctype == "Sales Invoice": - conditions = " and `tabSales Invoice`.update_stock = 1 and `tabSales Invoice`.is_pos = 0" + query = query.where((parent.update_stock == 1) & (parent.is_pos == 0)) if self.filters.get("item_group"): - conditions += " and `tab%s`.item_group = %s" % ( - child_doc, - frappe.db.escape(self.filters.get("item_group")), - ) - - for field in ["customer", "customer_group", "company"]: - if self.filters.get(field): - conditions += " and `tab%s`.%s = %s" % ( - doctype, - field, - frappe.db.escape(self.filters.get(field)), - ) - - sales_order_field = "against_sales_order" - if doctype == "Sales Invoice": - sales_order_field = "sales_order" + query = query.where(child.item_group == self.filters.get("item_group")) if self.filters.get("sales_order"): - conditions = " and `tab%s`.%s = '%s'" % ( - child_doc, - sales_order_field, - self.filters.get("sales_order"), - ) + query = query.where(child[sales_order_field] == self.filters.get("sales_order")) - self.transactions = frappe.db.sql( - """ SELECT `tab{child_doc}`.item_code, `tab{child_doc}`.item_name, - `tab{child_doc}`.item_group, `tab{child_doc}`.qty, `tab{child_doc}`.rate, `tab{child_doc}`.amount, - `tab{child_doc}`.so_detail, `tab{child_doc}`.{so_field} as sales_order, - `tab{doctype}`.shipping_address_name, `tab{doctype}`.po_no, `tab{doctype}`.customer, - `tab{doctype}`.posting_date, `tab{doctype}`.name, `tab{doctype}`.grand_total - FROM `tab{child_doc}`, `tab{doctype}` - WHERE - `tab{child_doc}`.parent = `tab{doctype}`.name and `tab{doctype}`.docstatus = 1 and - `tab{doctype}`.posting_date between %(from_date)s and %(to_date)s and - `tab{child_doc}`.{so_field} is not null and `tab{child_doc}`.{so_field} != '' {cond} - """.format( - cond=conditions, doctype=doctype, child_doc=child_doc, so_field=sales_order_field - ), - {"from_date": self.filters.get("from_date"), "to_date": self.filters.get("to_date")}, - as_dict=1, - ) + for field in ("customer", "customer_group", "company"): + if self.filters.get(field): + query = query.where(parent[field] == self.filters.get(field)) + + self.transactions = query.run(as_dict=True) if self.transactions: self.filter_transactions_data(consolidated) From 907a41a8dae77cbfe4598bdd6a89d47cefdf85e0 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 12:46:52 +0530 Subject: [PATCH 323/898] refactor: rewrite `Item Prices Report` queries in `QB` (cherry picked from commit e312d17eae2a957b71c3a11480cad1d5dd848077) --- .../stock/report/item_prices/item_prices.py | 169 ++++++++++-------- 1 file changed, 99 insertions(+), 70 deletions(-) diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py index 87f1a42e2b2a..ab47b4a8b991 100644 --- a/erpnext/stock/report/item_prices/item_prices.py +++ b/erpnext/stock/report/item_prices/item_prices.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import flt @@ -12,8 +13,7 @@ def execute(filters=None): filters = {} columns = get_columns(filters) - conditions = get_condition(filters) - item_map = get_item_details(conditions) + item_map = get_item_details(filters) pl = get_price_list() last_purchase_rate = get_last_purchase_rate() bom_rate = get_item_bom_rate() @@ -63,18 +63,24 @@ def get_columns(filters): return columns -def get_item_details(conditions): +def get_item_details(filters): """returns all items details""" item_map = {} - for i in frappe.db.sql( - """select name, item_group, item_name, description, - brand, stock_uom from tabItem %s - order by item_code, item_group""" - % (conditions), - as_dict=1, - ): + item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(item) + .select(item.name, item.item_group, item.item_name, item.description, item.brand, item.stock_uom) + .orderby(item.item_code, item.item_group) + ) + + if filters.get("items") == "Enabled Items only": + query = query.where(item.disabled == 0) + elif filters.get("items") == "Disabled Items only": + query = query.where(item.disabled == 1) + + for i in query.run(as_dict=True): item_map.setdefault(i.name, i) return item_map @@ -85,19 +91,38 @@ def get_price_list(): rate = {} - price_list = frappe.db.sql( - """select ip.item_code, ip.buying, ip.selling, - concat(ifnull(cu.symbol,ip.currency), " ", round(ip.price_list_rate,2), " - ", ip.price_list) as price - from `tabItem Price` ip, `tabPrice List` pl, `tabCurrency` cu - where ip.price_list=pl.name and pl.currency=cu.name and pl.enabled=1""", - as_dict=1, - ) + ip = frappe.qb.DocType("Item Price") + pl = frappe.qb.DocType("Price List") + cu = frappe.qb.DocType("Currency") + + price_list = ( + frappe.qb.from_(ip) + .from_(pl) + .from_(cu) + .select( + ip.item_code, + ip.buying, + ip.selling, + (IfNull(cu.symbol, ip.currency)).as_("currency"), + ip.price_list_rate, + ip.price_list, + ) + .where((ip.price_list == pl.name) & (pl.currency == cu.name) & (pl.enabled == 1)) + ).run(as_dict=True) - for j in price_list: - if j.price: - rate.setdefault(j.item_code, {}).setdefault("Buying" if j.buying else "Selling", []).append( - j.price + for d in price_list: + d.update( + {"price": "{0} {1} - {2}".format(d.currency, round(d.price_list_rate, 2), d.price_list)} + ) + d.pop("currency") + d.pop("price_list_rate") + d.pop("price_list") + + if d.price: + rate.setdefault(d.item_code, {}).setdefault("Buying" if d.buying else "Selling", []).append( + d.price ) + item_rate_map = {} for item in rate: @@ -112,30 +137,39 @@ def get_price_list(): def get_last_purchase_rate(): item_last_purchase_rate_map = {} - query = """select * from ( - (select - po_item.item_code, - po.transaction_date as posting_date, - po_item.base_rate - from `tabPurchase Order` po, `tabPurchase Order Item` po_item - where po.name = po_item.parent and po.docstatus = 1) - union - (select - pr_item.item_code, - pr.posting_date, - pr_item.base_rate - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - where pr.name = pr_item.parent and pr.docstatus = 1) - union - (select - pi_item.item_code, - pi.posting_date, - pi_item.base_rate - from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item - where pi.name = pi_item.parent and pi.docstatus = 1 and pi.update_stock = 1) - ) result order by result.item_code asc, result.posting_date asc""" - - for d in frappe.db.sql(query, as_dict=1): + po = frappe.qb.DocType("Purchase Order") + pr = frappe.qb.DocType("Purchase Receipt") + pi = frappe.qb.DocType("Purchase Invoice") + po_item = frappe.qb.DocType("Purchase Order Item") + pr_item = frappe.qb.DocType("Purchase Receipt Item") + pi_item = frappe.qb.DocType("Purchase Invoice Item") + + query = ( + frappe.qb.from_( + ( + frappe.qb.from_(po) + .from_(po_item) + .select(po_item.item_code, po.transaction_date.as_("posting_date"), po_item.base_rate) + .where((po.name == po_item.parent) & (po.docstatus == 1)) + ) + + ( + frappe.qb.from_(pr) + .from_(pr_item) + .select(pr_item.item_code, pr.posting_date, pr_item.base_rate) + .where((pr.name == pr_item.parent) & (pr.docstatus == 1)) + ) + + ( + frappe.qb.from_(pi) + .from_(pi_item) + .select(pi_item.item_code, pi.posting_date, pi_item.base_rate) + .where((pi.name == pi_item.parent) & (pi.docstatus == 1) & (pi.update_stock == 1)) + ) + ) + .select("*") + .orderby("item_code", "posting_date") + ) + + for d in query.run(as_dict=True): item_last_purchase_rate_map[d.item_code] = d.base_rate return item_last_purchase_rate_map @@ -146,12 +180,15 @@ def get_item_bom_rate(): item_bom_map = {} - for b in frappe.db.sql( - """select item, (total_cost/quantity) as bom_rate - from `tabBOM` where is_active=1 and is_default=1""", - as_dict=1, - ): - item_bom_map.setdefault(b.item, flt(b.bom_rate)) + bom = frappe.qb.DocType("BOM") + bom_data = ( + frappe.qb.from_(bom) + .select(bom.item, (bom.total_cost / bom.quantity).as_("bom_rate")) + .where((bom.is_active == 1) & (bom.is_default == 1)) + ).run(as_dict=True) + + for d in bom_data: + item_bom_map.setdefault(d.item, flt(d.bom_rate)) return item_bom_map @@ -161,25 +198,17 @@ def get_valuation_rate(): item_val_rate_map = {} - for d in frappe.db.sql( - """select item_code, - sum(actual_qty*valuation_rate)/sum(actual_qty) as val_rate - from tabBin where actual_qty > 0 group by item_code""", - as_dict=1, - ): + bin = frappe.qb.DocType("Bin") + bin_data = ( + frappe.qb.from_(bin) + .select( + bin.item_code, Sum(bin.actual_qty * bin.valuation_rate) / Sum(bin.actual_qty).as_("val_rate") + ) + .where(bin.actual_qty > 0) + .groupby(bin.item_code) + ).run(as_dict=True) + + for d in bin_data: item_val_rate_map.setdefault(d.item_code, d.val_rate) return item_val_rate_map - - -def get_condition(filters): - """Get Filter Items""" - - if filters.get("items") == "Enabled Items only": - conditions = " where disabled=0 " - elif filters.get("items") == "Disabled Items only": - conditions = " where disabled=1 " - else: - conditions = "" - - return conditions From 6903fb68d3cc15dcc490ce8d13497925ffdc6fa2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 6 Oct 2022 13:35:11 +0530 Subject: [PATCH 324/898] refactor: rewrite `Stock Projected Qty Report` queries in `QB` (cherry picked from commit c18f13a45ba6498245ba0429e61205a0df2434a6) --- .../stock_projected_qty.py | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index 49e797d6a30d..f477d8f08fdf 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -5,6 +5,7 @@ import frappe from frappe import _ from frappe.utils import flt, today +from pypika.terms import ExistsCriterion from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty from erpnext.stock.utils import ( @@ -218,10 +219,26 @@ def get_columns(): def get_bin_list(filters): - conditions = [] + bin = frappe.qb.DocType("Bin") + query = ( + frappe.qb.from_(bin) + .select( + bin.item_code, + bin.warehouse, + bin.actual_qty, + bin.planned_qty, + bin.indented_qty, + bin.ordered_qty, + bin.reserved_qty, + bin.reserved_qty_for_production, + bin.reserved_qty_for_sub_contract, + bin.projected_qty, + ) + .orderby(bin.item_code, bin.warehouse) + ) if filters.item_code: - conditions.append("item_code = '%s' " % filters.item_code) + query = query.where(bin.item_code == filters.item_code) if filters.warehouse: warehouse_details = frappe.db.get_value( @@ -229,21 +246,20 @@ def get_bin_list(filters): ) if warehouse_details: - conditions.append( - " exists (select name from `tabWarehouse` wh \ - where wh.lft >= %s and wh.rgt <= %s and bin.warehouse = wh.name)" - % (warehouse_details.lft, warehouse_details.rgt) + wh = frappe.qb.DocType("Warehouse") + query = query.where( + ExistsCriterion( + frappe.qb.from_(wh) + .select(wh.name) + .where( + (wh.lft >= warehouse_details.lft) + & (wh.rgt <= warehouse_details.rgt) + & (bin.warehouse == wh.name) + ) + ) ) - bin_list = frappe.db.sql( - """select item_code, warehouse, actual_qty, planned_qty, indented_qty, - ordered_qty, reserved_qty, reserved_qty_for_production, reserved_qty_for_sub_contract, projected_qty - from tabBin bin {conditions} order by item_code, warehouse - """.format( - conditions=" where " + " and ".join(conditions) if conditions else "" - ), - as_dict=1, - ) + bin_list = query.run(as_dict=True) return bin_list @@ -251,45 +267,43 @@ def get_bin_list(filters): def get_item_map(item_code, include_uom): """Optimization: get only the item doc and re_order_levels table""" - condition = "" + bin = frappe.qb.DocType("Bin") + item = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(item) + .select(item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom) + .where( + (item.is_stock_item == 1) + & (item.disabled == 0) + & ( + (item.end_of_life > today()) | (item.end_of_life.isnull()) | (item.end_of_life == "0000-00-00") + ) + & (ExistsCriterion(frappe.qb.from_(bin).select(bin.name).where(bin.item_code == item.name))) + ) + ) + if item_code: - condition = "and item_code = {0}".format(frappe.db.escape(item_code, percent=False)) + query = query.where(item.item_code == item_code) - cf_field = cf_join = "" if include_uom: - cf_field = ", ucd.conversion_factor" - cf_join = ( - "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%(include_uom)s" - ) + ucd = frappe.qb.DocType("UOM Conversion Detail") + query = query.left_join(ucd).on((ucd.parent == item.name) & (ucd.uom == include_uom)) - items = frappe.db.sql( - """ - select item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom{cf_field} - from `tabItem` item - {cf_join} - where item.is_stock_item = 1 - and item.disabled=0 - {condition} - and (item.end_of_life > %(today)s or item.end_of_life is null or item.end_of_life='0000-00-00') - and exists (select name from `tabBin` bin where bin.item_code=item.name)""".format( - cf_field=cf_field, cf_join=cf_join, condition=condition - ), - {"today": today(), "include_uom": include_uom}, - as_dict=True, - ) + items = query.run(as_dict=True) + + ir = frappe.qb.DocType("Item Reorder") + query = frappe.qb.from_(ir).select("*") - condition = "" if item_code: - condition = "where parent={0}".format(frappe.db.escape(item_code, percent=False)) + query = query.where(ir.parent == item_code) reorder_levels = frappe._dict() - for ir in frappe.db.sql( - """select * from `tabItem Reorder` {condition}""".format(condition=condition), as_dict=1 - ): - if ir.parent not in reorder_levels: - reorder_levels[ir.parent] = [] + for d in query.run(as_dict=True): + if d.parent not in reorder_levels: + reorder_levels[d.parent] = [] - reorder_levels[ir.parent].append(ir) + reorder_levels[d.parent].append(d) item_map = frappe._dict() for item in items: From 8af90b2421b4627f52e7e9a13efd1dd0047b7524 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 6 Oct 2022 17:19:29 +0530 Subject: [PATCH 325/898] refactor: rewrite `Supplier-Wise Sales Analytics Report` queries in `QB` (cherry picked from commit d3c073dc25c6b86abef3f96560a5ed19d4c2f42a) --- .../supplier_wise_sales_analytics.py | 117 ++++++++++++------ 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py index 5430fe6969db..8c76908cc3c7 100644 --- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull from frappe.utils import flt @@ -70,31 +71,33 @@ def get_columns(filters): return columns -def get_conditions(filters): - conditions = "" - values = [] +def get_consumed_details(filters): + item = frappe.qb.DocType("Item") + sle = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(sle) + .from_(item) + .select( + sle.item_code, + item.item_name, + item.description, + item.stock_uom, + sle.actual_qty, + sle.stock_value_difference, + sle.voucher_no, + sle.voucher_type, + ) + .where((sle.is_cancelled == 0) & (sle.item_code == item.name) & (sle.actual_qty < 0)) + ) if filters.get("from_date") and filters.get("to_date"): - conditions = "and sle.posting_date>=%s and sle.posting_date<=%s" - values = [filters.get("from_date"), filters.get("to_date")] - - return conditions, values + query = query.where( + (sle.posting_date >= filters.get("from_date")) & (sle.posting_date <= filters.get("to_date")) + ) - -def get_consumed_details(filters): - conditions, values = get_conditions(filters) consumed_details = {} - - for d in frappe.db.sql( - """select sle.item_code, i.item_name, i.description, - i.stock_uom, sle.actual_qty, sle.stock_value_difference, - sle.voucher_no, sle.voucher_type - from `tabStock Ledger Entry` sle, `tabItem` i - where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" - % conditions, - values, - as_dict=1, - ): + for d in query.run(as_dict=True): consumed_details.setdefault(d.item_code, []).append(d) return consumed_details @@ -104,24 +107,54 @@ def get_suppliers_details(filters): item_supplier_map = {} supplier = filters.get("supplier") - for d in frappe.db.sql( - """select pr.supplier, pri.item_code from - `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri - where pr.name=pri.parent and pr.docstatus=1 and - pri.item_code=(select name from `tabItem` where - is_stock_item=1 and name=pri.item_code)""", - as_dict=1, - ): + item = frappe.qb.DocType("Item") + pr = frappe.qb.DocType("Purchase Receipt") + pr_item = frappe.qb.DocType("Purchase Receipt Item") + + query = ( + frappe.qb.from_(pr) + .from_(pr_item) + .select(pr.supplier, pr_item.item_code) + .where( + (pr.name == pr_item.parent) + & (pr.docstatus == 1) + & ( + pr_item.item_code + == ( + frappe.qb.from_(item) + .select(item.name) + .where((item.is_stock_item == 1) & (item.name == pr_item.item_code)) + ) + ) + ) + ) + + for d in query.run(as_dict=True): item_supplier_map.setdefault(d.item_code, []).append(d.supplier) - for d in frappe.db.sql( - """select pr.supplier, pri.item_code from - `tabPurchase Invoice` pr, `tabPurchase Invoice Item` pri - where pr.name=pri.parent and pr.docstatus=1 and - ifnull(pr.update_stock, 0) = 1 and pri.item_code=(select name from `tabItem` - where is_stock_item=1 and name=pri.item_code)""", - as_dict=1, - ): + pi = frappe.qb.DocType("Purchase Invoice") + pi_item = frappe.qb.DocType("Purchase Invoice Item") + + query = ( + frappe.qb.from_(pi) + .from_(pi_item) + .select(pi.supplier, pi_item.item_code) + .where( + (pi.name == pi_item.parent) + & (pi.docstatus == 1) + & (IfNull(pi.update_stock, 0) == 1) + & ( + pi_item.item_code + == ( + frappe.qb.from_(item) + .select(item.name) + .where((item.is_stock_item == 1) & (item.name == pi_item.item_code)) + ) + ) + ) + ) + + for d in query.run(as_dict=True): if d.item_code not in item_supplier_map: item_supplier_map.setdefault(d.item_code, []).append(d.supplier) @@ -138,7 +171,11 @@ def get_suppliers_details(filters): def get_material_transfer_vouchers(): - return frappe.db.sql_list( - """select name from `tabStock Entry` where - purpose='Material Transfer' and docstatus=1""" + se = frappe.qb.DocType("Stock Entry") + query = ( + frappe.qb.from_(se) + .select(se.name) + .where((se.purpose == "Material Transfer") & (se.docstatus == 1)) ) + + return [r[0] for r in query.run()] From 362bf5fc199f8036b362c566fb76ce6bc48ed9c7 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 7 Oct 2022 09:59:28 +0530 Subject: [PATCH 326/898] refactor: rewrite `Total Stock Summary Report` queries in `QB` (cherry picked from commit 7c759b193ca957d039165209c9e9237bf93fd5e6) --- .../total_stock_summary.py | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py index 21529da2a12d..c3155bd1a5eb 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py @@ -4,60 +4,58 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Sum def execute(filters=None): if not filters: filters = {} - columns = get_columns() + columns = get_columns(filters) stock = get_total_stock(filters) return columns, stock -def get_columns(): +def get_columns(filters): columns = [ - _("Company") + ":Link/Company:250", - _("Warehouse") + ":Link/Warehouse:150", _("Item") + ":Link/Item:150", _("Description") + "::300", _("Current Qty") + ":Float:100", ] + if filters.get("group_by") == "Warehouse": + columns.insert(0, _("Warehouse") + ":Link/Warehouse:150") + else: + columns.insert(0, _("Company") + ":Link/Company:250") + return columns def get_total_stock(filters): - conditions = "" - columns = "" + bin = frappe.qb.DocType("Bin") + item = frappe.qb.DocType("Item") + wh = frappe.qb.DocType("Warehouse") + + query = ( + frappe.qb.from_(bin) + .inner_join(item) + .on(bin.item_code == item.item_code) + .inner_join(wh) + .on(wh.name == bin.warehouse) + .where(bin.actual_qty != 0) + ) if filters.get("group_by") == "Warehouse": if filters.get("company"): - conditions += " AND warehouse.company = %s" % frappe.db.escape( - filters.get("company"), percent=False - ) + query = query.where(wh.company == filters.get("company")) - conditions += " GROUP BY ledger.warehouse, item.item_code" - columns += "'' as company, ledger.warehouse" + query = query.select(bin.warehouse).groupby(bin.warehouse) else: - conditions += " GROUP BY warehouse.company, item.item_code" - columns += " warehouse.company, '' as warehouse" - - return frappe.db.sql( - """ - SELECT - %s, - item.item_code, - item.description, - sum(ledger.actual_qty) as actual_qty - FROM - `tabBin` AS ledger - INNER JOIN `tabItem` AS item - ON ledger.item_code = item.item_code - INNER JOIN `tabWarehouse` warehouse - ON warehouse.name = ledger.warehouse - WHERE - ledger.actual_qty != 0 %s""" - % (columns, conditions) - ) + query = query.select(wh.company).groupby(wh.company) + + query = query.select( + item.item_code, item.description, Sum(bin.actual_qty).as_("actual_qty") + ).groupby(item.item_code) + + return query.run() From 656008dd667b51f071372b018f1bd0392af2bdc5 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 6 Oct 2022 20:58:00 +0530 Subject: [PATCH 327/898] refactor: rewrite `Warehouse wise Item Balance Age and Value Report` queries in `QB` (cherry picked from commit 8103856a41279ac3278d560a99f603604b0fd956) --- ...rehouse_wise_item_balance_age_and_value.py | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index a54373f364ce..eedf1a046018 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -7,6 +7,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Count from frappe.utils import flt from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, get_average_age @@ -98,7 +99,7 @@ def get_columns(filters): def validate_filters(filters): if not (filters.get("item_code") or filters.get("warehouse")): - sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0]) + sle_count = flt(frappe.qb.from_("Stock Ledger Entry").select(Count("name")).run()[0][0]) if sle_count > 500000: frappe.throw(_("Please set filter based on Item or Warehouse")) if not filters.get("company"): @@ -108,25 +109,16 @@ def validate_filters(filters): def get_warehouse_list(filters): from frappe.core.doctype.user_permission.user_permission import get_permitted_documents - condition = "" + wh = frappe.qb.DocType("Warehouse") + query = frappe.qb.from_(wh).select(wh.name).where(wh.is_group == 0) + user_permitted_warehouse = get_permitted_documents("Warehouse") - value = () if user_permitted_warehouse: - condition = "and name in %s" - value = set(user_permitted_warehouse) - elif not user_permitted_warehouse and filters.get("warehouse"): - condition = "and name = %s" - value = filters.get("warehouse") - - return frappe.db.sql( - """select name - from `tabWarehouse` where is_group = 0 - {condition}""".format( - condition=condition - ), - value, - as_dict=1, - ) + query = query.where(wh.name.isin(set(user_permitted_warehouse))) + elif filters.get("warehouse"): + query = query.where(wh.name == filters.get("warehouse")) + + return query.run(as_dict=True) def add_warehouse_column(columns, warehouse_list): From 469813d3e1a9ed0609cd07799757a506e6faf950 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 18 Oct 2022 14:18:44 +0530 Subject: [PATCH 328/898] chore: conflicts --- .../batch_wise_balance_history.py | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 6c2f6c113822..44ba82866377 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -4,7 +4,6 @@ import frappe from frappe import _ -from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cint, flt, getdate from pypika import functions as fn @@ -72,8 +71,9 @@ def get_columns(filters): def get_stock_ledger_entries(filters): if not filters.get("from_date"): frappe.throw(_("'From Date' is required")) + if not filters.get("to_date"): + frappe.throw(_("'To Date' is required")) -<<<<<<< HEAD sle = frappe.qb.DocType("Stock Ledger Entry") query = ( frappe.qb.from_(sle) @@ -87,45 +87,17 @@ def get_stock_ledger_entries(filters): .where( (sle.docstatus < 2) & (sle.is_cancelled == 0) - & (sle.batch_no.isnotnull()) - & (sle.batch_no != "") + & (fn.IfNull(sle.batch_no, "") != "") + & (sle.posting_date <= filters["to_date"]) ) .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) .orderby(sle.item_code, sle.warehouse) ) - if to_date := filters.get("to_date"): - query = query.where(sle.posting_date <= to_date) - else: - frappe.throw(_("'To Date' is required")) - query = apply_warehouse_filter(query, sle, filters) for field in ["item_code", "batch_no", "company"]: if filters.get(field): query = query.where(sle[field] == filters.get(field)) -======= - if not filters.get("to_date"): - frappe.throw(_("'To Date' is required")) - - sle = frappe.qb.DocType("Stock Ledger Entry") - query = ( - frappe.qb.from_(sle) - .select( - sle.item_code, - sle.batch_no, - sle.warehouse, - sle.posting_date, - Sum(sle.actual_qty).as_("actual_qty"), - ) - .where((sle.is_cancelled == 0) & (sle.docstatus < 2) & (IfNull(sle.batch_no, "") != "")) - .groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse) - .orderby(sle.item_code, sle.warehouse) - ) - - for field in ("item_code", "warehouse", "batch_no", "company"): - if filters.get(field): - query = query.where(sle[field] == filters[field]) ->>>>>>> 1c1f991d2f (refactor: rewrite `Batch-Wise Balance History Report` queries in `QB`) return query.run(as_dict=True) @@ -167,3 +139,4 @@ def get_item_details(filters): item_map.setdefault(d.name, d) return item_map + \ No newline at end of file From 831f60f439a71fb39c9d2ef287bcaf3fe49a6d38 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 18 Oct 2022 14:27:56 +0530 Subject: [PATCH 329/898] fix: linter --- .../batch_wise_balance_history/batch_wise_balance_history.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 44ba82866377..0d57938e31fa 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -139,4 +139,3 @@ def get_item_details(filters): item_map.setdefault(d.name, d) return item_map - \ No newline at end of file From ffd82f330233ed00bbafbeabef9ceef21a491f4f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:01:08 +0530 Subject: [PATCH 330/898] fix: pricing rule item code UOM apply & conversions (backport #32566) (#32637) fix: pricing rule for non stock UOM and conversions * fix: pricing rule for non stock UOM and conversions (cherry picked from commit 96b4211ea1cfc9a10ba79b54b5645105f0925943) Co-authored-by: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> --- .../doctype/pricing_rule/pricing_rule.py | 18 ++- .../doctype/pricing_rule/test_pricing_rule.py | 115 ++++++++++++++++++ .../accounts/doctype/pricing_rule/utils.py | 6 + .../sales_invoice/test_sales_invoice.py | 2 +- erpnext/public/js/controllers/transaction.js | 3 + 5 files changed, 142 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 9af3188e476d..826d71b12e9e 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -268,6 +268,18 @@ def get_serial_no_for_item(args): return item_details +def update_pricing_rule_uom(pricing_rule, args): + child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get( + pricing_rule.apply_on + ) + + apply_on_field = frappe.scrub(pricing_rule.apply_on) + + for row in pricing_rule.get(child_doc): + if row.get(apply_on_field) == args.get(apply_on_field): + pricing_rule.uom = row.uom + + def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): from erpnext.accounts.doctype.pricing_rule.utils import ( get_applied_pricing_rules, @@ -324,6 +336,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if isinstance(pricing_rule, str): pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule) + update_pricing_rule_uom(pricing_rule, args) pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or [] if pricing_rule.get("suggestion"): @@ -440,12 +453,15 @@ def apply_price_discount_rule(pricing_rule, item_details, args): if pricing_rule.currency == args.currency: pricing_rule_rate = pricing_rule.rate + # TODO https://github.com/frappe/erpnext/pull/23636 solve this in some other way. if pricing_rule_rate: + is_blank_uom = pricing_rule.get("uom") != args.get("uom") # Override already set price list rate (from item price) # if pricing_rule_rate > 0 item_details.update( { - "price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1), + "price_list_rate": pricing_rule_rate + * (args.get("conversion_factor", 1) if is_blank_uom else 1), } ) item_details.update({"discount_percentage": 0.0}) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 0a9db6b0f59a..fbe567824f2d 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -595,6 +595,121 @@ def test_item_price_with_pricing_rule(self): frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() item.delete() + def test_item_price_with_blank_uom_pricing_rule(self): + properties = { + "item_code": "Item Blank UOM", + "stock_uom": "Nos", + "sales_uom": "Box", + "uoms": [dict(uom="Box", conversion_factor=10)], + } + item = make_item(properties=properties) + + make_item_price("Item Blank UOM", "_Test Price List", 100) + + pricing_rule_record = { + "doctype": "Pricing Rule", + "title": "_Test Item Blank UOM Rule", + "apply_on": "Item Code", + "items": [ + { + "item_code": "Item Blank UOM", + } + ], + "selling": 1, + "currency": "INR", + "rate_or_discount": "Rate", + "rate": 101, + "company": "_Test Company", + } + rule = frappe.get_doc(pricing_rule_record) + rule.insert() + + si = create_sales_invoice( + do_not_save=True, item_code="Item Blank UOM", uom="Box", conversion_factor=10 + ) + si.selling_price_list = "_Test Price List" + si.save() + + # If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM. + # rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010 + self.assertEqual(si.items[0].price_list_rate, 1010) + self.assertEqual(si.items[0].rate, 1010) + + si.delete() + + si = create_sales_invoice(do_not_save=True, item_code="Item Blank UOM", uom="Nos") + si.selling_price_list = "_Test Price List" + si.save() + + # UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM. + # rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101 + self.assertEqual(si.items[0].price_list_rate, 101) + self.assertEqual(si.items[0].rate, 101) + + si.delete() + rule.delete() + frappe.get_doc("Item Price", {"item_code": "Item Blank UOM"}).delete() + + item.delete() + + def test_item_price_with_selling_uom_pricing_rule(self): + properties = { + "item_code": "Item UOM other than Stock", + "stock_uom": "Nos", + "sales_uom": "Box", + "uoms": [dict(uom="Box", conversion_factor=10)], + } + item = make_item(properties=properties) + + make_item_price("Item UOM other than Stock", "_Test Price List", 100) + + pricing_rule_record = { + "doctype": "Pricing Rule", + "title": "_Test Item UOM other than Stock Rule", + "apply_on": "Item Code", + "items": [ + { + "item_code": "Item UOM other than Stock", + "uom": "Box", + } + ], + "selling": 1, + "currency": "INR", + "rate_or_discount": "Rate", + "rate": 101, + "company": "_Test Company", + } + rule = frappe.get_doc(pricing_rule_record) + rule.insert() + + si = create_sales_invoice( + do_not_save=True, item_code="Item UOM other than Stock", uom="Box", conversion_factor=10 + ) + si.selling_price_list = "_Test Price List" + si.save() + + # UOM is Box so apply pricing_rule only on Box UOM. + # Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor. + self.assertEqual(si.items[0].price_list_rate, 101) + self.assertEqual(si.items[0].rate, 101) + + si.delete() + + si = create_sales_invoice(do_not_save=True, item_code="Item UOM other than Stock", uom="Nos") + si.selling_price_list = "_Test Price List" + si.save() + + # UOM is Box so pricing_rule won't apply as selling_uom is Nos. + # As Pricing Rule is not applied price of 100 will be fetched from Item Price List. + self.assertEqual(si.items[0].price_list_rate, 100) + self.assertEqual(si.items[0].rate, 100) + + si.delete() + rule.delete() + frappe.get_doc("Item Price", {"item_code": "Item UOM other than Stock"}).delete() + + item.delete() + def test_pricing_rule_for_different_currency(self): make_item("Test Sanitizer Item") diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 1f29d732ba54..4c78d7261d09 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -111,6 +111,12 @@ def _get_pricing_rules(apply_on, args, values): ) if apply_on_field == "item_code": + if args.get("uom", None): + item_conditions += ( + " and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format( + child_doc=child_doc, item_uom=args.get("uom") + ) + ) if "variant_of" not in args: args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of") diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 301d3e136ec1..8d4ec38c81a0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3320,7 +3320,7 @@ def create_sales_invoice(**args): "asset": args.asset or None, "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, - "conversion_factor": 1, + "conversion_factor": args.get("conversion_factor", 1), "incoming_rate": args.incoming_rate or 0, "batch_no": args.batch_no or None, }, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index c17610b58a21..7fecb18fad4d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -426,6 +426,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if(!this.validate_company_and_party()) { this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove(); } else { + item.pricing_rules = '' return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_details", child: item, @@ -1045,6 +1046,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe uom(doc, cdt, cdn) { var me = this; var item = frappe.get_doc(cdt, cdn); + item.pricing_rules = '' if(item.item_code && item.uom) { return this.frm.call({ method: "erpnext.stock.get_item_details.get_conversion_factor", @@ -1121,6 +1123,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe qty(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); + item.pricing_rules = '' this.conversion_factor(doc, cdt, cdn, true); this.calculate_stock_uom_rate(doc, cdt, cdn); this.apply_pricing_rule(item, true); From 8a88105aed3de147a8fc176467435292870195c9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 15:41:57 +0530 Subject: [PATCH 331/898] fix: Advance paid amount in orders (backport #32642) (#32648) fix: Advance paid amount in orders (#32642) (cherry picked from commit 430492152f7ec00a7ea530a0aac0e34682f11d10) Co-authored-by: Deepesh Garg --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7f245fd083a0..94874894b0fd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -995,7 +995,9 @@ def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: for d in self.get("references"): if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"): - frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid() + frappe.get_doc( + d.reference_doctype, d.reference_name, for_update=True + ).set_total_advance_paid() def on_recurring(self, reference_doc, auto_repeat_doc): self.reference_no = reference_doc.name From ed2a093e493ea41efbfc9b47c9c32856d4fdd9de Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Oct 2022 14:12:51 +0530 Subject: [PATCH 332/898] fix: overlap error not raised for job card in case of workstation with production capacity (cherry picked from commit 8b2165e0d16401ea24b4e571c12488e3199a48d5) --- .../doctype/job_card/job_card.py | 2 +- .../doctype/job_card/test_job_card.py | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ed45106634d3..fb94e8aa9947 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -133,7 +133,7 @@ def get_overlap_for(self, args, check_next_available_slot=False): (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0} ) and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1} - order by jctl.to_time desc limit 1""".format( + order by jctl.to_time desc""".format( extra_cond, validate_overlap_for ), { diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index ac7114138c38..4d2dab73e3af 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -136,6 +136,45 @@ def test_job_card_overlap(self): ) self.assertRaises(OverlapError, jc2.save) + def test_job_card_overlap_with_capacity(self): + wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + workstation = make_workstation(workstation_name=random_string(5)).name + frappe.db.set_value("Workstation", workstation, "production_capacity", 1) + + jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) + jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name}) + + jc1.workstation = workstation + jc1.append( + "time_logs", + {"from_time": "2021-01-01 00:00:00", "to_time": "2021-01-01 08:00:00", "completed_qty": 1}, + ) + jc1.save() + + jc2.workstation = workstation + + # add a new entry in same time slice + jc2.append( + "time_logs", + {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1}, + ) + self.assertRaises(OverlapError, jc2.save) + + frappe.db.set_value("Workstation", workstation, "production_capacity", 2) + jc2.load_from_db() + + jc2.workstation = workstation + + # add a new entry in same time slice + jc2.append( + "time_logs", + {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1}, + ) + + jc2.save() + self.assertTrue(jc2.name) + def test_job_card_multiple_materials_transfer(self): "Test transferring RMs separately against Job Card with multiple RMs." self.transfer_material_against = "Job Card" From ccc58f48e38f1aaeaf23410e8b3a8a45dbcd9eea Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 19 Oct 2022 18:37:02 +0200 Subject: [PATCH 333/898] fix: allow to create Sales Order from expired Quotation (#32641) (cherry picked from commit 4ad3002861339651e85ac9ab4e532251b03590b2) --- erpnext/selling/doctype/quotation/quotation.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 70ae085051da..6b42e4daead2 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -84,11 +84,12 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. } } - if(doc.docstatus == 1 && !(['Lost', 'Ordered']).includes(doc.status)) { - if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) { - cur_frm.add_custom_button(__('Sales Order'), - cur_frm.cscript['Make Sales Order'], __('Create')); - } + if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) { + this.frm.add_custom_button( + __("Sales Order"), + this.frm.cscript["Make Sales Order"], + __("Create") + ); if(doc.status!=="Ordered") { this.frm.add_custom_button(__('Set as Lost'), () => { From c3024af296813e63f7c2177040efffe4f01ccf13 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 09:39:11 +0530 Subject: [PATCH 334/898] feat(report):added account wise redirection (backport #32529) (#32657) feat(report): added account wise redirection * feat(report):added account wise redirection Co-authored-by: Deepesh Garg (cherry picked from commit c52b41d311b67887834c78072760b3780b4fb1c3) Co-authored-by: FinByz Tech Pvt. Ltd --- erpnext/public/js/financial_statements.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 1a309ba01568..b0082bdb2817 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -28,7 +28,7 @@ erpnext.financial_statements = { }, "open_general_ledger": function(data) { if (!data.account) return; - var project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; }) + let project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; }); frappe.route_options = { "account": data.account, @@ -37,7 +37,16 @@ erpnext.financial_statements = { "to_date": data.to_date || data.year_end_date, "project": (project && project.length > 0) ? project[0].$input.val() : "" }; - frappe.set_route("query-report", "General Ledger"); + + let report = "General Ledger"; + + if (["Payable", "Receivable"].includes(data.account_type)) { + report = data.account_type == "Payable" ? "Accounts Payable" : "Accounts Receivable"; + frappe.route_options["party_account"] = data.account; + frappe.route_options["report_date"] = data.year_end_date; + } + + frappe.set_route("query-report", report); }, "tree": true, "name_field": "account", From da538a37addaec6911d30dd3aec361f93d58df35 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Oct 2022 18:38:34 +0530 Subject: [PATCH 335/898] fix: incorrect qty in material request (cherry picked from commit ad278b200769e03b52f0051c26873a9eca5d8b43) --- .../doctype/production_plan/production_plan.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 4bb4dcc648ef..000ee07f2c16 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -27,6 +27,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from erpnext.manufacturing.doctype.work_order.work_order import get_item_details from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.stock.get_item_details import get_conversion_factor from erpnext.utilities.transaction_base import validate_uom_is_integer @@ -648,13 +649,23 @@ def make_material_request(self): else: material_request = material_request_map[key] + conversion_factor = 1.0 + if ( + material_request_type == "Purchase" + and item_doc.purchase_uom + and item_doc.purchase_uom != item_doc.stock_uom + ): + conversion_factor = ( + get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0 + ) + # add item material_request.append( "items", { "item_code": item.item_code, "from_warehouse": item.from_warehouse, - "qty": item.quantity, + "qty": item.quantity / conversion_factor, "schedule_date": schedule_date, "warehouse": item.warehouse, "sales_order": item.sales_order, From 92b9d3dc6d15840f0ffaef55ed29038538db91a1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Oct 2022 19:08:27 +0530 Subject: [PATCH 336/898] test: validate qty and purchase uom in material request which is created from PP (cherry picked from commit 4d5ef721f72b068fc2c94c21748373762626f4b3) --- .../production_plan/test_production_plan.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 60e63980724a..c4ab0f886fa0 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -806,6 +806,35 @@ def test_produced_qty_for_multi_level_bom_item(self): self.assertEqual(pln.status, "Completed") self.assertEqual(pln.po_items[0].produced_qty, 5) + def test_material_request_item_for_purchase_uom(self): + from erpnext.stock.doctype.item.test_item import make_item + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + bom_item = make_item( + properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"} + ).name + + if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}): + doc = frappe.get_doc("Item", bom_item) + doc.append("uoms", {"uom": "Nos", "conversion_factor": 10}) + doc.save() + + make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1" + ) + + pln.make_material_request() + for row in frappe.get_all( + "Material Request Item", + filters={"production_plan": pln.name}, + fields=["item_code", "uom", "qty"], + ): + self.assertEqual(row.item_code, bom_item) + self.assertEqual(row.uom, "Nos") + self.assertEqual(row.qty, 1) + def create_production_plan(**args): """ From f8934faa73c020660c189a9b458a94afaebae892 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Oct 2022 23:50:39 +0530 Subject: [PATCH 337/898] fix: Billing Address for inter-company purchase docs (cherry picked from commit 796f2d3c09ae082c20ad1a2501b55db58477a085) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 +++ erpnext/stock/doctype/delivery_note/delivery_note.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index afd5a59df4e0..0c03c550ba07 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2017,6 +2017,9 @@ def update_details(source_doc, target_doc, source_parent): update_address( target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address ) + update_address( + target_doc, "billing_address", "billing_address_display", source_doc.customer_address + ) if currency: target_doc.currency = currency diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 36d5a6ce0e18..9dd28dc60b36 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -842,6 +842,9 @@ def update_details(source_doc, target_doc, source_parent): update_address( target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address ) + update_address( + target_doc, "billing_address", "billing_address_display", source_doc.customer_address + ) update_taxes( target_doc, From e539579fb4e3e3147517aaafb076e55097730dfa Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Oct 2022 13:49:46 +0530 Subject: [PATCH 338/898] fix: BOM cost update message (cherry picked from commit 9cfe527492cb740d40d290b6f8ef5da9dfbaed76) --- erpnext/manufacturing/doctype/bom/bom.py | 12 ++++++++- erpnext/manufacturing/doctype/bom/test_bom.py | 27 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index ff84991c36e5..580838e215bf 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -385,6 +385,7 @@ def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate if self.docstatus == 2: return + self.flags.cost_updated = False existing_bom_cost = self.total_cost if self.docstatus == 1: @@ -407,7 +408,11 @@ def update_cost(self, update_parent=True, from_child_bom=False, update_hour_rate frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) if not from_child_bom: - frappe.msgprint(_("Cost Updated"), alert=True) + msg = "Cost Updated" + if not self.flags.cost_updated: + msg = "No changes in cost found" + + frappe.msgprint(_(msg), alert=True) def update_parent_cost(self): if self.total_cost: @@ -593,11 +598,16 @@ def calculate_cost(self, save_updates=False, update_hour_rate=False): # not via doc event, table is not regenerated and needs updation self.calculate_exploded_cost() + old_cost = self.total_cost + self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost self.base_total_cost = ( self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost ) + if self.total_cost != old_cost: + self.flags.cost_updated = True + def calculate_op_cost(self, update_hour_rate=False): """Update workstation rate and calculates totals""" self.operating_cost = 0 diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 27f3cc905b96..e34ac12cd236 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -9,7 +9,10 @@ from frappe.tests.utils import FrappeTestCase from frappe.utils import cstr, flt -from erpnext.controllers.tests.test_subcontracting_controller import set_backflush_based_on +from erpnext.controllers.tests.test_subcontracting_controller import ( + make_stock_in_entry, + set_backflush_based_on, +) from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import ( update_cost_in_all_boms_in_test, @@ -639,6 +642,28 @@ def test_exploded_items_rate(self): bom.submit() self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + def test_bom_cost_update_flag(self): + rm_item = make_item( + properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} + ).name + fg_item = make_item(properties={"is_stock_item": 1}).name + + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + bom = make_bom(item=fg_item, raw_materials=[rm_item]) + + create_stock_reconciliation( + item_code=rm_item, warehouse="_Test Warehouse - _TC", qty=100, rate=600 + ) + + bom.load_from_db() + bom.update_cost() + self.assertTrue(bom.flags.cost_updated) + + bom.load_from_db() + bom.update_cost() + self.assertFalse(bom.flags.cost_updated) + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) From 3300856fb462a9aa1d146f50d157f5a862df6e97 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 15:57:21 +0530 Subject: [PATCH 339/898] perf: cache barcode scan result (backport #32629) (#32672) perf: cache barcode scan result (#32629) * perf: cache barcode scan result * feat: BarcodeScanResult type * fix: use safe `get_value` `set_value` Co-authored-by: Ankush Menat (cherry picked from commit b88e850d55f6ced279990de5ff4995ef61b0ea0b) Co-authored-by: Devin Slauenwhite --- erpnext/stock/utils.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 9fb3be5188e1..c8ca8a8f659b 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -13,6 +13,8 @@ import erpnext from erpnext.stock.valuation import FIFOValuation, LIFOValuation +BarcodeScanResult = Dict[str, Optional[str]] + class InvalidWarehouseCompany(frappe.ValidationError): pass @@ -552,7 +554,16 @@ def check_pending_reposting(posting_date: str, throw_error: bool = True) -> bool @frappe.whitelist() -def scan_barcode(search_value: str) -> Dict[str, Optional[str]]: +def scan_barcode(search_value: str) -> BarcodeScanResult: + def set_cache(data: BarcodeScanResult): + frappe.cache().set_value(f"erpnext:barcode_scan:{search_value}", data, expires_in_sec=120) + + def get_cache() -> Optional[BarcodeScanResult]: + if data := frappe.cache().get_value(f"erpnext:barcode_scan:{search_value}"): + return data + + if scan_data := get_cache(): + return scan_data # search barcode no barcode_data = frappe.db.get_value( @@ -562,7 +573,9 @@ def scan_barcode(search_value: str) -> Dict[str, Optional[str]]: as_dict=True, ) if barcode_data: - return _update_item_info(barcode_data) + _update_item_info(barcode_data) + set_cache(barcode_data) + return barcode_data # search serial no serial_no_data = frappe.db.get_value( @@ -572,7 +585,9 @@ def scan_barcode(search_value: str) -> Dict[str, Optional[str]]: as_dict=True, ) if serial_no_data: - return _update_item_info(serial_no_data) + _update_item_info(serial_no_data) + set_cache(serial_no_data) + return serial_no_data # search batch no batch_no_data = frappe.db.get_value( @@ -582,6 +597,8 @@ def scan_barcode(search_value: str) -> Dict[str, Optional[str]]: as_dict=True, ) if batch_no_data: + _update_item_info(batch_no_data) + set_cache(batch_no_data) return _update_item_info(batch_no_data) return {} From 887690449dbc56d3e801ccb4ab3d649e78278a44 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Oct 2022 16:17:57 +0530 Subject: [PATCH 340/898] fix: dont update item info twice [skip ci] --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index c8ca8a8f659b..b8c5187b2c34 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -599,7 +599,7 @@ def get_cache() -> Optional[BarcodeScanResult]: if batch_no_data: _update_item_info(batch_no_data) set_cache(batch_no_data) - return _update_item_info(batch_no_data) + return batch_no_data return {} From fff9e7671844d8b6df9ff9a25dfdbfb5b5b58ba1 Mon Sep 17 00:00:00 2001 From: codezart Date: Mon, 10 Oct 2022 11:24:42 -0400 Subject: [PATCH 341/898] fix: number of months subscription plan (cherry picked from commit 2d30b36ccaef9a07ca38f503c8187dc5c022ccb7) --- .../accounts/doctype/subscription_plan/subscription_plan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index a95e0a9c2daf..dcd40b11dfb6 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -8,6 +8,7 @@ from frappe.utils import date_diff, flt, get_first_day, get_last_day, getdate from erpnext.utilities.product import get_price +from dateutil import relativedelta class SubscriptionPlan(Document): @@ -49,7 +50,7 @@ def get_plan_rate( start_date = getdate(start_date) end_date = getdate(end_date) - no_of_months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1 + no_of_months = relativedelta.relativedelta(end_date, start_date).months + 1 cost = plan.cost * no_of_months # Adjust cost if start or end date is not month start or end From cab3f39f9500c89b28bb91f31e30a78e3be03c9e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Oct 2022 12:09:42 +0530 Subject: [PATCH 342/898] chore: Linting issues (cherry picked from commit 1ca472cc8a795fed132979a9ba3a2538ab13ca0a) --- erpnext/accounts/doctype/subscription_plan/subscription_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index dcd40b11dfb6..f3acdc5aa87f 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -3,12 +3,12 @@ import frappe +from dateutil import relativedelta from frappe import _ from frappe.model.document import Document from frappe.utils import date_diff, flt, get_first_day, get_last_day, getdate from erpnext.utilities.product import get_price -from dateutil import relativedelta class SubscriptionPlan(Document): From e1fc67c730c4602a9335bc22af6f6fb031b47dc6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Oct 2022 19:15:50 +0530 Subject: [PATCH 343/898] chore: disable coverage reporting on stable branches --- .github/workflows/server-tests-mariadb.yml | 25 ---------------------- 1 file changed, 25 deletions(-) diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index ba6a28356924..2e272f6b38a7 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -124,28 +124,3 @@ jobs: env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} - ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - - name: Upload coverage data - uses: actions/upload-artifact@v3 - with: - name: coverage-${{ matrix.container }} - path: /home/runner/frappe-bench/sites/coverage.xml - - coverage: - name: Coverage Wrap Up - needs: test - runs-on: ubuntu-latest - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Download artifacts - uses: actions/download-artifact@v3 - - - name: Upload coverage data - uses: codecov/codecov-action@v2 - with: - name: MariaDB - fail_ci_if_error: true - verbose: true From 5cb9f7b3a451d609872384005238b252d02cc882 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 6 Jun 2022 08:50:54 +0530 Subject: [PATCH 344/898] feat: Basic Payment Ledger report (cherry picked from commit 8f60f0a0cf7f22ee95f353ceba63de004bdc1d47) --- .../report/payment_ledger/__init__.py | 0 .../report/payment_ledger/payment_ledger.js | 59 +++++ .../report/payment_ledger/payment_ledger.json | 32 +++ .../report/payment_ledger/payment_ledger.py | 222 ++++++++++++++++++ 4 files changed, 313 insertions(+) create mode 100644 erpnext/accounts/report/payment_ledger/__init__.py create mode 100644 erpnext/accounts/report/payment_ledger/payment_ledger.js create mode 100644 erpnext/accounts/report/payment_ledger/payment_ledger.json create mode 100644 erpnext/accounts/report/payment_ledger/payment_ledger.py diff --git a/erpnext/accounts/report/payment_ledger/__init__.py b/erpnext/accounts/report/payment_ledger/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js new file mode 100644 index 000000000000..9779844dc945 --- /dev/null +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js @@ -0,0 +1,59 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +function get_filters() { + let filters = [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"period_start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1) + }, + { + "fieldname":"period_end_date", + "label": __("End Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + }, + { + "fieldname":"account", + "label": __("Account"), + "fieldtype": "MultiSelectList", + "options": "Account", + get_data: function(txt) { + return frappe.db.get_link_options('Account', txt, { + company: frappe.query_report.get_filter_value("company") + }); + } + }, + { + "fieldname":"voucher_no", + "label": __("Voucher No"), + "fieldtype": "Data", + "width": 100, + }, + { + "fieldname":"against_voucher_no", + "label": __("Against Voucher No"), + "fieldtype": "Data", + "width": 100, + }, + + ] + return filters; +} + +frappe.query_reports["Payment Ledger"] = { + "filters": get_filters() +}; diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.json b/erpnext/accounts/report/payment_ledger/payment_ledger.json new file mode 100644 index 000000000000..716329fbeff6 --- /dev/null +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2022-06-06 08:50:43.933708", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2022-06-06 08:50:43.933708", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Ledger", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Payment Ledger Entry", + "report_name": "Payment Ledger", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py new file mode 100644 index 000000000000..e470c2727e38 --- /dev/null +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -0,0 +1,222 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from collections import OrderedDict + +import frappe +from frappe import _, qb +from frappe.query_builder import Criterion + + +class PaymentLedger(object): + def __init__(self, filters=None): + self.filters = filters + self.columns, self.data = [], [] + self.voucher_dict = OrderedDict() + self.voucher_amount = [] + self.ple = qb.DocType("Payment Ledger Entry") + + def init_voucher_dict(self): + + if self.voucher_amount: + s = set() + # build a set of unique vouchers + for ple in self.voucher_amount: + key = (ple.voucher_type, ple.voucher_no, ple.party) + s.add(key) + + # for each unique vouchers, initialize +/- list + for key in s: + self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list()) + + # for each ple, using against voucher and amount, assign it to +/- list + # group by against voucher + for ple in self.voucher_amount: + against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) + target = None + if self.voucher_dict.get(against_key): + if ple.amount > 0: + target = self.voucher_dict.get(against_key).increase + else: + target = self.voucher_dict.get(against_key).decrease + + # this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple) + # need to somehow include the stray entries as well. + if target is not None: + entry = frappe._dict( + company=ple.company, + account=ple.account, + party_type=ple.party_type, + party=ple.party, + voucher_type=ple.voucher_type, + voucher_no=ple.voucher_no, + against_voucher_type=ple.against_voucher_type, + against_voucher_no=ple.against_voucher_no, + amount=ple.amount, + currency=ple.account_currency, + ) + + if self.filters.include_account_currency: + entry["amount_in_account_currency"] = ple.amount_in_account_currency + + target.append(entry) + + def build_data(self): + self.data.clear() + + for value in self.voucher_dict.values(): + voucher_data = [] + if value.increase != []: + voucher_data.extend(value.increase) + if value.decrease != []: + voucher_data.extend(value.decrease) + + if voucher_data: + # balance row + total = 0 + total_in_account_currency = 0 + + for x in voucher_data: + total += x.amount + if self.filters.include_account_currency: + total_in_account_currency += x.amount_in_account_currency + + entry = frappe._dict( + against_voucher_no="Outstanding:", + amount=total, + currency=voucher_data[0].currency, + ) + + if self.filters.include_account_currency: + entry["amount_in_account_currency"] = total_in_account_currency + + voucher_data.append(entry) + + # empty row + voucher_data.append(frappe._dict()) + self.data.extend(voucher_data) + + def build_conditions(self): + self.conditions = [] + + if self.filters.company: + self.conditions.append(self.ple.company == self.filters.company) + + if self.filters.account: + self.conditions.append(self.ple.account.isin(self.filters.account)) + + if self.filters.period_start_date: + self.conditions.append(self.ple.posting_date.gte(self.filters.period_start_date)) + + if self.filters.period_end_date: + self.conditions.append(self.ple.posting_date.lte(self.filters.period_end_date)) + + if self.filters.voucher_no: + self.conditions.append(self.ple.voucher_no == self.filters.voucher_no) + + if self.filters.against_voucher_no: + self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no) + + def get_data(self): + ple = self.ple + + self.build_conditions() + + # fetch data from table + self.voucher_amount = ( + qb.from_(ple) + .select(ple.star) + .where(ple.delinked == 0) + .where(Criterion.all(self.conditions)) + .run(as_dict=True) + ) + + def get_columns(self): + options = None + self.columns.append( + dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100") + ) + + self.columns.append( + dict(label=_("Account"), fieldname="account", fieldtype="data", options=options, width="100") + ) + + self.columns.append( + dict( + label=_("Party Type"), fieldname="party_type", fieldtype="data", options=options, width="100" + ) + ) + self.columns.append( + dict(label=_("Party"), fieldname="party", fieldtype="data", options=options, width="100") + ) + self.columns.append( + dict( + label=_("Voucher Type"), + fieldname="voucher_type", + fieldtype="data", + options=options, + width="100", + ) + ) + self.columns.append( + dict( + label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100" + ) + ) + self.columns.append( + dict( + label=_("Against Voucher Type"), + fieldname="against_voucher_type", + fieldtype="data", + options=options, + width="100", + ) + ) + self.columns.append( + dict( + label=_("Against Voucher No"), + fieldname="against_voucher_no", + fieldtype="data", + options=options, + width="100", + ) + ) + self.columns.append( + dict( + label=_("Amount"), + fieldname="amount", + fieldtype="Currency", + options="Company:company:default_currency", + width="100", + ) + ) + + if self.filters.include_account_currency: + self.columns.append( + dict( + label=_("Amount in Account Currency"), + fieldname="amount_in_account_currency", + fieldtype="Currency", + options="currency", + width="100", + ) + ) + self.columns.append( + dict(label=_("Currency"), fieldname="currency", fieldtype="Currency", hidden=True) + ) + + def run(self): + self.get_columns() + self.get_data() + + # initialize dictionary and group using against voucher + self.init_voucher_dict() + + # convert dictionary to list and add balance rows + self.build_data() + + return self.columns, self.data + + +def execute(filters=None): + return PaymentLedger(filters).run() From 77f4c666ee3bc491f27e1b569fae3cfa35e01c63 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Oct 2022 13:36:34 +0530 Subject: [PATCH 345/898] test: invoice outstandings and payments (cherry picked from commit 6e55b419a6b4130b346a9c105d1d7e887f3082bf) --- .../payment_ledger/test_payment_ledger.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 erpnext/accounts/report/payment_ledger/test_payment_ledger.py diff --git a/erpnext/accounts/report/payment_ledger/test_payment_ledger.py b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py new file mode 100644 index 000000000000..5ae9b87cde9f --- /dev/null +++ b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py @@ -0,0 +1,65 @@ +import unittest + +import frappe +from frappe import qb +from frappe.tests.utils import FrappeTestCase + +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.report.payment_ledger.payment_ledger import execute + + +class TestPaymentLedger(FrappeTestCase): + def setUp(self): + self.create_company() + self.cleanup() + + def cleanup(self): + doctypes = [] + doctypes.append(qb.DocType("GL Entry")) + doctypes.append(qb.DocType("Payment Ledger Entry")) + doctypes.append(qb.DocType("Sales Invoice")) + doctypes.append(qb.DocType("Payment Entry")) + + for doctype in doctypes: + qb.from_(doctype).delete().where(doctype.company == self.company).run() + + def create_company(self): + name = "Test Payment Ledger" + company = None + if frappe.db.exists("Company", name): + company = frappe.get_doc("Company", name) + else: + company = frappe.get_doc( + { + "doctype": "Company", + "company_name": name, + "country": "India", + "default_currency": "INR", + "create_chart_of_accounts_based_on": "Standard Template", + "chart_of_accounts": "Standard", + } + ) + company = company.save() + self.company = company.name + self.cost_center = company.cost_center + self.warehouse = "All Warehouses" + " - " + company.abbr + self.income_account = company.default_income_account + self.expense_account = company.default_expense_account + self.debit_to = company.default_receivable_account + + def test_unpaid_invoice_outstanding(self): + sinv = create_sales_invoice( + company=self.company, + debit_to=self.debit_to, + expense_account=self.expense_account, + cost_center=self.cost_center, + income_account=self.income_account, + warehouse=self.warehouse, + ) + pe = get_payment_entry(sinv.doctype, sinv.name).save().submit() + + filters = frappe._dict({"company": self.company}) + columns, data = execute(filters=filters) + outstanding = [x for x in data if x.get("against_voucher_no") == "Outstanding:"] + self.assertEqual(outstanding[0].get("amount"), 0) From 9a5e238702fa7f7469e5269e84e1c274f92e0821 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Oct 2022 09:33:37 +0530 Subject: [PATCH 346/898] fix: party type and party mandatory on updating outstanding (cherry picked from commit 43b80683ebd6a902fdb0ea0c4e11a8b2bee9841a) --- erpnext/accounts/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6667454c482c..d7bf9916887d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1479,7 +1479,12 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa # on cancellation outstanding can be an empty list voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter) - if voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] and voucher_outstanding: + if ( + voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] + and party_type + and party + and voucher_outstanding + ): outstanding = voucher_outstanding[0] ref_doc = frappe.get_doc(voucher_type, voucher_no) From c2f8f64d1e9f3918e8370486940f0fedf499abf3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Oct 2022 17:24:36 +0530 Subject: [PATCH 347/898] test: use payable account in tax and to trigger party validation (cherry picked from commit 8f42e7f7034c6ba63b4eb3a6a122295f98cd04f8) --- .../doctype/sales_invoice/test_sales_invoice.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 8d4ec38c81a0..1ba782451b40 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3228,6 +3228,22 @@ def test_batch_expiry_for_sales_invoice_return(self): self.assertTrue(return_si.docstatus == 1) + def test_sales_invoice_with_payable_tax_account(self): + si = create_sales_invoice(do_not_submit=True) + si.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Creditors - _TC", + "description": "Test", + "cost_center": "Main - _TC", + "tax_amount": 10, + "total": 10, + "dont_recompute_tax": 0, + }, + ) + self.assertRaises(frappe.ValidationError, si.submit) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() From d7a65b1d419fb9c8131a48ada866f1fe8ed5037a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 19 Oct 2022 12:26:56 +0200 Subject: [PATCH 348/898] fix: unset contact details (cherry picked from commit 23f0bb45b01fbe7f1884bceb7a457969df79034e) --- erpnext/public/js/utils/party.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 58594b0a13d4..644adff1e273 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -242,20 +242,29 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) { }); }; -erpnext.utils.get_contact_details = function(frm) { +erpnext.utils.get_contact_details = function (frm) { if (frm.updating_party_details) return; if (frm.doc["contact_person"]) { frappe.call({ method: "frappe.contacts.doctype.contact.contact.get_contact_details", - args: {contact: frm.doc.contact_person }, - callback: function(r) { - if (r.message) - frm.set_value(r.message); - } - }) + args: { contact: frm.doc.contact_person }, + callback: function (r) { + if (r.message) frm.set_value(r.message); + }, + }); + } else { + frm.set_value({ + contact_person: "", + contact_display: "", + contact_email: "", + contact_mobile: "", + contact_phone: "", + contact_designation: "", + contact_department: "", + }); } -} +}; erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) { if (!value) { From 6ce32fd5a983739186d690f3209dbc69d6e888f0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Sep 2022 10:13:17 +0530 Subject: [PATCH 349/898] feat: Repayment schedule types for term loans (cherry picked from commit 76c6ccab5d0d518cfc8aacddb72f13f995e8922e) --- .../loan_management/doctype/loan/loan.json | 2 +- erpnext/loan_management/doctype/loan/loan.py | 86 ++++++++++++++----- .../doctype/loan_type/loan_type.json | 11 ++- .../quick_stock_balance.py | 1 + 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 47488f43ce34..d245edc1ce37 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -407,7 +407,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-07-12 11:50:31.957360", + "modified": "2022-09-29 11:50:31.957360", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index d84eef6d8caa..d55af703c062 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -7,7 +7,17 @@ import frappe from frappe import _ -from frappe.utils import add_months, flt, get_last_day, getdate, now_datetime, nowdate +from frappe.utils import ( + add_days, + add_months, + date_diff, + flt, + get_first_day, + get_last_day, + getdate, + now_datetime, + nowdate, +) import erpnext from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry @@ -107,30 +117,66 @@ def make_repayment_schedule(self): if not self.repayment_start_date: frappe.throw(_("Repayment Start Date is mandatory for term loans")) + schedule_type = frappe.db.get_value("Loan Type", self.loan_type, "repayment_schedule_type") self.repayment_schedule = [] payment_date = self.repayment_start_date balance_amount = self.loan_amount + while balance_amount > 0: - interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12 * 100)) - principal_amount = self.monthly_repayment_amount - interest_amount - balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount) - if balance_amount < 0: - principal_amount += balance_amount - balance_amount = 0.0 - - total_payment = principal_amount + interest_amount - self.append( - "repayment_schedule", - { - "payment_date": payment_date, - "principal_amount": principal_amount, - "interest_amount": interest_amount, - "total_payment": total_payment, - "balance_loan_amount": balance_amount, - }, + interest_amount, principal_amount, balance_amount, total_payment = self.get_amounts( + payment_date, balance_amount, schedule_type + ) + + if schedule_type == "Pro-rated calendar months": + next_payment_date = add_days(get_last_day(payment_date), 1) + payment_date = next_payment_date + + self.add_repayment_schedule_row( + payment_date, principal_amount, interest_amount, total_payment, balance_amount ) - next_payment_date = add_single_month(payment_date) - payment_date = next_payment_date + + if schedule_type == "Monthly as per repayment start date": + next_payment_date = add_single_month(payment_date) + payment_date = next_payment_date + + def get_amounts(self, payment_date, balance_amount, schedule_type): + first_day_of_month = get_first_day(payment_date) + + if schedule_type == "Monthly as per repayment start date": + days = 1 + months = 12 + else: + if first_day_of_month == payment_date: + days = 30 + months = 365 + else: + days = date_diff(get_last_day(payment_date), payment_date) + months = 365 + + interest_amount = flt(balance_amount * flt(self.rate_of_interest) * days / (months * 100)) + principal_amount = self.monthly_repayment_amount - interest_amount + balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount) + if balance_amount < 0: + principal_amount += balance_amount + balance_amount = 0.0 + + total_payment = principal_amount + interest_amount + + return interest_amount, principal_amount, balance_amount, total_payment + + def add_repayment_schedule_row( + self, payment_date, principal_amount, interest_amount, total_payment, balance_loan_amount + ): + self.append( + "repayment_schedule", + { + "payment_date": payment_date, + "principal_amount": principal_amount, + "interest_amount": interest_amount, + "total_payment": total_payment, + "balance_loan_amount": balance_loan_amount, + }, + ) def set_repayment_period(self): if self.repayment_method == "Repay Fixed Amount per Period": diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index 00337e4b4c3c..e1ed3caf8f4d 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -16,6 +16,7 @@ "company", "is_term_loan", "disabled", + "repayment_schedule_type", "description", "account_details_section", "mode_of_payment", @@ -157,12 +158,20 @@ "label": "Disbursement Account", "options": "Account", "reqd": 1 + }, + { + "depends_on": "is_term_loan", + "fieldname": "repayment_schedule_type", + "fieldtype": "Select", + "label": "Repayment Schedule Type", + "mandatory_depends_on": "is_term_loan", + "options": "\nMonthly as per repayment start date\nPro-rated calendar months" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-01-25 16:23:57.009349", + "modified": "2022-09-28 21:31:01.278941", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", diff --git a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py index 846be0b9bdcd..403d87468952 100644 --- a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py +++ b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py @@ -15,6 +15,7 @@ class QuickStockBalance(Document): @frappe.whitelist() def get_stock_item_details(warehouse, date, item=None, barcode=None): + print(warehouse, date, item, "########") out = {} if barcode: out["item"] = frappe.db.get_value( From 7398cbdf128a7cdd02b9b8f9150c58c354ae4564 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Sep 2022 10:19:58 +0530 Subject: [PATCH 350/898] chore: Remove print statements (cherry picked from commit 3466461eb3ec97394a5d1e29f0a5ae1df5987042) --- erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py index 403d87468952..846be0b9bdcd 100644 --- a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py +++ b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py @@ -15,7 +15,6 @@ class QuickStockBalance(Document): @frappe.whitelist() def get_stock_item_details(warehouse, date, item=None, barcode=None): - print(warehouse, date, item, "########") out = {} if barcode: out["item"] = frappe.db.get_value( From 8550bbde2752f9e227e289375862982fe68b1601 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Sep 2022 10:39:45 +0530 Subject: [PATCH 351/898] chore: Update labels as per repayment type (cherry picked from commit 2ddee50f27277fa951a2b236b513f377afdc611a) --- erpnext/loan_management/doctype/loan/loan.js | 8 ++++++++ erpnext/loan_management/doctype/loan/loan.json | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 38328e696741..c386ce3ac079 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -103,6 +103,14 @@ frappe.ui.form.on('Loan', { frm.trigger("toggle_fields"); }, + repayment_schedule_type: function(frm) { + if (frm.doc.repayment_schedule_type == "Pro-rated calendar months") { + frm.set_df_property("repayment_start_date", "label", "Interest Calculation Start Date"); + } else { + frm.set_df_property("repayment_start_date", "label", "Repayment Start Date"); + } + }, + loan_type: function(frm) { frm.toggle_reqd("repayment_method", frm.doc.is_term_loan); frm.toggle_display("repayment_method", frm.doc.is_term_loan); diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index d245edc1ce37..dc8b03e89d35 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -18,6 +18,7 @@ "status", "section_break_8", "loan_type", + "repayment_schedule_type", "loan_amount", "rate_of_interest", "is_secured_loan", @@ -158,7 +159,8 @@ "depends_on": "is_term_loan", "fieldname": "repayment_start_date", "fieldtype": "Date", - "label": "Repayment Start Date" + "label": "Repayment Start Date", + "mandatory_depends_on": "is_term_loan" }, { "fieldname": "column_break_11", @@ -402,12 +404,20 @@ "fieldname": "is_npa", "fieldtype": "Check", "label": "Is NPA" + }, + { + "depends_on": "is_term_loan", + "fetch_from": "loan_type.repayment_schedule_type", + "fieldname": "repayment_schedule_type", + "fieldtype": "Data", + "label": "Repayment Schedule Type", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-09-29 11:50:31.957360", + "modified": "2022-09-30 10:36:47.902903", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From 25e87641b6c113ac3148b7b1792a17a80fca21d4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Sep 2022 14:06:06 +0530 Subject: [PATCH 352/898] chore: Add patch to update repayment schedule type in loan documents (cherry picked from commit 679b5ed5514b1fc7ee2840326f073764333fe3b6) # Conflicts: # erpnext/patches.txt --- erpnext/loan_management/doctype/loan/test_loan.py | 1 + erpnext/patches.txt | 4 ++++ .../patches/v13_0/update_schedule_type_in_loans.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 erpnext/patches/v13_0/update_schedule_type_in_loans.py diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index da05c8e90c4d..2608e42e08a7 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -1042,6 +1042,7 @@ def create_loan_type( "company": "_Test Company", "loan_name": loan_name, "is_term_loan": is_term_loan, + "repayment_schedule_type": "Monthly as per repayment start date", "maximum_loan_amount": maximum_loan_amount, "rate_of_interest": rate_of_interest, "penalty_interest_rate": penalty_interest_rate, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fc63f124e154..b39f5658efd6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -315,4 +315,8 @@ erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger +<<<<<<< HEAD erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization +======= +erpnext.patches.v13_0.update_schedule_type_in_loans +>>>>>>> 679b5ed551 (chore: Add patch to update repayment schedule type in loan documents) diff --git a/erpnext/patches/v13_0/update_schedule_type_in_loans.py b/erpnext/patches/v13_0/update_schedule_type_in_loans.py new file mode 100644 index 000000000000..e5b5f643604f --- /dev/null +++ b/erpnext/patches/v13_0/update_schedule_type_in_loans.py @@ -0,0 +1,14 @@ +import frappe + + +def execute(): + loan = frappe.qb.DocType("Loan") + loan_type = frappe.qb.DocType("Loan Type") + + frappe.qb.update(loan_type).set( + loan_type.repayment_schedule_type, "Monthly as per repayment start date" + ).where(loan_type.is_term_loan == 1).run() + + frappe.qb.update(loan).set( + loan.repayment_schedule_type, "Monthly as per repayment start date" + ).where(loan.is_term_loan == 1).run() From b9bf5666bcc04eb113aa75e2a6d88055f470b928 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Sep 2022 15:29:07 +0530 Subject: [PATCH 353/898] chore: label post save (cherry picked from commit bf7a51791aec5c1e1c522e5243e391a05b4bf1f7) --- erpnext/loan_management/doctype/loan/loan.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index c386ce3ac079..20e2b0b20139 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -61,6 +61,10 @@ frappe.ui.form.on('Loan', { }, refresh: function (frm) { + if (frm.doc.repayment_schedule_type == "Pro-rated calendar months") { + frm.set_df_property("repayment_start_date", "label", "Interest Calculation Start Date"); + } + if (frm.doc.docstatus == 1) { if (["Disbursed", "Partially Disbursed"].includes(frm.doc.status) && (!frm.doc.repay_from_salary)) { frm.add_custom_button(__('Request Loan Closure'), function() { From 1b3a0be0f1067d4572eb440fca94a5bdcccf46b5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 22 Oct 2022 23:46:01 +0530 Subject: [PATCH 354/898] chore: Add repayment date on option (cherry picked from commit ef0cb17fafce696fff11685a5cf74e936a3f0508) --- erpnext/loan_management/doctype/loan/loan.py | 34 ++++-- .../loan_management/doctype/loan/test_loan.py | 104 +++++++++++++++++- .../doctype/loan_type/loan_type.json | 13 ++- 3 files changed, 138 insertions(+), 13 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index d55af703c062..0c9c97f60fd5 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -12,7 +12,6 @@ add_months, date_diff, flt, - get_first_day, get_last_day, getdate, now_datetime, @@ -117,36 +116,51 @@ def make_repayment_schedule(self): if not self.repayment_start_date: frappe.throw(_("Repayment Start Date is mandatory for term loans")) - schedule_type = frappe.db.get_value("Loan Type", self.loan_type, "repayment_schedule_type") + schedule_type_details = frappe.db.get_value( + "Loan Type", self.loan_type, ["repayment_schedule_type", "repayment_date_on"], as_dict=1 + ) + self.repayment_schedule = [] payment_date = self.repayment_start_date balance_amount = self.loan_amount while balance_amount > 0: interest_amount, principal_amount, balance_amount, total_payment = self.get_amounts( - payment_date, balance_amount, schedule_type + payment_date, + balance_amount, + schedule_type_details.repayment_schedule_type, + schedule_type_details.repayment_date_on, ) - if schedule_type == "Pro-rated calendar months": - next_payment_date = add_days(get_last_day(payment_date), 1) + if schedule_type_details.repayment_schedule_type == "Pro-rated calendar months": + next_payment_date = get_last_day(payment_date) + if schedule_type_details.repayment_date_on == "Start of the next month": + next_payment_date = add_days(next_payment_date, 1) + payment_date = next_payment_date self.add_repayment_schedule_row( payment_date, principal_amount, interest_amount, total_payment, balance_amount ) - if schedule_type == "Monthly as per repayment start date": + if ( + schedule_type_details.repayment_schedule_type == "Monthly as per repayment start date" + or schedule_type_details.repayment_date_on == "End of the current month" + ): next_payment_date = add_single_month(payment_date) payment_date = next_payment_date - def get_amounts(self, payment_date, balance_amount, schedule_type): - first_day_of_month = get_first_day(payment_date) - + def get_amounts(self, payment_date, balance_amount, schedule_type, repayment_date_on): if schedule_type == "Monthly as per repayment start date": days = 1 months = 12 else: - if first_day_of_month == payment_date: + expected_payment_date = get_last_day(payment_date) + if repayment_date_on == "Start of the next month": + expected_payment_date = add_days(expected_payment_date, 1) + + if expected_payment_date == payment_date: + # using 30 days for calculating interest for all full months days = 30 months = 365 else: diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 2608e42e08a7..0e61161601fc 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -4,7 +4,16 @@ import unittest import frappe -from frappe.utils import add_days, add_months, add_to_date, date_diff, flt, get_datetime, nowdate +from frappe.utils import ( + add_days, + add_months, + add_to_date, + date_diff, + flt, + format_date, + get_datetime, + nowdate, +) from erpnext.loan_management.doctype.loan.loan import ( make_loan_write_off, @@ -49,6 +58,50 @@ def setUp(self): penalty_income_account="Penalty Income Account - _TC", ) + create_loan_type( + "Term Loan Type 1", + 12000, + 7.5, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Monthly as per repayment start date", + ) + + create_loan_type( + "Term Loan Type 2", + 12000, + 7.5, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Pro-rated calendar months", + repayment_date_on="Start of the next month", + ) + + create_loan_type( + "Term Loan Type 3", + 12000, + 7.5, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Pro-rated calendar months", + repayment_date_on="End of the current month", + ) + create_loan_type( "Stock Loan", 2000000, @@ -902,6 +955,45 @@ def test_loan_amount_write_off(self): amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(flt(amounts["pending_principal_amount"], 0), 0) + def test_term_loan_schedule_types(self): + loan = create_loan( + self.applicant1, + "Term Loan Type 1", + 12000, + "Repay Over Number of Periods", + 12, + repayment_start_date="2022-10-17", + ) + + # Check for first, second and last installment date + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "17-10-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "17-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "17-09-2023") + + loan.loan_type = "Term Loan Type 2" + loan.save() + + # Check for first, second and last installment date + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "01-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "01-12-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "01-10-2023") + + loan.loan_type = "Term Loan Type 3" + loan.save() + + # Check for first, second and last installment date + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "31-10-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "30-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "30-09-2023") + + loan.repayment_method = "Repay Fixed Amount per Period" + loan.monthly_repayment_amount = 1042 + loan.save() + + self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "31-10-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "30-11-2022") + self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "30-09-2023") + def create_loan_scenario_for_penalty(doc): pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}] @@ -1033,6 +1125,8 @@ def create_loan_type( penalty_income_account=None, repayment_method=None, repayment_periods=None, + repayment_schedule_type=None, + repayment_date_on=None, ): if not frappe.db.exists("Loan Type", loan_name): @@ -1057,8 +1151,14 @@ def create_loan_type( "repayment_periods": repayment_periods, "write_off_amount": 100, } - ).insert() + ) + + if loan_type.is_term_loan: + loan_type.repayment_schedule_type = repayment_schedule_type + if loan_type.repayment_schedule_type != "Monthly as per repayment start date": + loan_type.repayment_date_on = repayment_date_on + loan_type.insert() loan_type.submit() diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index e1ed3caf8f4d..5cc9464585b3 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -17,6 +17,7 @@ "is_term_loan", "disabled", "repayment_schedule_type", + "repayment_date_on", "description", "account_details_section", "mode_of_payment", @@ -161,17 +162,27 @@ }, { "depends_on": "is_term_loan", + "description": "The schedule type that will be used for generating the term loan schedules (will affect the payment date and monthly repayment amount)", "fieldname": "repayment_schedule_type", "fieldtype": "Select", "label": "Repayment Schedule Type", "mandatory_depends_on": "is_term_loan", "options": "\nMonthly as per repayment start date\nPro-rated calendar months" + }, + { + "depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"", + "description": "Select whether the repayment date should be the end of the current month or start of the upcoming month", + "fieldname": "repayment_date_on", + "fieldtype": "Select", + "label": "Repayment Date On", + "mandatory_depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"", + "options": "\nStart of the next month\nEnd of the current month" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-09-28 21:31:01.278941", + "modified": "2022-10-22 17:43:03.954201", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", From 2348c42c79b7b9f3298a632746b8655411d4a11c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Oct 2022 18:51:51 +0530 Subject: [PATCH 355/898] chore: Update tests (cherry picked from commit e59b147a620c45d8727c8a0adf9ea1cd0d57df6f) --- .../loan_management/doctype/loan/test_loan.py | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 0e61161601fc..388e65d9e585 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -56,6 +56,7 @@ def setUp(self): loan_account="Loan Account - _TC", interest_income_account="Interest Income Account - _TC", penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Monthly as per repayment start date", ) create_loan_type( @@ -115,6 +116,7 @@ def setUp(self): "Loan Account - _TC", "Interest Income Account - _TC", "Penalty Income Account - _TC", + repayment_schedule_type="Monthly as per repayment start date", ) create_loan_type( @@ -966,33 +968,57 @@ def test_term_loan_schedule_types(self): ) # Check for first, second and last installment date - self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "17-10-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "17-11-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "17-09-2023") + self.assertEqual( + format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "17-10-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "17-11-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "17-09-2023" + ) loan.loan_type = "Term Loan Type 2" loan.save() # Check for first, second and last installment date - self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "01-11-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "01-12-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "01-10-2023") + self.assertEqual( + format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "01-11-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "01-12-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "01-10-2023" + ) loan.loan_type = "Term Loan Type 3" loan.save() # Check for first, second and last installment date - self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "31-10-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "30-11-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "30-09-2023") + self.assertEqual( + format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023" + ) loan.repayment_method = "Repay Fixed Amount per Period" loan.monthly_repayment_amount = 1042 loan.save() - self.assertEqual(format_date(loan.get("repayment_schedule")[0].payment_date), "31-10-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[1].payment_date), "30-11-2022") - self.assertEqual(format_date(loan.get("repayment_schedule")[-1].payment_date), "30-09-2023") + self.assertEqual( + format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022" + ) + self.assertEqual( + format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023" + ) def create_loan_scenario_for_penalty(doc): From a0ace27f5668ae596b46b26b7e6cc23dcbf7289c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Oct 2022 19:43:02 +0530 Subject: [PATCH 356/898] chore: resolve conflicts --- erpnext/patches.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b39f5658efd6..5ed8f3f4a91c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -315,8 +315,6 @@ erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger -<<<<<<< HEAD erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization -======= erpnext.patches.v13_0.update_schedule_type_in_loans ->>>>>>> 679b5ed551 (chore: Add patch to update repayment schedule type in loan documents) + From fb1c30718ba5f1cf9dcbf5c5f21b603bca10b4a8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Oct 2022 15:48:34 +0530 Subject: [PATCH 357/898] fix: searchfield not working for cuctsomer, supplier as per customize form (cherry picked from commit 46d148defd59cbb1b9147e035aa6de41ee5fd099) --- erpnext/controllers/queries.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 8eae0a070281..691a39aa8b57 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -83,13 +83,11 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] cust_master_name = frappe.defaults.get_user_default("cust_master_name") - if cust_master_name == "Customer Name": - fields = ["name", "customer_group", "territory"] - else: - fields = ["name", "customer_name", "customer_group", "territory"] + fields = ["name"] + if cust_master_name != "Customer Name": + fields = ["customer_name"] fields = get_fields(doctype, fields) - searchfields = frappe.get_meta(doctype).get_search_fields() searchfields = " or ".join(field + " like %(txt)s" for field in searchfields) @@ -122,10 +120,9 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): doctype = "Supplier" supp_master_name = frappe.defaults.get_user_default("supp_master_name") - if supp_master_name == "Supplier Name": - fields = ["name", "supplier_group"] - else: - fields = ["name", "supplier_name", "supplier_group"] + fields = ["name"] + if supp_master_name != "Supplier Name": + fields = ["supplier_name"] fields = get_fields(doctype, fields) From f50b485225fa6d8b62fa90312bff75d1854813c8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Oct 2022 16:10:47 +0530 Subject: [PATCH 358/898] test: added test case to validate seachfields for customer, supplier (cherry picked from commit 5f84993bae5df78e257cc2bfc41c123a1122a0b6) --- .../buying/doctype/supplier/test_supplier.py | 35 +++++++++++++++++++ erpnext/controllers/queries.py | 6 ++-- .../selling/doctype/customer/test_customer.py | 28 +++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index 55722686fe43..e2dbf21be2c8 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -3,6 +3,7 @@ import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.test_runner import make_test_records from erpnext.accounts.party import get_due_date @@ -152,6 +153,40 @@ def test_party_details_tax_category(self): # Rollback address.delete() + def test_serach_fields_for_supplier(self): + from erpnext.controllers.queries import supplier_query + + supplier_name = create_supplier(supplier_name="Test Supplier 1").name + + make_property_setter( + "Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype" + ) + + data = supplier_query( + "Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True + ) + + self.assertEqual(data[0].name, supplier_name) + self.assertEqual(data[0].supplier_group, "Services") + self.assertTrue("supplier_type" not in data[0]) + + make_property_setter( + "Supplier", + None, + "search_fields", + "supplier_group, supplier_type", + "Data", + for_doctype="Doctype", + ) + data = supplier_query( + "Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True + ) + + self.assertEqual(data[0].name, supplier_name) + self.assertEqual(data[0].supplier_group, "Services") + self.assertEqual(data[0].supplier_type, "Company") + self.assertTrue("supplier_type" in data[0]) + def create_supplier(**args): args = frappe._dict(args) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 691a39aa8b57..3bdc01706825 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -78,7 +78,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def customer_query(doctype, txt, searchfield, start, page_len, filters): +def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): doctype = "Customer" conditions = [] cust_master_name = frappe.defaults.get_user_default("cust_master_name") @@ -110,13 +110,14 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): } ), {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, + as_dict=as_dict, ) # searches for supplier @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def supplier_query(doctype, txt, searchfield, start, page_len, filters): +def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): doctype = "Supplier" supp_master_name = frappe.defaults.get_user_default("supp_master_name") @@ -142,6 +143,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): **{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)} ), {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, + as_dict=as_dict, ) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 7dc3fab6239f..691adccd4dd1 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -3,6 +3,7 @@ import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.test_runner import make_test_records from frappe.tests.utils import FrappeTestCase from frappe.utils import flt @@ -341,6 +342,33 @@ def test_customer_payment_terms(self): due_date = get_due_date("2017-01-22", "Customer", "_Test Customer") self.assertEqual(due_date, "2017-01-22") + def test_serach_fields_for_customer(self): + from erpnext.controllers.queries import customer_query + + make_property_setter( + "Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype" + ) + + data = customer_query( + "Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True + ) + + self.assertEqual(data[0].name, "_Test Customer") + self.assertEqual(data[0].customer_group, "_Test Customer Group") + self.assertTrue("territory" not in data[0]) + + make_property_setter( + "Customer", None, "search_fields", "customer_group, territory", "Data", for_doctype="Doctype" + ) + data = customer_query( + "Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True + ) + + self.assertEqual(data[0].name, "_Test Customer") + self.assertEqual(data[0].customer_group, "_Test Customer Group") + self.assertEqual(data[0].territory, "_Test Territory") + self.assertTrue("territory" in data[0]) + def get_customer_dict(customer_name): return { From 29197dcd7fd56ad5ab962cb77362940f6b3c4bf0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Oct 2022 21:18:50 +0530 Subject: [PATCH 359/898] fix: Filter fixes in Accounts Payable report (cherry picked from commit a5a73ba85762e0940ea1c48f39b22f035923a860) --- erpnext/accounts/report/accounts_payable/accounts_payable.js | 2 ++ .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 7cf14e673859..e1a30a4b77e0 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -51,6 +51,8 @@ frappe.query_reports["Accounts Payable"] = { } else { frappe.query_report.set_filter_value('tax_id', ""); } + + frappe.query_report.refresh(); } }, { diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 8557c03bd5e5..f2ee1eb10ea1 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -748,7 +748,7 @@ def prepare_conditions(self): self.add_accounting_dimensions_filters() - def get_cost_center_conditions(self, conditions): + def get_cost_center_conditions(self): lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"]) cost_center_list = [ center.name From 953f78d6a9ac4e346f7a0ae71aaf10be95ae0cd9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Oct 2022 16:48:24 +0530 Subject: [PATCH 360/898] fix: Add condition for discount section collapse (cherry picked from commit 4cd65027c4a80acce8251c3d3b274c3963daad17) --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 4 +++- .../doctype/sales_invoice_item/sales_invoice_item.json | 3 ++- .../doctype/purchase_order_item/purchase_order_item.json | 3 ++- .../selling/doctype/sales_order_item/sales_order_item.json | 3 ++- .../stock/doctype/delivery_note_item/delivery_note_item.json | 3 ++- .../doctype/purchase_receipt_item/purchase_receipt_item.json | 3 ++- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 9de90368871f..a8f6f80b6b9b 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -214,6 +214,7 @@ "reqd": 1 }, { + "default": "1", "depends_on": "eval:doc.uom != doc.stock_uom", "fieldname": "conversion_factor", "fieldtype": "Float", @@ -820,6 +821,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "section_break_26", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -871,7 +873,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-12 03:37:29.032732", + "modified": "2022-10-26 16:05:37.304788", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index a307a6c17cdb..7f1a1eccc45b 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -247,6 +247,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -876,7 +877,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-10 20:57:38.340026", + "modified": "2022-10-26 11:38:36.119339", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", 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 82e92e87bc9c..b8203bd1286a 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -777,6 +777,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin_section", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -894,7 +895,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-09-07 11:12:38.634976", + "modified": "2022-10-26 16:47:41.364387", "modified_by": "Administrator", "module": "Buying", "name": "Purchase 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 2cf836f9fcc7..ea0b25f41cc6 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -272,6 +272,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -842,7 +843,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-09-06 13:24:18.065312", + "modified": "2022-10-26 16:05:02.712705", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 0a5cbabab0b0..77c32534321e 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -261,6 +261,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -814,7 +815,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-10-12 03:36:05.344847", + "modified": "2022-10-26 16:05:17.720768", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 772736e0adc7..474ee92e268f 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -919,6 +919,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin_section", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -1000,7 +1001,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-12 03:37:59.516609", + "modified": "2022-10-26 16:06:02.524435", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 797512ca131ea70e844a5b9bf2b38f64605e945a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Oct 2022 11:02:14 +0530 Subject: [PATCH 361/898] fix: Company bank account filter in Bank Clearance (cherry picked from commit f9f78c1086a5d19f9aef7370c85ca76b404e4c63) --- .../doctype/bank_clearance/bank_clearance.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js index 63cc46518ff3..7e57c2fc471d 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js @@ -4,23 +4,33 @@ frappe.ui.form.on("Bank Clearance", { setup: function(frm) { frm.add_fetch("account", "account_currency", "account_currency"); - }, - - onload: function(frm) { - - let default_bank_account = frappe.defaults.get_user_default("Company")? - locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: ""; - frm.set_value("account", default_bank_account); frm.set_query("account", function() { return { "filters": { "account_type": ["in",["Bank","Cash"]], - "is_group": 0 + "is_group": 0, } }; }); + frm.set_query("bank_account", function () { + return { + filters: { + 'is_company_account': 1 + }, + }; + }); + }, + + onload: function(frm) { + + let default_bank_account = frappe.defaults.get_user_default("Company")? + locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: ""; + frm.set_value("account", default_bank_account); + + + frm.set_value("from_date", frappe.datetime.month_start()); frm.set_value("to_date", frappe.datetime.month_end()); }, From de3996e411c29d3e4eaa0a948bad6e178eb91dfb Mon Sep 17 00:00:00 2001 From: Vishal Date: Fri, 28 Oct 2022 11:36:14 +0530 Subject: [PATCH 362/898] chore: Added Material Request Reference in Purchase Recipt Dashboard for Tracking (cherry picked from commit a04c44fe34845b6e1e254fc86ea452a6ce8f3930) --- .../doctype/purchase_receipt/purchase_receipt_dashboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py index 06ba93655619..60e5fcffd069 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py @@ -1,6 +1,5 @@ from frappe import _ - def get_data(): return { "fieldname": "purchase_receipt_no", @@ -12,13 +11,14 @@ def get_data(): "Purchase Receipt": "return_against", }, "internal_links": { + "Material Request": ["items", "material_request"], "Purchase Order": ["items", "purchase_order"], "Project": ["items", "project"], "Quality Inspection": ["items", "quality_inspection"], }, "transactions": [ {"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]}, - {"label": _("Reference"), "items": ["Purchase Order", "Quality Inspection", "Project"]}, + {"label": _("Reference"), "items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"]}, {"label": _("Returns"), "items": ["Purchase Receipt"]}, {"label": _("Subscription"), "items": ["Auto Repeat"]}, ], From 0ab69c0e329c3a86bce992950b0709e00da71515 Mon Sep 17 00:00:00 2001 From: Vishal Date: Fri, 28 Oct 2022 11:59:10 +0530 Subject: [PATCH 363/898] chore: minor linting issue fixed (cherry picked from commit e8c01570176212322bf7b0688ece2f3fbf98a184) --- .../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 60e5fcffd069..b3ae7b58b498 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py @@ -1,5 +1,6 @@ from frappe import _ + def get_data(): return { "fieldname": "purchase_receipt_no", @@ -18,7 +19,10 @@ def get_data(): }, "transactions": [ {"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]}, - {"label": _("Reference"), "items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"]}, + { + "label": _("Reference"), + "items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"], + }, {"label": _("Returns"), "items": ["Purchase Receipt"]}, {"label": _("Subscription"), "items": ["Auto Repeat"]}, ], From 6eafff86941d49983b6c7934a580479b3781f5e4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 24 Oct 2022 11:45:24 +0530 Subject: [PATCH 364/898] fix: Clear invoice table post importing invoices (cherry picked from commit 267e7c3a9001c56bf7c1af88e2dfdef175e0502f) --- .../opening_invoice_creation_tool.js | 13 +++---------- .../opening_invoice_creation_tool.py | 2 -- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index 7eb5c4234d13..1f4166151a10 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -22,13 +22,13 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { } if (data.user != frappe.session.user) return; if (data.count == data.total) { - setTimeout((title) => { + setTimeout(() => { frm.doc.import_in_progress = false; frm.clear_table("invoices"); frm.refresh_fields(); frm.page.clear_indicator(); - frm.dashboard.hide_progress(title); - frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type])); + frm.dashboard.hide_progress(); + frappe.msgprint(__("Opening {0} Invoices created", [frm.doc.invoice_type])); }, 1500, data.title); return; } @@ -51,13 +51,6 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { method: "make_invoices", freeze: 1, freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]), - callback: function(r) { - if (r.message.length == 1) { - frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type])); - } else if (r.message.length < 50) { - frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type])); - } - } }); }); diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 99377421c583..c566b420ba84 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -257,8 +257,6 @@ def start_import(invoices): def publish(index, total, doctype): - if total < 50: - return frappe.publish_realtime( "opening_invoice_creation_progress", dict( From 195500cb32b274b21ff63525bbe898f022bf1845 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 24 Oct 2022 17:33:44 +0530 Subject: [PATCH 365/898] fix: Curreny in SOA print for multi-currency party (cherry picked from commit 49ee8736558d4c788ebb70cfddbd116e18d1e4b2) --- .../accounts/report/general_ledger/general_ledger.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 378fa3791c18..7e1df267b128 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -52,22 +52,22 @@
{% } %} - {%= format_currency(data[i].debit, filters.presentation_currency) %} + {%= format_currency(data[i].debit, data[i].account_currency) %} - {%= format_currency(data[i].credit, filters.presentation_currency) %} + {%= format_currency(data[i].credit, data[i].account_currency) %} {% } else { %} {%= frappe.format(data[i].account, {fieldtype: "Link"}) || " " %} - {%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %} + {%= data[i].account && format_currency(data[i].debit, data[i].account_currency) %} - {%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %} + {%= data[i].account && format_currency(data[i].credit, data[i].account_currency) %} {% } %} - {%= format_currency(data[i].balance, filters.presentation_currency) %} + {%= format_currency(data[i].balance, data[i].account_currency) %} {% } %} From 3f0b03c0a4b4d49db37b007fe646262f30d133ac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 24 Oct 2022 19:13:02 +0530 Subject: [PATCH 366/898] chore: Use account currency as fallback (cherry picked from commit a18a715bb47c69578a435af326c0efb9e0baec23) --- .../accounts/report/general_ledger/general_ledger.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 7e1df267b128..c04f518d7e6c 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -52,22 +52,22 @@
{% } %} - {%= format_currency(data[i].debit, data[i].account_currency) %} + {%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %} - {%= format_currency(data[i].credit, data[i].account_currency) %} + {%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %} {% } else { %} {%= frappe.format(data[i].account, {fieldtype: "Link"}) || " " %} - {%= data[i].account && format_currency(data[i].debit, data[i].account_currency) %} + {%= data[i].account && format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %} - {%= data[i].account && format_currency(data[i].credit, data[i].account_currency) %} + {%= data[i].account && format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %} {% } %} - {%= format_currency(data[i].balance, data[i].account_currency) %} + {%= format_currency(data[i].balance, filters.presentation_currency || data[i].account_currency) %} {% } %} From d742e6d56b8c66df9f582a763da13a716faf4850 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Oct 2022 18:01:39 +0530 Subject: [PATCH 367/898] fix: Total Sales amount update in project via Sales Order (cherry picked from commit 6063c4e3c0bca9ff6ad3798888b307c839025d5c) --- 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 f0e9e4b7d927..1f3419fd5ddf 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -627,6 +627,7 @@ def postprocess(source, doc): "field_map": { "name": "sales_order", "base_grand_total": "estimated_costing", + "net_total": "total_sales_amount", }, }, }, From faf25c0b950e72a164acaa892c1124e956110a09 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Oct 2022 20:21:36 +0530 Subject: [PATCH 368/898] fix: Reference due date field type in Journal Entry Accounts table (cherry picked from commit e7caa48e2fd2ad7bd8f2a45cbe25c79c7008a8a2) --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 3 +-- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 +++++- .../journal_entry_account/journal_entry_account.json | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 763e2e6992cd..a5ff7f1aa79b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -312,8 +312,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro } } - get_outstanding(doctype, docname, company, child, due_date) { - var me = this; + get_outstanding(doctype, docname, company, child) { var args = { "doctype": doctype, "docname": docname, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 52690e1e662c..de012b28ec81 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1210,6 +1210,7 @@ def get_outstanding(args): args = json.loads(args) company_currency = erpnext.get_company_currency(args.get("company")) + due_date = None if args.get("doctype") == "Journal Entry": condition = " and party=%(party)s" if args.get("party") else "" @@ -1234,10 +1235,12 @@ def get_outstanding(args): invoice = frappe.db.get_value( args["doctype"], args["docname"], - ["outstanding_amount", "conversion_rate", scrub(party_type)], + ["outstanding_amount", "conversion_rate", scrub(party_type), "due_date"], as_dict=1, ) + due_date = invoice.get("due_date") + exchange_rate = ( invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1 ) @@ -1260,6 +1263,7 @@ def get_outstanding(args): "exchange_rate": exchange_rate, "party_type": party_type, "party": invoice.get(scrub(party_type)), + "reference_due_date": due_date, } diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index a0ea43332c9f..47ad19e0f98a 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -216,7 +216,7 @@ { "depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])", "fieldname": "reference_due_date", - "fieldtype": "Select", + "fieldtype": "Date", "label": "Reference Due Date", "no_copy": 1 }, @@ -284,7 +284,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-13 17:07:17.999191", + "modified": "2022-10-26 20:03:10.906259", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", From c4587d8caa3486a299f6ad41d82050f907c29b48 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 14 Oct 2022 14:04:43 +0530 Subject: [PATCH 369/898] refactor: rewrite `Stock Ledger Report` queries in `QB` (cherry picked from commit feaa2dbba8226adea4094d87fd783877364c1335) --- .../stock/report/stock_ledger/stock_ledger.py | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index a95119736a05..af7f20f3ccff 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -305,20 +305,25 @@ def get_inventory_dimension_fields(): def get_items(filters): + item = frappe.qb.DocType("Item") + query = frappe.qb.from_(item).select(item.name) conditions = [] - if filters.get("item_code"): - conditions.append("item.name=%(item_code)s") + + if item_code := filters.get("item_code"): + conditions.append(item.name == item_code) else: - if filters.get("brand"): - conditions.append("item.brand=%(brand)s") - if filters.get("item_group"): - conditions.append(get_item_group_condition(filters.get("item_group"))) + if brand := filters.get("brand"): + conditions.append(item.brand == brand) + if item_group := filters.get("item_group"): + if condition := get_item_group_condition(item_group, item): + conditions.append(condition) items = [] if conditions: - items = frappe.db.sql_list( - """select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters - ) + for condition in conditions: + query = query.where(condition) + items = [r[0] for r in query.run()] + return items @@ -330,29 +335,22 @@ def get_item_details(items, sl_entries, include_uom): if not items: return item_details - cf_field = cf_join = "" + item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(item) + .select(item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom) + .where(item.name.isin(items)) + ) + if include_uom: - cf_field = ", ucd.conversion_factor" - cf_join = ( - "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" - % frappe.db.escape(include_uom) + ucd = frappe.qb.DocType("UOM Conversion Detail") + query = ( + query.left_join(ucd) + .on((ucd.parent == item.name) & (ucd.uom == include_uom)) + .select(ucd.conversion_factor) ) - res = frappe.db.sql( - """ - select - item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field} - from - `tabItem` item - {cf_join} - where - item.name in ({item_codes}) - """.format( - cf_field=cf_field, cf_join=cf_join, item_codes=",".join(["%s"] * len(items)) - ), - items, - as_dict=1, - ) + res = query.run(as_dict=True) for item in res: item_details.setdefault(item.name, item) @@ -427,16 +425,28 @@ def get_warehouse_condition(warehouse): return "" -def get_item_group_condition(item_group): +def get_item_group_condition(item_group, item_table=None): item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1) if item_group_details: - return ( - "item.item_group in (select ig.name from `tabItem Group` ig \ - where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)" - % (item_group_details.lft, item_group_details.rgt) - ) - - return "" + if item_table: + ig = frappe.qb.DocType("Item Group") + return item_table.item_group.isin( + ( + frappe.qb.from_(ig) + .select(ig.name) + .where( + (ig.lft >= item_group_details.lft) + & (ig.rgt <= item_group_details.rgt) + & (item_table.item_group == ig.name) + ) + ) + ) + else: + return ( + "item.item_group in (select ig.name from `tabItem Group` ig \ + where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)" + % (item_group_details.lft, item_group_details.rgt) + ) def check_inventory_dimension_filters_applied(filters) -> bool: From a4b99a34cb99de2adfee8602967d73e018913210 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 14 Oct 2022 15:35:19 +0530 Subject: [PATCH 370/898] refactor: rewrite `Product Bundle Balance Report` queries in `QB` (cherry picked from commit cde785f1bb8692fbb6f22f554f56f5f463488ab9) --- .../product_bundle_balance.py | 156 +++++++++--------- 1 file changed, 82 insertions(+), 74 deletions(-) diff --git a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py index 854875a0532d..9e75201bd141 100644 --- a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py +++ b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py @@ -4,7 +4,9 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull from frappe.utils import flt +from pypika.terms import ExistsCriterion from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition @@ -123,43 +125,65 @@ def get_items(filters): pb_details = frappe._dict() item_details = frappe._dict() - conditions = get_parent_item_conditions(filters) - parent_item_details = frappe.db.sql( - """ - select item.name as item_code, item.item_name, pb.description, item.item_group, item.brand, item.stock_uom - from `tabItem` item - inner join `tabProduct Bundle` pb on pb.new_item_code = item.name - where ifnull(item.disabled, 0) = 0 {0} - """.format( - conditions - ), - filters, - as_dict=1, - ) # nosec + item = frappe.qb.DocType("Item") + pb = frappe.qb.DocType("Product Bundle") + + query = ( + frappe.qb.from_(item) + .inner_join(pb) + .on(pb.new_item_code == item.name) + .select( + item.name.as_("item_code"), + item.item_name, + pb.description, + item.item_group, + item.brand, + item.stock_uom, + ) + .where(IfNull(item.disabled, 0) == 0) + ) + + if item_code := filters.get("item_code"): + query = query.where(item.item_code == item_code) + else: + if brand := filters.get("brand"): + query = query.where(item.brand == brand) + if item_group := filters.get("item_group"): + if conditions := get_item_group_condition(item_group, item): + query = query.where(conditions) + + parent_item_details = query.run(as_dict=True) parent_items = [] for d in parent_item_details: parent_items.append(d.item_code) item_details[d.item_code] = d + child_item_details = [] if parent_items: - child_item_details = frappe.db.sql( - """ - select - pb.new_item_code as parent_item, pbi.item_code, item.item_name, pbi.description, item.item_group, item.brand, - item.stock_uom, pbi.uom, pbi.qty - from `tabProduct Bundle Item` pbi - inner join `tabProduct Bundle` pb on pb.name = pbi.parent - inner join `tabItem` item on item.name = pbi.item_code - where pb.new_item_code in ({0}) - """.format( - ", ".join(["%s"] * len(parent_items)) - ), - parent_items, - as_dict=1, - ) # nosec - else: - child_item_details = [] + item = frappe.qb.DocType("Item") + pb = frappe.qb.DocType("Product Bundle") + pbi = frappe.qb.DocType("Product Bundle Item") + + child_item_details = ( + frappe.qb.from_(pbi) + .inner_join(pb) + .on(pb.name == pbi.parent) + .inner_join(item) + .on(item.name == pbi.item_code) + .select( + pb.new_item_code.as_("parent_item"), + pbi.item_code, + item.item_name, + pbi.description, + item.item_group, + item.brand, + item.stock_uom, + pbi.uom, + pbi.qty, + ) + .where(pb.new_item_code.isin(parent_items)) + ).run(as_dict=1) child_items = set() for d in child_item_details: @@ -184,58 +208,42 @@ def get_stock_ledger_entries(filters, items): if not items: return [] - item_conditions_sql = " and sle.item_code in ({})".format( - ", ".join(frappe.db.escape(i) for i in items) + sle = frappe.qb.DocType("Stock Ledger Entry") + sle2 = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(sle) + .force_index("posting_sort_index") + .left_join(sle2) + .on( + (sle.item_code == sle2.item_code) + & (sle.warehouse == sle2.warehouse) + & (sle.posting_date < sle2.posting_date) + & (sle.posting_time < sle2.posting_time) + & (sle.name < sle2.name) + ) + .select(sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company) + .where((sle2.name.isnull()) & (sle.docstatus < 2) & (sle.item_code.isin(items))) ) - conditions = get_sle_conditions(filters) - - return frappe.db.sql( - """ - select - sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company - from - `tabStock Ledger Entry` sle force index (posting_sort_index) - left join `tabStock Ledger Entry` sle2 on - sle.item_code = sle2.item_code and sle.warehouse = sle2.warehouse - and (sle.posting_date, sle.posting_time, sle.name) < (sle2.posting_date, sle2.posting_time, sle2.name) - where sle2.name is null and sle.docstatus < 2 %s %s""" - % (item_conditions_sql, conditions), - as_dict=1, - ) # nosec - - -def get_parent_item_conditions(filters): - conditions = [] - - if filters.get("item_code"): - conditions.append("item.item_code = %(item_code)s") + if date := filters.get("date"): + query = query.where(sle.posting_date <= date) else: - if filters.get("brand"): - conditions.append("item.brand=%(brand)s") - if filters.get("item_group"): - conditions.append(get_item_group_condition(filters.get("item_group"))) - - conditions = " and ".join(conditions) - return "and {0}".format(conditions) if conditions else "" - - -def get_sle_conditions(filters): - conditions = "" - if not filters.get("date"): frappe.throw(_("'Date' is required")) - conditions += " and sle.posting_date <= %s" % frappe.db.escape(filters.get("date")) - if filters.get("warehouse"): warehouse_details = frappe.db.get_value( "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 ) + if warehouse_details: - conditions += ( - " and exists (select name from `tabWarehouse` wh \ - where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)" - % (warehouse_details.lft, warehouse_details.rgt) - ) # nosec + wh = frappe.qb.DocType("Warehouse") + query = query.where( + ExistsCriterion( + frappe.qb.from_(wh) + .select(wh.name) + .where((wh.lft >= warehouse_details.lft) & (wh.rgt <= warehouse_details.rgt)) + ) + ) - return conditions + return query.run(as_dict=True) From 98428f0bce44bb56d22488311aeac18e33fe7f8d Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 14 Oct 2022 16:33:02 +0530 Subject: [PATCH 371/898] refactor: rewrite `Itemwise Recommended Reorder Level Report` queries in `QB` (cherry picked from commit 40bd1215932689cc37619a2797298e55ed2235cc) --- .../itemwise_recommended_reorder_level.py | 134 +++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index f308e9e41f11..a6fc049cbde4 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -3,6 +3,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Abs, Sum from frappe.utils import flt, getdate @@ -11,8 +12,6 @@ def execute(filters=None): filters = {} float_precision = frappe.db.get_default("float_precision") - condition = get_condition(filters) - avg_daily_outgoing = 0 diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1 if diff <= 0: @@ -20,8 +19,8 @@ def execute(filters=None): columns = get_columns() items = get_item_info(filters) - consumed_item_map = get_consumed_items(condition) - delivered_item_map = get_delivered_items(condition) + consumed_item_map = get_consumed_items(filters) + delivered_item_map = get_delivered_items(filters) data = [] for item in items: @@ -71,76 +70,86 @@ def get_columns(): def get_item_info(filters): from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition - conditions = [get_item_group_condition(filters.get("item_group"))] - if filters.get("brand"): - conditions.append("item.brand=%(brand)s") - conditions.append("is_stock_item = 1") - - return frappe.db.sql( - """select name, item_name, description, brand, item_group, - safety_stock, lead_time_days from `tabItem` item where {}""".format( - " and ".join(conditions) - ), - filters, - as_dict=1, + item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(item) + .select( + item.name, + item.item_name, + item.description, + item.brand, + item.item_group, + item.safety_stock, + item.lead_time_days, + ) + .where(item.is_stock_item == 1) ) + if brand := filters.get("brand"): + query = query.where(item.brand == brand) + + if conditions := get_item_group_condition(filters.get("item_group"), item): + query = query.where(conditions) -def get_consumed_items(condition): + return query.run(as_dict=True) + + +def get_consumed_items(filters): purpose_to_exclude = [ "Material Transfer for Manufacture", "Material Transfer", "Send to Subcontractor", ] - condition += """ - and ( - purpose is NULL - or purpose not in ({}) + se = frappe.qb.DocType("Stock Entry") + sle = frappe.qb.DocType("Stock Ledger Entry") + query = ( + frappe.qb.from_(sle) + .left_join(se) + .on(sle.voucher_no == se.name) + .select(sle.item_code, Abs(Sum(sle.actual_qty)).as_("consumed_qty")) + .where( + (sle.actual_qty < 0) + & (sle.is_cancelled == 0) + & (sle.voucher_type.notin(["Delivery Note", "Sales Invoice"])) + & ((se.purpose.isnull()) | (se.purpose.notin(purpose_to_exclude))) ) - """.format( - ", ".join(f"'{p}'" for p in purpose_to_exclude) - ) - condition = condition.replace("posting_date", "sle.posting_date") - - consumed_items = frappe.db.sql( - """ - select item_code, abs(sum(actual_qty)) as consumed_qty - from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se - on sle.voucher_no = se.name - where - actual_qty < 0 - and is_cancelled = 0 - and voucher_type not in ('Delivery Note', 'Sales Invoice') - %s - group by item_code""" - % condition, - as_dict=1, + .groupby(sle.item_code) ) + query = get_filtered_query(filters, sle, query) + + consumed_items = query.run(as_dict=True) consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items} return consumed_items_map -def get_delivered_items(condition): - dn_items = frappe.db.sql( - """select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty - from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item - where dn.name = dn_item.parent and dn.docstatus = 1 %s - group by dn_item.item_code""" - % (condition), - as_dict=1, +def get_delivered_items(filters): + parent = frappe.qb.DocType("Delivery Note") + child = frappe.qb.DocType("Delivery Note Item") + query = ( + frappe.qb.from_(parent) + .from_(child) + .select(child.item_code, Sum(child.stock_qty).as_("dn_qty")) + .where((parent.name == child.parent) & (parent.docstatus == 1)) + .groupby(child.item_code) ) - - si_items = frappe.db.sql( - """select si_item.item_code, sum(si_item.stock_qty) as si_qty - from `tabSales Invoice` si, `tabSales Invoice Item` si_item - where si.name = si_item.parent and si.docstatus = 1 and - si.update_stock = 1 %s - group by si_item.item_code""" - % (condition), - as_dict=1, + query = get_filtered_query(filters, parent, query) + + dn_items = query.run(as_dict=True) + + parent = frappe.qb.DocType("Sales Invoice") + child = frappe.qb.DocType("Sales Invoice Item") + query = ( + frappe.qb.from_(parent) + .from_(child) + .select(child.item_code, Sum(child.stock_qty).as_("si_qty")) + .where((parent.name == child.parent) & (parent.docstatus == 1) & (parent.update_stock == 1)) + .groupby(child.item_code) ) + query = get_filtered_query(filters, parent, query) + + si_items = query.run(as_dict=True) dn_item_map = {} for item in dn_items: @@ -152,13 +161,10 @@ def get_delivered_items(condition): return dn_item_map -def get_condition(filters): - conditions = "" +def get_filtered_query(filters, table, query): if filters.get("from_date") and filters.get("to_date"): - conditions += " and posting_date between '%s' and '%s'" % ( - filters["from_date"], - filters["to_date"], - ) + query = query.where(table.posting_date.between(filters["from_date"], filters["to_date"])) else: - frappe.throw(_("From and To dates required")) - return conditions + frappe.throw(_("From and To dates are required")) + + return query From a03ec0afb382e506bfdf2af109b89644d82c1fd7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Oct 2022 16:22:52 +0530 Subject: [PATCH 372/898] feat: additional filters on Payment terms report Filter on Status and Due dates (cherry picked from commit aadb6b1772815095a1778ce324e6877b6464d55a) --- .../payment_terms_status_for_sales_order.js | 30 ++++++++++++++++++- .../payment_terms_status_for_sales_order.py | 14 +++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js index c068ae3b5a47..991ac719cdc8 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js @@ -74,7 +74,35 @@ function get_filters() { ] } } - } + }, + { + "fieldname":"from_due_date", + "label": __("From Due Date"), + "fieldtype": "Date", + }, + { + "fieldname":"to_due_date", + "label": __("To Due Date"), + "fieldtype": "Date", + }, + { + "fieldname":"status", + "label": __("Status"), + "fieldtype": "MultiSelectList", + "width": 100, + get_data: function(txt) { + let status = ["Overdue", "Unpaid", "Completed", "Partly Paid"] + let options = [] + for (let option of status){ + options.push({ + "value": option, + "label": __(option), + "description": "" + }) + } + return options + } + }, ] return filters; } diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py index 91f4a5e50a5c..8bf56865a7d6 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py @@ -162,6 +162,12 @@ def build_filter_criterions(filters): if filters.item: qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item) + if filters.from_due_date: + qb_criterions.append(qb.DocType("Payment Schedule").due_date.gte(filters.from_due_date)) + + if filters.to_due_date: + qb_criterions.append(qb.DocType("Payment Schedule").due_date.lte(filters.to_due_date)) + return qb_criterions @@ -279,11 +285,19 @@ def prepare_chart(s_orders): return chart +def filter_on_calculated_status(filters, sales_orders): + if filters.status and sales_orders: + return [x for x in sales_orders if x.status in filters.status] + return sales_orders + + def execute(filters=None): columns = get_columns() sales_orders, so_invoices = get_so_with_invoices(filters) sales_orders, so_invoices = set_payment_terms_statuses(sales_orders, so_invoices, filters) + sales_orders = filter_on_calculated_status(filters, sales_orders) + prepare_chart(sales_orders) data = sales_orders From 6114241ff2545cbede7bf55ccfa5b68dba07955b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Oct 2022 17:50:01 +0530 Subject: [PATCH 373/898] fix: key error in filter access (cherry picked from commit 4765f937eaa0dd6cf3397245a5ef55d27805df16) --- ...st_payment_terms_status_for_sales_order.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py index 9d542f5079c4..67c96af90174 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py @@ -77,12 +77,14 @@ def test_01_payment_terms_status(self): sinv.insert() sinv.submit() columns, data, message, chart = execute( - { - "company": "_Test Company", - "period_start_date": "2021-06-01", - "period_end_date": "2021-06-30", - "item": item.item_code, - } + frappe._dict( + { + "company": "_Test Company", + "period_start_date": "2021-06-01", + "period_end_date": "2021-06-30", + "item": item.item_code, + } + ) ) expected_value = [ @@ -167,12 +169,14 @@ def test_02_alternate_currency(self): sinv.insert() sinv.submit() columns, data, message, chart = execute( - { - "company": "_Test Company", - "period_start_date": "2021-06-01", - "period_end_date": "2021-06-30", - "item": item.item_code, - } + frappe._dict( + { + "company": "_Test Company", + "period_start_date": "2021-06-01", + "period_end_date": "2021-06-30", + "item": item.item_code, + } + ) ) # report defaults to company currency. From 6dbb4a3b0d7b614acf73e2f61547ea620558db63 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Oct 2022 18:14:51 +0530 Subject: [PATCH 374/898] test: due date filter on Payment Terms report (cherry picked from commit fed39a53cbbae7743b5aa65bba3f6f93044e751e) --- ...st_payment_terms_status_for_sales_order.py | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py index 67c96af90174..525ae8e7ea72 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py @@ -2,7 +2,7 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days +from frappe.utils import add_days, nowdate from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order @@ -342,3 +342,60 @@ def test_03_group_filters(self): with self.subTest(filters=filters): columns, data, message, chart = execute(filters) self.assertEqual(data, expected_values_for_group_filters[idx]) + + def test_04_due_date_filter(self): + self.create_payment_terms_template() + item = create_item(item_code="_Test Excavator 1", is_stock_item=0) + transaction_date = nowdate() + so = make_sales_order( + transaction_date=add_days(transaction_date, -30), + delivery_date=add_days(transaction_date, -15), + item=item.item_code, + qty=10, + rate=100000, + do_not_save=True, + ) + so.po_no = "" + so.taxes_and_charges = "" + so.taxes = "" + so.payment_terms_template = self.template.name + so.save() + so.submit() + + # make invoice with 60% of the total sales order value + sinv = make_sales_invoice(so.name) + sinv.taxes_and_charges = "" + sinv.taxes = "" + sinv.items[0].qty = 6 + sinv.insert() + sinv.submit() + columns, data, message, chart = execute( + frappe._dict( + { + "company": "_Test Company", + "item": item.item_code, + "from_due_date": add_days(transaction_date, -30), + "to_due_date": add_days(transaction_date, -15), + } + ) + ) + + expected_value = [ + { + "name": so.name, + "customer": so.customer, + "submitted": datetime.date.fromisoformat(add_days(transaction_date, -30)), + "status": "Completed", + "payment_term": None, + "description": "_Test 50-50", + "due_date": datetime.date.fromisoformat(add_days(transaction_date, -15)), + "invoice_portion": 50.0, + "currency": "INR", + "base_payment_amount": 500000.0, + "paid_amount": 500000.0, + "invoices": "," + sinv.name, + }, + ] + # Only the first term should be pulled + self.assertEqual(len(data), 1) + self.assertEqual(data, expected_value) From fc6389280ce790aa98c345578d3da39579c2cf25 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sat, 29 Oct 2022 11:56:34 +0530 Subject: [PATCH 375/898] fix: add `Sales Order` reference in Material Request Dashboard (cherry picked from commit 15ebf4a0cf87360ce4265014fe23b2a95e171506) --- .../doctype/material_request/material_request_dashboard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py index b073e6a22eee..691a8b39b1b5 100644 --- a/erpnext/stock/doctype/material_request/material_request_dashboard.py +++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py @@ -4,10 +4,13 @@ def get_data(): return { "fieldname": "material_request", + "internal_links": { + "Sales Order": ["items", "sales_order"], + }, "transactions": [ { "label": _("Reference"), - "items": ["Request for Quotation", "Supplier Quotation", "Purchase Order"], + "items": ["Sales Order", "Request for Quotation", "Supplier Quotation", "Purchase Order"], }, {"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]}, {"label": _("Manufacturing"), "items": ["Work Order"]}, From 403587329508822ec33cae87c86de562d26277e5 Mon Sep 17 00:00:00 2001 From: Hossein Yousefian <86075967+ihosseinu@users.noreply.github.com> Date: Sat, 29 Oct 2022 20:24:59 +0330 Subject: [PATCH 376/898] fix: Pass project to stock entry items fix: Pass project to stock entry items (cherry picked from commit 54c2ffc36b8976fdc0139a8cb06e5e0a9d4f19e5) --- erpnext/assets/doctype/asset_repair/asset_repair.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 8758e9c17dbb..d5913c59463b 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -135,6 +135,7 @@ def decrease_stock_quantity(self): "basic_rate": stock_item.valuation_rate, "serial_no": stock_item.serial_no, "cost_center": self.cost_center, + "project": self.project, }, ) From 34bd7837e27cbc57ebedbdfaa1dc5745a4f36869 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 31 Oct 2022 19:01:54 +0530 Subject: [PATCH 377/898] fix: Reset advance paid amount on Oreder cancel and amend (cherry picked from commit 92f37ca111504eb3aa7092ec61db4513f24b2c61) --- erpnext/buying/doctype/purchase_order/purchase_order.js | 5 +++++ erpnext/selling/doctype/sales_order/sales_order.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index ddf81ca3aec7..06fdea030c8c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -101,6 +101,11 @@ frappe.ui.form.on("Purchase Order", { erpnext.queries.setup_queries(frm, "Warehouse", function() { return erpnext.queries.warehouse(frm.doc); }); + + // On cancel and amending a purchase order with advance payment, reset advance paid amount + if (frm.is_new()) { + frm.set_value("advance_paid", 0) + } }, apply_tds: function(frm) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 386c12b6386b..fb64772479b5 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -124,6 +124,11 @@ frappe.ui.form.on("Sales Order", { return query; }); + // On cancel and amending a sales order with advance payment, reset advance paid amount + if (frm.is_new()) { + frm.set_value("advance_paid", 0) + } + frm.ignore_doctypes_on_cancel_all = ['Purchase Order']; }, From 89a1c83431950129bd305c35f012e07843763098 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 30 Oct 2022 19:33:27 +0530 Subject: [PATCH 378/898] fix: Budget validation for main cost center (cherry picked from commit 4e26d42d1723fcc82ed8f990fad4bf0f4a56d135) --- erpnext/accounts/doctype/budget/budget.py | 8 ++--- .../accounts/doctype/budget/test_budget.py | 33 +++++++++++++++++++ erpnext/accounts/general_ledger.py | 6 ++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 6ac3350c3b0b..637ac7a04cec 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -107,7 +107,7 @@ def before_naming(self): self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###" -def validate_expense_against_budget(args): +def validate_expense_against_budget(args, expense_amount=0): args = frappe._dict(args) if args.get("company") and not args.fiscal_year: @@ -175,13 +175,13 @@ def validate_expense_against_budget(args): ) # nosec if budget_records: - validate_budget_records(args, budget_records) + validate_budget_records(args, budget_records, expense_amount) -def validate_budget_records(args, budget_records): +def validate_budget_records(args, budget_records, expense_amount): for budget in budget_records: if flt(budget.budget_amount): - amount = get_amount(args, budget) + amount = expense_amount or get_amount(args, budget) yearly_action, monthly_action = get_actions(args, budget) if monthly_action in ["Stop", "Warn"]: diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index c48c7d97a2ab..11af9a29f6f4 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -334,6 +334,39 @@ def test_monthly_budget_against_parent_group_cost_center(self): budget.cancel() jv.cancel() + def test_monthly_budget_against_main_cost_center(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + from erpnext.accounts.doctype.cost_center_allocation.test_cost_center_allocation import ( + create_cost_center_allocation, + ) + + cost_centers = [ + "Main Budget Cost Center 1", + "Sub Budget Cost Center 1", + "Sub Budget Cost Center 2", + ] + + for cc in cost_centers: + create_cost_center(cost_center_name=cc, company="_Test Company") + + create_cost_center_allocation( + "_Test Company", + "Main Budget Cost Center 1 - _TC", + {"Sub Budget Cost Center 1 - _TC": 60, "Sub Budget Cost Center 2 - _TC": 40}, + ) + + make_budget(budget_against="Cost Center", cost_center="Main Budget Cost Center 1 - _TC") + + jv = make_journal_entry( + "_Test Account Cost for Goods Sold - _TC", + "_Test Bank - _TC", + 400000, + "Main Budget Cost Center 1 - _TC", + posting_date=nowdate(), + ) + + self.assertRaises(BudgetError, jv.submit) + def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): if budget_against_field == "project": diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f4a50a5f915f..6d164eef2bee 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -128,6 +128,12 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None): new_gl_map = [] for d in gl_map: cost_center = d.get("cost_center") + + # Validate budget against main cost center + validate_expense_against_budget( + d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision) + ) + if cost_center and cost_center_allocation.get(cost_center): for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items(): gle = copy.deepcopy(d) From 1152ac3ff10c73acddb0ec9973baa2cada2e9345 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 27 Oct 2022 18:16:30 +0530 Subject: [PATCH 379/898] fix: duplicate custom fields for inventory dimension (cherry picked from commit 45ededbed52e61910476ac4df33b62d16ea89395) --- .../inventory_dimension/inventory_dimension.py | 14 ++++++++++---- .../test_inventory_dimension.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 9e8c10b394db..7b99b0097bb1 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -121,18 +121,24 @@ def add_custom_fields(self): if self.apply_to_all_doctypes: for doctype in get_inventory_documents(): - custom_fields.setdefault(doctype[0], dimension_fields) - else: + if not field_exists(doctype[0], self.source_fieldname): + custom_fields.setdefault(doctype[0], dimension_fields) + elif not field_exists(self.document_type, self.source_fieldname): custom_fields.setdefault(self.document_type, dimension_fields) if not frappe.db.get_value( "Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname} - ): + ) and not field_exists("Stock Ledger Entry", self.target_fieldname): dimension_field = dimension_fields[1] dimension_field["fieldname"] = self.target_fieldname custom_fields["Stock Ledger Entry"] = dimension_field - create_custom_fields(custom_fields) + if custom_fields: + create_custom_fields(custom_fields) + + +def field_exists(doctype, fieldname) -> str or None: + return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name") @frappe.whitelist() diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 19ddc449f0e0..52b3deb3f01a 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -191,6 +191,21 @@ def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self): self.assertEqual(sle_rack, "Rack 1") + def test_check_standard_dimensions(self): + create_inventory_dimension( + reference_document="Project", + type_of_transaction="Outward", + dimension_name="Project", + apply_to_all_doctypes=0, + document_type="Stock Ledger Entry", + ) + + self.assertFalse( + frappe.db.get_value( + "Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name" + ) + ) + def prepare_test_data(): if not frappe.db.exists("DocType", "Shelf"): From d1b2786f24559c46fbee02e354016243f9c0ecfe Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 1 Nov 2022 12:45:28 +0530 Subject: [PATCH 380/898] fix: pro_rata_amount calculation in assets tests (cherry picked from commit 65e855bfff6b0e2ca935c3f81a907ad9b6eb7e2d) --- erpnext/assets/doctype/asset/test_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 370b13bb98d2..5c1311d68a9e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -221,7 +221,7 @@ def test_scrap_asset(self): asset.precision("gross_purchase_amount"), ) pro_rata_amount, _, _ = asset.get_pro_rata_amt( - asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date + asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount) @@ -283,7 +283,7 @@ def test_gle_made_by_asset_sale(self): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") pro_rata_amount, _, _ = asset.get_pro_rata_amt( - asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date + asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) From 4c5b688e0d29a86138157b7f263a048c391f6312 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 1 Nov 2022 14:25:38 +0530 Subject: [PATCH 381/898] chore: empty commit to try fixing stuck test (cherry picked from commit 672fbd38498dc8031588c7a0ba1d6c4327db6935) From 9b66020fc70209196cda3d9b6e6a654b3b32d8c1 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 1 Nov 2022 16:39:32 +0530 Subject: [PATCH 382/898] feat(pricing rule): free qty rounding and recursion qty (#32577) Option to specify recursion start qty and repeating qty Co-authored-by: Deepesh Garg (cherry picked from commit 1d83fb20d6678a2495c128380969b673ebc41b1a) --- .../doctype/pricing_rule/pricing_rule.json | 27 ++++++++++++- .../doctype/pricing_rule/pricing_rule.py | 13 +++++++ .../doctype/pricing_rule/test_pricing_rule.py | 39 +++++++++++++++++++ .../accounts/doctype/pricing_rule/utils.py | 8 +++- erpnext/public/js/controllers/transaction.js | 3 +- 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 6e7ebd1414d8..ce9ce647db0d 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -52,7 +52,10 @@ "free_item_rate", "column_break_42", "free_item_uom", + "round_free_qty", "is_recursive", + "recurse_for", + "apply_recursion_over", "section_break_23", "valid_from", "valid_upto", @@ -578,12 +581,34 @@ "fieldtype": "Select", "label": "Naming Series", "options": "PRLE-.####" + }, + { + "default": "0", + "fieldname": "round_free_qty", + "fieldtype": "Check", + "label": "Round Free Qty" + }, + { + "depends_on": "is_recursive", + "description": "Give free item for every N quantity", + "fieldname": "recurse_for", + "fieldtype": "Float", + "label": "Recurse Every (As Per Transaction UOM)", + "mandatory_depends_on": "is_recursive" + }, + { + "default": "0", + "depends_on": "is_recursive", + "description": "Qty for which recursion isn't applicable.", + "fieldname": "apply_recursion_over", + "fieldtype": "Float", + "label": "Apply Recursion Over (As Per Transaction UOM)" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2022-09-16 16:00:38.356266", + "modified": "2022-10-13 19:05:35.056304", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 826d71b12e9e..ed46d85e3a42 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -24,6 +24,7 @@ def validate(self): self.validate_applicable_for_selling_or_buying() self.validate_min_max_amt() self.validate_min_max_qty() + self.validate_recursion() self.cleanup_fields_value() self.validate_rate_or_discount() self.validate_max_discount() @@ -109,6 +110,18 @@ def validate_min_max_amt(self): if self.min_amt and self.max_amt and flt(self.min_amt) > flt(self.max_amt): throw(_("Min Amt can not be greater than Max Amt")) + def validate_recursion(self): + if self.price_or_product_discount != "Product": + return + if self.free_item or self.same_item: + if flt(self.recurse_for) <= 0: + self.recurse_for = 1 + if self.is_recursive: + if flt(self.apply_recursion_over) > flt(self.min_qty): + throw(_("Min Qty should be greater than Recurse Over Qty")) + if flt(self.apply_recursion_over) < 0: + throw(_("Recurse Over Qty cannot be less than 0")) + def cleanup_fields_value(self): for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]: fieldname = frappe.scrub(self.get(logic_field) or "") diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index fbe567824f2d..79c7c12b4137 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -943,6 +943,45 @@ def test_pricing_rule_for_other_items_cond_with_amount(self): si.delete() rule.delete() + def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self): + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule", + "apply_on": "Item Code", + "currency": "USD", + "items": [ + { + "item_code": "_Test Item", + } + ], + "selling": 1, + "rate": 0, + "min_qty": 3, + "max_qty": 7, + "price_or_product_discount": "Product", + "same_item": 1, + "free_qty": 1, + "round_free_qty": 1, + "is_recursive": 1, + "recurse_for": 2, + "company": "_Test Company", + } + frappe.get_doc(test_record.copy()).insert() + + # With pricing rule + so = make_sales_order(item_code="_Test Item", qty=5) + so.load_from_db() + self.assertEqual(so.items[1].is_free_item, 1) + self.assertEqual(so.items[1].item_code, "_Test Item") + self.assertEqual(so.items[1].qty, 2) + + so = make_sales_order(item_code="_Test Item", qty=7) + so.load_from_db() + self.assertEqual(so.items[1].is_free_item, 1) + self.assertEqual(so.items[1].item_code, "_Test Item") + self.assertEqual(so.items[1].qty, 4) + test_dependencies = ["Campaign"] diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 4c78d7261d09..35eed49da709 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -627,9 +627,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): qty = pricing_rule.free_qty or 1 if pricing_rule.is_recursive: - transaction_qty = args.get("qty") if args else doc.total_qty + transaction_qty = ( + args.get("qty") if args else doc.total_qty + ) - pricing_rule.apply_recursion_over if transaction_qty: - qty = flt(transaction_qty) * qty + qty = flt(transaction_qty) * qty / pricing_rule.recurse_for + if pricing_rule.round_free_qty: + qty = round(qty) free_item_data_args = { "item_code": free_item, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 7fecb18fad4d..dd957c72ac64 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1404,7 +1404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (!r.exc && r.message) { me._set_values_for_item_list(r.message); if(item) me.set_gross_profit(item); - if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on") + if (me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on") } } }); @@ -1577,6 +1577,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe for (let key in pr_row) { row_to_modify[key] = pr_row[key]; } + this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]); }); // free_item_data is a temporary variable From 74a6479f707c7c849b5aac626e698dca94111a03 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 1 Nov 2022 19:54:41 +0530 Subject: [PATCH 383/898] fix: Issues while cancel/amending Purchase Invoice with TDS enabled (cherry picked from commit f7c9258770a79aff0b951dc18c0b7c39f794b03f) --- .../accounts/doctype/purchase_invoice/purchase_invoice.js | 4 ++++ .../accounts/doctype/purchase_invoice/purchase_invoice.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c3a9855ff448..39a623519a6d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -569,6 +569,10 @@ frappe.ui.form.on("Purchase Invoice", { erpnext.queries.setup_queries(frm, "Warehouse", function() { return erpnext.queries.warehouse(frm.doc); }); + + if (frm.is_new()) { + frm.clear_table("tax_withheld_vouchers"); + } }, is_subcontracted: function(frm) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3d74b8f139bb..882a374046da 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -71,6 +71,9 @@ def onload(self): supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category") self.set_onload("supplier_tds", supplier_tds) + if self.is_new(): + self.set("tax_withheld_vouchers", []) + def before_save(self): if not self.on_hold: self.release_date = "" @@ -1415,7 +1418,7 @@ def on_cancel(self): "Stock Ledger Entry", "Repost Item Valuation", "Payment Ledger Entry", - "Purchase Invoice", + "Tax Withheld Vouchers", ) self.update_advance_tax_references(cancel=1) From a260426dd471d7b186fb1f2a9352e48d06b0cd4e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 31 Oct 2022 19:58:46 +0530 Subject: [PATCH 384/898] fix: Mode of payment for returns in POS Sales Invoice (cherry picked from commit 06e8e28531e2584fd5499df1c233082657de12b0) --- erpnext/controllers/taxes_and_totals.py | 39 +++++++++++++++---------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index cbcccce5f719..b5836c9070a9 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -889,24 +889,33 @@ def set_item_wise_tax_breakup(self): self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc) def set_total_amount_to_default_mop(self, total_amount_to_pay): - default_mode_of_payment = frappe.db.get_value( - "POS Payment Method", - {"parent": self.doc.pos_profile, "default": 1}, - ["mode_of_payment"], - as_dict=1, - ) + total_paid_amount = 0 + for payment in self.doc.get("payments"): + total_paid_amount += ( + payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount + ) + + pending_amount = total_amount_to_pay - total_paid_amount - if default_mode_of_payment: - self.doc.payments = [] - self.doc.append( - "payments", - { - "mode_of_payment": default_mode_of_payment.mode_of_payment, - "amount": total_amount_to_pay, - "default": 1, - }, + if pending_amount > 0: + default_mode_of_payment = frappe.db.get_value( + "POS Payment Method", + {"parent": self.doc.pos_profile, "default": 1}, + ["mode_of_payment"], + as_dict=1, ) + if default_mode_of_payment: + self.doc.payments = [] + self.doc.append( + "payments", + { + "mode_of_payment": default_mode_of_payment.mode_of_payment, + "amount": pending_amount, + "default": 1, + }, + ) + def get_itemised_tax_breakup_html(doc): if not doc.taxes: From 3f72156ea93235240dba7e4b7ef53e6a345252e0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 1 Nov 2022 20:17:34 +0530 Subject: [PATCH 385/898] chore: Update tests (cherry picked from commit 5b74161195b3b006d47f29de5a97428effca527e) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1ba782451b40..cb0d1a75a0c6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -965,7 +965,8 @@ def test_pos_returns_with_repayment(self): pos_return.insert() pos_return.submit() - self.assertEqual(pos_return.get("payments")[0].amount, -1000) + self.assertEqual(pos_return.get("payments")[0].amount, -500) + self.assertEqual(pos_return.get("payments")[1].amount, -500) def test_pos_change_amount(self): make_pos_profile( From 65104644822683c5cd4774d7411fded24f0e4f1e Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 1 Nov 2022 10:38:33 +0530 Subject: [PATCH 386/898] fix: use `flt` instead of `cint` in `get_batch_no` (cherry picked from commit 9fb3fb4c836aef1d05e9a8160baf7089cd3bb224) --- erpnext/stock/doctype/batch/batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 52854a0f013d..f14288beb20f 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -291,7 +291,7 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None): batches = get_batches(item_code, warehouse, qty, throw, serial_no) for batch in batches: - if cint(qty) <= cint(batch.qty): + if flt(qty) <= flt(batch.qty): batch_no = batch.batch_id break From a561432908191bd903a8041cee128c0b81c5087c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 1 Nov 2022 10:11:38 +0530 Subject: [PATCH 387/898] fix: update advance paid in SO/PO from Payment Ledger (cherry picked from commit 4487065b6716ad3d48982df2d4642aa3b8447a80) --- erpnext/controllers/accounts_controller.py | 40 ++++++++-------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 22291a354411..7f5dc0262dae 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,7 +7,7 @@ import frappe from frappe import _, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( add_days, add_months, @@ -1334,30 +1334,20 @@ def get_stock_items(self): return stock_items def set_total_advance_paid(self): - if self.doctype == "Sales Order": - dr_or_cr = "credit_in_account_currency" - rev_dr_or_cr = "debit_in_account_currency" - party = self.customer - else: - dr_or_cr = "debit_in_account_currency" - rev_dr_or_cr = "credit_in_account_currency" - party = self.supplier - - advance = frappe.db.sql( - """ - select - account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount - from - `tabGL Entry` - where - against_voucher_type = %s and against_voucher = %s and party=%s - and docstatus = 1 - """.format( - dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr - ), - (self.doctype, self.name, party), - as_dict=1, - ) # nosec + ple = frappe.qb.DocType("Payment Ledger Entry") + party = self.customer if self.doctype == "Sales Order" else self.supplier + advance = ( + frappe.qb.from_(ple) + .select(ple.account_currency, Abs(Sum(ple.amount)).as_("amount")) + .where( + (ple.against_voucher_type == self.doctype) + & (ple.against_voucher_no == self.name) + & (ple.party == party) + & (ple.delinked == 0) + & (ple.company == self.company) + ) + .run(as_dict=True) + ) if advance: advance = advance[0] From 388cf5113ba1ab074bc3f7e34987003bb0b640c1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 2 Nov 2022 15:40:56 +0530 Subject: [PATCH 388/898] test: refactor use @change_settings decorator when possible (cherry picked from commit 81d791eea0eaec86d532a0a248752976eeaf08c2) --- .../doctype/purchase_order/test_purchase_order.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 6c1bcc7dd490..e0981c697b09 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -5,7 +5,7 @@ import json import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate from frappe.utils.data import today @@ -709,13 +709,10 @@ def test_terms_copied(self): pi.insert() self.assertTrue(pi.get("payment_schedule")) + @change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1}) def test_advance_payment_entry_unlink_against_purchase_order(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 1 - ) - po_doc = create_purchase_order() pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC") @@ -735,10 +732,6 @@ def test_advance_payment_entry_unlink_against_purchase_order(self): pe_doc = frappe.get_doc("Payment Entry", pe.name) pe_doc.cancel() - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 - ) - def test_schedule_date(self): po = create_purchase_order(do_not_submit=True) po.schedule_date = None From 87dc812a4b9ee908de8530e5c0d70d396c1a7724 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 2 Nov 2022 15:43:45 +0530 Subject: [PATCH 389/898] test: PO advance paid on payment submission and cancellation (cherry picked from commit 1a0a8ac7e238ab3ca912b0912e76b1de69ace363) --- .../purchase_order/test_purchase_order.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index e0981c697b09..5a96131157be 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -732,6 +732,32 @@ def test_advance_payment_entry_unlink_against_purchase_order(self): pe_doc = frappe.get_doc("Payment Entry", pe.name) pe_doc.cancel() + @change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1}) + def test_advance_paid_upon_payment_entry_cancellation(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + po_doc = create_purchase_order() + + pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = po_doc.currency + pe.paid_to_account_currency = po_doc.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = po_doc.grand_total + pe.save(ignore_permissions=True) + pe.submit() + + po_doc.reload() + self.assertEqual(po_doc.advance_paid, po_doc.base_grand_total) + + pe_doc = frappe.get_doc("Payment Entry", pe.name) + pe_doc.cancel() + + po_doc.reload() + self.assertEqual(po_doc.advance_paid, 0) + def test_schedule_date(self): po = create_purchase_order(do_not_submit=True) po.schedule_date = None From cf32e1905c65195191e3788ba2897376c735ba6e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 2 Nov 2022 15:54:23 +0530 Subject: [PATCH 390/898] test: SO advance paid on Payment submission and cancellation (cherry picked from commit 721ac6b847ed56943621697123359d7d37360a3e) --- .../doctype/sales_order/test_sales_order.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index adfb39c1ae92..dfa341b1fc7c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -6,7 +6,7 @@ import frappe import frappe.permissions from frappe.core.doctype.user_permission.test_user_permission import create_user -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today from erpnext.controllers.accounts_controller import update_child_qty_rate @@ -1346,6 +1346,33 @@ def test_advance_payment_entry_unlink_against_sales_order(self): self.assertRaises(frappe.LinkExistsError, so_doc.cancel) + @change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1}) + def test_advance_paid_upon_payment_cancellation(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + so = make_sales_order() + + pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = so.currency + pe.paid_to_account_currency = so.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = so.grand_total + pe.save(ignore_permissions=True) + pe.submit() + so.reload() + + self.assertEqual(so.advance_paid, so.base_grand_total) + + # cancel advance payment + pe.reload() + pe.cancel() + + so.reload() + self.assertEqual(so.advance_paid, 0) + def test_cancel_sales_order_after_cancel_payment_entry(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry From 0feec4ca8a630471371e83fb4005037d0c6e1f41 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 1 Nov 2022 19:30:37 +0530 Subject: [PATCH 391/898] fix: test cases (cherry picked from commit ddd1b4be3ff1daea9e9abc922c62c4abea5be951) --- erpnext/stock/doctype/material_request/test_material_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 78af1532ea80..005480e3996e 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -591,6 +591,7 @@ def _get_requested_qty(): mr.material_request_type = "Material Issue" mr.submit() + self.assertTrue(frappe.db.exists("Material Request", mr.name)) # testing bin value after material request is submitted self.assertEqual(_get_requested_qty(), existing_requested_qty - 54.0) From d3fbe3074dcdb9cedc7b91df2ff3de2e470324b0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 1 Nov 2022 19:56:55 +0530 Subject: [PATCH 392/898] test: run tmate (cherry picked from commit 3f2728e3f7645080b20a6835da6bb39bbd084b11) --- erpnext/stock/doctype/material_request/test_material_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 005480e3996e..f02462c5965d 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -590,8 +590,8 @@ def _get_requested_qty(): mr = frappe.copy_doc(test_records[0]) mr.material_request_type = "Material Issue" mr.submit() + frappe.db.value_cache = {} - self.assertTrue(frappe.db.exists("Material Request", mr.name)) # testing bin value after material request is submitted self.assertEqual(_get_requested_qty(), existing_requested_qty - 54.0) From b8caa587d2296cf856122bb2f57a85263a9a69f1 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 21:54:55 -0600 Subject: [PATCH 393/898] fix: add translate function to name of chart labels in deferred_revenue_and_expense.py (cherry picked from commit a963618b08f90408bdcaeaa033cb7cbe1f481232) --- .../deferred_revenue_and_expense.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 1eb257ac8538..6cc86c3efec4 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -396,7 +396,7 @@ def prepare_chart(self): "labels": [period.label for period in self.period_list], "datasets": [ { - "name": "Actual Posting", + "name": _("Actual Posting"), "chartType": "bar", "values": [x.actual for x in self.period_total], } @@ -410,7 +410,7 @@ def prepare_chart(self): if self.filters.with_upcoming_postings: chart["data"]["datasets"].append( - {"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total]} + {"name": _("Expected"), "chartType": "line", "values": [x.total for x in self.period_total]} ) return chart From 2dc24f22ea6762e9e6fb4f1a9bb73dca45433ed7 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:00:46 -0600 Subject: [PATCH 394/898] fix: refactor code for better translatable string (cherry picked from commit a671652ab2ebb5d25aa45d979d1c3c8b3861efcf) --- .../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 f2ee1eb10ea1..a195c575866f 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1009,7 +1009,7 @@ def setup_ageing_columns(self): "{range3}-{range4}".format( range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"] ), - "{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")), + _("{range4}-Above").format(range4=cint(self.filters["range4"]) + 1), ] ): self.add_column(label=label, fieldname="range" + str(i + 1)) From 0ead51642f718d08c7345ff78ffc844fd1de5071 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:05:25 -0600 Subject: [PATCH 395/898] fix: refactor code for better translatable string in stock_ageing.py (cherry picked from commit 71a0ae2e596538de3cac07839d4e03cf24f7e1bd) --- erpnext/stock/report/stock_ageing/stock_ageing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 7c430e491abd..944433103f20 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -198,11 +198,11 @@ def setup_ageing_columns(filters: Filters, range_columns: List): f"0 - {filters['range1']}", f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}", f"{cint(filters['range2']) + 1} - {cint(filters['range3'])}", - f"{cint(filters['range3']) + 1} - {_('Above')}", + _("{0} - Above").format(cint(filters['range3']) + 1), ] for i, label in enumerate(ranges): fieldname = "range" + str(i + 1) - add_column(range_columns, label=f"Age ({label})", fieldname=fieldname) + add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname) def add_column( From b0c06d5a04adb12704fa1c10e35cca329dedc594 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:09:02 -0600 Subject: [PATCH 396/898] fix: add translate function to period in stock_analytics.py (cherry picked from commit c1e608d9ef84b4b9e765b84482b666554c98220c) --- erpnext/stock/report/stock_analytics/stock_analytics.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 89ca9d9126e9..175c479ddaef 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -114,11 +114,11 @@ def get_period(posting_date, filters): months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] if filters.range == "Weekly": - period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year) + period = _("Week {0} {1}").format(str(posting_date.isocalendar()[1]), str(posting_date.year)) elif filters.range == "Monthly": - period = str(months[posting_date.month - 1]) + " " + str(posting_date.year) + period = _(str(months[posting_date.month - 1])) + " " + str(posting_date.year) elif filters.range == "Quarterly": - period = "Quarter " + str(((posting_date.month - 1) // 3) + 1) + " " + str(posting_date.year) + period = _("Quarter {0} {1}").format(str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)) else: year = get_fiscal_year(posting_date, company=filters.company) period = str(year[2]) From e681f068832384ee55e8eb170ce6a637a902a81d Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:17:06 -0600 Subject: [PATCH 397/898] fix: add translate function to period in sales_analytics.py (cherry picked from commit 083a78135c04af032ca5665e6b451876a4b758ed) --- erpnext/selling/report/sales_analytics/sales_analytics.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 9d7d806c716c..7dfb7d0d2205 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -313,11 +313,11 @@ def get_periodic_data(self): def get_period(self, posting_date): if self.filters.range == "Weekly": - period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year) + period = _("Week {0} {1}").format(str(posting_date.isocalendar()[1]), str(posting_date.year)) elif self.filters.range == "Monthly": - period = str(self.months[posting_date.month - 1]) + " " + str(posting_date.year) + period = _(str(self.months[posting_date.month - 1])) + " " + str(posting_date.year) elif self.filters.range == "Quarterly": - period = "Quarter " + str(((posting_date.month - 1) // 3) + 1) + " " + str(posting_date.year) + period = _("Quarter {0} {1}").format(str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)) else: year = get_fiscal_year(posting_date, company=self.filters.company) period = str(year[0]) From 16f364da37c4a67cdee1a00714a3560b155ef9fe Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:18:47 -0600 Subject: [PATCH 398/898] fix: add translate function to name of chart labels in budget_variance_report.py (cherry picked from commit 48ed6381b374032a3d46f4afb3f3252ba83c2580) --- .../report/budget_variance_report/budget_variance_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 7b774ba740b6..96cfab9f11f5 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -383,8 +383,8 @@ def get_chart_data(filters, columns, data): "data": { "labels": labels, "datasets": [ - {"name": "Budget", "chartType": "bar", "values": budget_values}, - {"name": "Actual Expense", "chartType": "bar", "values": actual_values}, + {"name": _("Budget"), "chartType": "bar", "values": budget_values}, + {"name": _("Actual Expense"), "chartType": "bar", "values": actual_values}, ], }, "type": "bar", From 595aaad99d5956f6345a6dadfd1994a12ac0bf26 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:24:33 -0600 Subject: [PATCH 399/898] fix: add translate function to string on budget_variance_report.js to match the variance word translated (cherry picked from commit 2012bdf4bd8d1cead09de99e5b9a68aa8a9990ef) --- .../report/budget_variance_report/budget_variance_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index 718b6e2fcb65..5955c2e0fc97 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -75,7 +75,7 @@ frappe.query_reports["Budget Variance Report"] = { "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.fieldname.includes('variance')) { + if (column.fieldname.includes(__("variance"))) { if (data[column.fieldname] < 0) { value = "" + value + ""; From 5acc9be5c931aaa22b17be36b581068501d10c87 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:34:21 -0600 Subject: [PATCH 400/898] fix: correct linters (cherry picked from commit b7b53b58574de501410a80fab02073291d9a5fc4) --- erpnext/selling/report/sales_analytics/sales_analytics.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 7dfb7d0d2205..b08f1deefe34 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -317,7 +317,9 @@ def get_period(self, posting_date): elif self.filters.range == "Monthly": period = _(str(self.months[posting_date.month - 1])) + " " + str(posting_date.year) elif self.filters.range == "Quarterly": - period = _("Quarter {0} {1}").format(str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)) + period = _("Quarter {0} {1}").format( + str(((posting_date.month - 1) // 3) + 1), str(posting_date.year) + ) else: year = get_fiscal_year(posting_date, company=self.filters.company) period = str(year[0]) From 440e20859f1b26e253141652d9a4c1bca06165de Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:35:37 -0600 Subject: [PATCH 401/898] fix: correct linters (cherry picked from commit 9c529c61bb90ac7827e8448b90e33b812a9f7093) --- erpnext/stock/report/stock_analytics/stock_analytics.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 175c479ddaef..27b94ab3f968 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -118,7 +118,9 @@ def get_period(posting_date, filters): elif filters.range == "Monthly": period = _(str(months[posting_date.month - 1])) + " " + str(posting_date.year) elif filters.range == "Quarterly": - period = _("Quarter {0} {1}").format(str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)) + period = _("Quarter {0} {1}").format( + str(((posting_date.month - 1) // 3) + 1), str(posting_date.year) + ) else: year = get_fiscal_year(posting_date, company=filters.company) period = str(year[2]) From 8f6f9a429a3131ee3bdab4836775223bb4a4b1b4 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 25 Oct 2022 22:39:05 -0600 Subject: [PATCH 402/898] fix: correct linters (cherry picked from commit 4c7fa9482d81ec0794c69570371e4b923d188f5f) --- erpnext/stock/report/stock_ageing/stock_ageing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 944433103f20..2fa97ae35458 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -198,7 +198,7 @@ def setup_ageing_columns(filters: Filters, range_columns: List): f"0 - {filters['range1']}", f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}", f"{cint(filters['range2']) + 1} - {cint(filters['range3'])}", - _("{0} - Above").format(cint(filters['range3']) + 1), + _("{0} - Above").format(cint(filters["range3"]) + 1), ] for i, label in enumerate(ranges): fieldname = "range" + str(i + 1) From 01a1c963148b60d23dbdd66638d6b8d2a5456b2c Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Mon, 31 Oct 2022 12:41:37 +0530 Subject: [PATCH 403/898] fix: for asset's purchase_date, if bill_date is set, use that instead of posting_date (cherry picked from commit f322c608cf7acbb8c5928f151876a2e5ddc13595) --- erpnext/assets/doctype/asset/asset.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 5512d4159d86..7e5421974072 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -432,7 +432,11 @@ frappe.ui.form.on('Asset', { set_values_from_purchase_doc: function(frm, doctype, purchase_doc) { frm.set_value('company', purchase_doc.company); - frm.set_value('purchase_date', purchase_doc.posting_date); + if (purchase_doc.bill_date) { + frm.set_value('purchase_date', purchase_doc.bill_date); + } else { + frm.set_value('purchase_date', purchase_doc.posting_date); + } const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code); if (!item) { doctype_field = frappe.scrub(doctype) From 4d9bbd4c9c06811086e0ae4e2c16763fe7d171a3 Mon Sep 17 00:00:00 2001 From: Bhavesh Maheshwari <34086262+bhavesh95863@users.noreply.github.com> Date: Thu, 3 Nov 2022 10:59:20 +0530 Subject: [PATCH 404/898] fix: mysql syntax issue --- erpnext/stock/doctype/pick_list/pick_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 9c1c7e567962..8bad9bc272f2 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -699,7 +699,8 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte AND `company` = %(company)s AND `name` like %(txt)s ORDER BY - (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end) name + (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end), + name LIMIT %(start)s, %(page_length)s""", { From 6989cdf4f2c327bcc7565ce70a51b794a83f5c94 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 3 Nov 2022 11:24:58 +0530 Subject: [PATCH 405/898] fix: not able to select customer / supplier (cherry picked from commit b0fc568c80ec5bead83fc0bc61be78e95ba24813) --- erpnext/buying/doctype/supplier/test_supplier.py | 4 ++++ erpnext/controllers/queries.py | 4 ++-- erpnext/selling/doctype/customer/test_customer.py | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index e2dbf21be2c8..b9fc344647b7 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -156,6 +156,8 @@ def test_party_details_tax_category(self): def test_serach_fields_for_supplier(self): from erpnext.controllers.queries import supplier_query + frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series") + supplier_name = create_supplier(supplier_name="Test Supplier 1").name make_property_setter( @@ -187,6 +189,8 @@ def test_serach_fields_for_supplier(self): self.assertEqual(data[0].supplier_type, "Company") self.assertTrue("supplier_type" in data[0]) + frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name") + def create_supplier(**args): args = frappe._dict(args) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 3bdc01706825..b0cf72416691 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -85,7 +85,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict= fields = ["name"] if cust_master_name != "Customer Name": - fields = ["customer_name"] + fields.append("customer_name") fields = get_fields(doctype, fields) searchfields = frappe.get_meta(doctype).get_search_fields() @@ -123,7 +123,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict= fields = ["name"] if supp_master_name != "Supplier Name": - fields = ["supplier_name"] + fields.append("supplier_name") fields = get_fields(doctype, fields) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 691adccd4dd1..a621c737ed38 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -345,6 +345,8 @@ def test_customer_payment_terms(self): def test_serach_fields_for_customer(self): from erpnext.controllers.queries import customer_query + frappe.db.set_value("Selling Settings", None, "cust_master_name", "Naming Series") + make_property_setter( "Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype" ) @@ -369,6 +371,8 @@ def test_serach_fields_for_customer(self): self.assertEqual(data[0].territory, "_Test Territory") self.assertTrue("territory" in data[0]) + frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name") + def get_customer_dict(customer_name): return { From e334b7dfeeccc018a141496f632d36469fb0fc5f Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 3 Nov 2022 13:36:01 -0600 Subject: [PATCH 406/898] chore: add translation function to Bank Reconciliation Tool related files chore: add translation function to Bank Reconciliation Tool related files (cherry picked from commit ad0dd693ac4e5441f26f2ab3af1c6aa1d55426df) --- .../bank_reconciliation_tool.js | 3 ++ .../bank_reconciliation_tool.json | 5 ++- .../bank_statement_import.js | 2 +- .../data_table_manager.js | 24 ++++++------- .../dialog_manager.js | 34 ++++++------------- .../bank_reconciliation_tool/number_card.js | 6 ++-- 6 files changed, 32 insertions(+), 42 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index 46ba27c004d0..28e79b5d2c6a 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -12,6 +12,9 @@ frappe.ui.form.on("Bank Reconciliation Tool", { }, }; }); + let no_bank_transactions_text = + `
${__("No Matching Bank Transactions Found")}
` + set_field_options("no_bank_transactions", no_bank_transactions_text); }, onload: function (frm) { diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json index b643e6e09124..f666101d3fd8 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json @@ -81,8 +81,7 @@ }, { "fieldname": "no_bank_transactions", - "fieldtype": "HTML", - "options": "
No Matching Bank Transactions Found
" + "fieldtype": "HTML" } ], "hide_toolbar": 1, @@ -109,4 +108,4 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} 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 f74562086efa..04af32346bbd 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -100,7 +100,7 @@ frappe.ui.form.on("Bank Statement Import", { if (frm.doc.status.includes("Success")) { frm.add_custom_button( - __("Go to {0} List", [frm.doc.reference_doctype]), + __("Go to {0} List", [__(frm.doc.reference_doctype)]), () => frappe.set_route("List", frm.doc.reference_doctype) ); } diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js index 5bb58faf2fca..9ef8ce6b63e0 100644 --- a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js @@ -30,28 +30,28 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { get_dt_columns() { this.columns = [ { - name: "Date", + name: __("Date"), editable: false, width: 100, }, { - name: "Party Type", + name: __("Party Type"), editable: false, width: 95, }, { - name: "Party", + name: __("Party"), editable: false, width: 100, }, { - name: "Description", + name: __("Description"), editable: false, width: 350, }, { - name: "Deposit", + name: __("Deposit"), editable: false, width: 100, format: (value) => @@ -60,7 +60,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { "", }, { - name: "Withdrawal", + name: __("Withdrawal"), editable: false, width: 100, format: (value) => @@ -69,26 +69,26 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { "", }, { - name: "Unallocated Amount", + name: __("Unallocated Amount"), editable: false, width: 100, format: (value) => - "" + + "" + format_currency(value, this.currency) + "", }, { - name: "Reference Number", + name: __("Reference Number"), editable: false, width: 140, }, { - name: "Actions", + name: __("Actions"), editable: false, sortable: false, focusable: false, dropdown: false, - width: 80, + width: 100, }, ]; } @@ -118,7 +118,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { row["reference_number"], `