From 25d208aa8a1fcb535696f7da450176aacc23d675 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 6 Sep 2021 10:37:41 +0530 Subject: [PATCH 01/68] fix: GL Entries on advance TDS allocation --- erpnext/controllers/accounts_controller.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b57..515d6fdef55 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -832,7 +832,7 @@ class AccountsController(TransactionBase): if pe.reference_type == 'Payment Entry': pe = frappe.get_doc('Payment Entry', pe.reference_name) for tax in pe.get('taxes'): - allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head) + allocated_amount = flt(tax_map.get(tax.account_head)) - flt(allocated_tax_map.get(tax.account_head)) if allocated_amount > tax.tax_amount: allocated_amount = tax.tax_amount @@ -931,15 +931,19 @@ class AccountsController(TransactionBase): if pe.reference_type == "Payment Entry" and \ frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'): pe = frappe.get_doc("Payment Entry", pe.reference_name) + advance_tax_account = pe.advance_tax_account + for tax in pe.get("taxes"): account_currency = get_account_currency(tax.account_head) if self.doctype == "Purchase Invoice": - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - else: dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + advance_tax_account = pe.advance_tax_account if pe.paid_from != pe.advance_tax_account \ + else self.credit_to + else: + dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" + rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" party = self.supplier if self.doctype == "Purchase Invoice" else self.customer unallocated_amount = tax.tax_amount - tax.allocated_amount @@ -961,13 +965,15 @@ class AccountsController(TransactionBase): gl_entries.append( self.get_gl_dict({ - "account": pe.advance_tax_account, + "account": advance_tax_account, "against": party, rev_dr_cr: unallocated_amount, rev_dr_cr + "_in_account_currency": unallocated_amount if account_currency==self.company_currency else unallocated_amount, - "cost_center": tax.cost_center + "cost_center": self.get('cost_center') if advance_tax_account == self.get('credit_to') else tax.cost_center, + "party_type": 'Supplier' if advance_tax_account == self.get('credit_to') else '', + "party": self.get('supplier') if advance_tax_account == self.get('credit_to') else '', }, account_currency, item=tax)) frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount", From 27236b7e9ee7a325dfcf75c7588ad31747a04ec1 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 28 Oct 2021 16:45:23 +0530 Subject: [PATCH 02/68] fix: Remove RM Cost column as cost is not retrievable from Job card --- .../cost_of_poor_quality_report.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index 0dcad448d79..c79ded68042 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -1,8 +1,6 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from frappe import _ from frappe.utils import flt @@ -31,7 +29,7 @@ def get_data(report_filters): for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - update_raw_material_cost(row, report_filters) + # update_raw_material_cost(row, report_filters) data.append(row) return data @@ -47,11 +45,11 @@ def get_filters(report_filters, operations): return filters -def update_raw_material_cost(row, filters): - row.rm_cost = 0.0 - for data in frappe.get_all("Job Card Item", fields = ["amount"], - filters={"parent": row.name, "docstatus": 1}): - row.rm_cost += data.amount +# def update_raw_material_cost(row, filters): +# row.rm_cost = 0.0 +# for data in frappe.get_all("Job Card Item", fields = ["amount"], +# filters={"parent": row.name, "docstatus": 1}): +# row.rm_cost += data.amount def get_columns(filters): return [ @@ -60,7 +58,7 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": "name", "options": "Job Card", - "width": "100" + "width": "120" }, { "label": _("Work Order"), @@ -112,18 +110,18 @@ def get_columns(filters): "label": _("Operating Cost"), "fieldtype": "Currency", "fieldname": "operating_cost", - "width": "100" - }, - { - "label": _("Raw Material Cost"), - "fieldtype": "Currency", - "fieldname": "rm_cost", - "width": "100" + "width": "150" }, + # { + # "label": _("Raw Material Cost"), + # "fieldtype": "Currency", + # "fieldname": "rm_cost", + # "width": "100" + # }, { "label": _("Total Time (in Mins)"), "fieldtype": "Float", "fieldname": "total_time_in_mins", - "width": "100" + "width": "150" } ] From 8502ccb5b2bd9306ce0ecdb49f4710a66c11860a Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 28 Oct 2021 16:53:45 +0530 Subject: [PATCH 03/68] chore: Add comment hinting to reason --- .../cost_of_poor_quality_report/cost_of_poor_quality_report.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index c79ded68042..e6666f00bf5 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -45,6 +45,8 @@ def get_filters(report_filters, operations): return filters +# Check PR #28123 as to why this is commented + # def update_raw_material_cost(row, filters): # row.rm_cost = 0.0 # for data in frappe.get_all("Job Card Item", fields = ["amount"], From c78b8b7897152ea5daba9bfa0005a294a40c5670 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 9 Nov 2021 14:46:45 +0530 Subject: [PATCH 04/68] fix: Shipping Rule picking up old net_rate --- erpnext/public/js/controllers/taxes_and_totals.js | 3 +++ erpnext/public/js/controllers/transaction.js | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b5a6d8fdf65..0ada6601bf5 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -268,6 +268,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { }); frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); + if(frappe.meta.get_docfield(this.frm.doc.doctype,"shipping_rule",this.frm.doc.name)) { + this.shipping_rule() + } } add_taxes_from_item_tax_template(item_tax_map) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 63fd8a1c67c..0cfc008c13a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1085,16 +1085,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return this.frm.call({ doc: this.frm.doc, method: "apply_shipping_rule", - callback: function(r) { - if(!r.exc) { - me.calculate_taxes_and_totals(); - } - } }).fail(() => this.frm.set_value('shipping_rule', '')); } - else { - me.calculate_taxes_and_totals(); - } } set_margin_amount_based_on_currency(exchange_rate) { From 68dc42fb1fe47185777babff93a8e24ee99b0762 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 10 Nov 2021 10:12:29 +0530 Subject: [PATCH 05/68] fix: function name [is_work_order_stopped -> is_work_order_closed] --- erpnext/manufacturing/doctype/job_card/job_card.js | 2 +- erpnext/manufacturing/doctype/job_card/job_card.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index e3eed92d7e1..453ad50e8e6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -28,7 +28,7 @@ frappe.ui.form.on('Job Card', { frappe.flags.resume_job = 0; let has_items = frm.doc.items && frm.doc.items.length; - if (frm.doc.__onload.work_order_stopped) { + if (frm.doc.__onload.work_order_closed) { frm.disable_save(); return; } diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 5eea032be59..d6ae48995bc 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -36,7 +36,7 @@ class JobCard(Document): def onload(self): excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") self.set_onload("job_card_excess_transfer", excess_transfer) - self.set_onload("work_order_stopped", self.is_work_order_stopped()) + self.set_onload("work_order_closed", self.is_work_order_closed()) def validate(self): self.validate_time_logs() @@ -549,10 +549,10 @@ class JobCard(Document): .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError) def validate_work_order(self): - if self.is_work_order_stopped(): + if self.is_work_order_closed(): frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped.")) - def is_work_order_stopped(self): + def is_work_order_closed(self): if self.work_order: status = frappe.get_value('Work Order', self.work_order) From 1a7851b1ad53808b1ec73dca841261864e41ae4c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 10 Nov 2021 14:55:13 +0530 Subject: [PATCH 06/68] fix: error message --- erpnext/manufacturing/doctype/job_card/job_card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d6ae48995bc..90792c3c53e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -550,7 +550,7 @@ class JobCard(Document): def validate_work_order(self): if self.is_work_order_closed(): - frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped.")) + frappe.throw(_("You can't make any changes to Job Card since Work Order is closed.")) def is_work_order_closed(self): if self.work_order: From 18ae03d9675ada7f0ab7affff72604709ce0aa76 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 10 Nov 2021 15:57:41 +0530 Subject: [PATCH 07/68] fix: calling shipping rule method during net_total calculation in taxes_adn_totals.py --- erpnext/controllers/taxes_and_totals.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 70cc8a58bfe..8cca7ddc72c 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -259,6 +259,10 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) + if hasattr(self.doc, "shipping_rule"): + shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) + shipping_rule.apply(self.doc) + def calculate_taxes(self): self.doc.rounding_adjustment = 0 # maintain actual tax rate based on idx From af1fce0419bddd0e63f58cde3c2610d73752ef81 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 10 Nov 2021 16:49:12 +0530 Subject: [PATCH 08/68] fix: check if shipping rule value exists --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 8cca7ddc72c..1b192b7a328 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -259,7 +259,7 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) - if hasattr(self.doc, "shipping_rule"): + if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule: shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) shipping_rule.apply(self.doc) From 3aaf7cb4084892810cca8e7952419ed4a1c42237 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 02:33:12 +0530 Subject: [PATCH 09/68] fix: Remove default Totals row --- erpnext/accounts/report/gross_profit/gross_profit.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json index 5fff3fdba77..76c560ad247 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.json +++ b/erpnext/accounts/report/gross_profit/gross_profit.json @@ -9,7 +9,7 @@ "filters": [], "idx": 3, "is_standard": "Yes", - "modified": "2021-08-19 18:57:07.468202", + "modified": "2021-11-13 19:14:23.730198", "modified_by": "Administrator", "module": "Accounts", "name": "Gross Profit", From 582a7ae9641b7693899a63809598bb2d9abafd46 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 02:33:51 +0530 Subject: [PATCH 10/68] fix: Add totals row when Grouped By Invoice --- .../report/gross_profit/gross_profit.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 9d5a24227c6..5716350739a 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -245,19 +245,28 @@ class GrossProfitGenerator(object): self.add_to_totals(new_row) else: for i, row in enumerate(self.grouped[key]): - if row.parent in self.returned_invoices \ - and row.item_code in self.returned_invoices[row.parent]: - returned_item_rows = self.returned_invoices[row.parent][row.item_code] - for returned_item_row in returned_item_rows: - row.qty += flt(returned_item_row.qty) - row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) - row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) - if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): - row = self.set_average_rate(row) - self.grouped_data.append(row) - self.add_to_totals(row) + if row.indent == 1.0: + if row.parent in self.returned_invoices \ + and row.item_code in self.returned_invoices[row.parent]: + returned_item_rows = self.returned_invoices[row.parent][row.item_code] + for returned_item_row in returned_item_rows: + row.qty += flt(returned_item_row.qty) + row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) + row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) + if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): + row = self.set_average_rate(row) + self.grouped_data.append(row) + self.add_to_totals(row) + self.set_average_gross_profit(self.totals) - self.grouped_data.append(self.totals) + + if self.filters.get("group_by") == "Invoice": + self.totals.indent = 0.0 + self.totals.parent_invoice = "" + self.totals.parent = "Totals" + self.si_list.append(self.totals) + else: + self.grouped_data.append(self.totals) def is_not_invoice_row(self, row): return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice" From c7ffe3a7b423d08a16ef442abd1594983a3e4887 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 02:36:58 +0530 Subject: [PATCH 11/68] fix: Remove unnecessary condition --- 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 5716350739a..c7d794ea1ef 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -253,7 +253,7 @@ class GrossProfitGenerator(object): row.qty += flt(returned_item_row.qty) row.base_amount += flt(returned_item_row.base_amount, self.currency_precision) row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision) - if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row): + if (flt(row.qty) or row.base_amount): row = self.set_average_rate(row) self.grouped_data.append(row) self.add_to_totals(row) From 508a2301ee041e806a26bc3a09b01ac115ed8204 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 03:22:04 +0530 Subject: [PATCH 12/68] fix: Display totals row in bold --- erpnext/accounts/report/gross_profit/gross_profit.js | 2 +- erpnext/accounts/report/gross_profit/gross_profit.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 856b97d1645..685f2d6176b 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -44,7 +44,7 @@ frappe.query_reports["Gross Profit"] = { "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (data && data.indent == 0.0) { + if (data && (data.indent == 0.0 || row[1].content == "Total")) { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); value = $value.wrap("

").parent().html(); diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index c7d794ea1ef..7e4a1dfa0fd 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -77,7 +77,8 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_ row.append(filters.currency) if idx == len(gross_profit_data.grouped_data)-1: - row[0] = frappe.bold("Total") + row[0] = "Total" + data.append(row) def get_columns(group_wise_columns, filters): From 1affa12b74443a3c7078f767503db2cdc6ee0826 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 16 Nov 2021 03:36:47 +0530 Subject: [PATCH 13/68] fix: Replace 'Totals' with 'Total' --- 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 7e4a1dfa0fd..a6fb6f505b5 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -264,7 +264,7 @@ class GrossProfitGenerator(object): if self.filters.get("group_by") == "Invoice": self.totals.indent = 0.0 self.totals.parent_invoice = "" - self.totals.parent = "Totals" + self.totals.parent = "Total" self.si_list.append(self.totals) else: self.grouped_data.append(self.totals) From a8e2c02e146a197590547d70ce2a93eda04f3a8f Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 16 Nov 2021 19:06:49 +0530 Subject: [PATCH 14/68] fix: fixed tests, separated a method for shipping charges --- .../purchase_invoice/test_purchase_invoice.py | 21 ++----------------- .../sales_invoice/test_sales_invoice.py | 20 ++---------------- erpnext/controllers/taxes_and_totals.py | 2 ++ .../public/js/controllers/taxes_and_totals.js | 3 +++ 4 files changed, 9 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 6e81c6d8b80..774d70ba632 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -811,29 +811,12 @@ class TestPurchaseInvoice(unittest.TestCase): pi.shipping_rule = shipping_rule.name pi.insert() - - shipping_amount = 0.0 - for condition in shipping_rule.get("conditions"): - if not condition.to_value or (flt(condition.from_value) <= pi.net_total <= flt(condition.to_value)): - shipping_amount = condition.shipping_amount - - shipping_charge = { - "doctype": "Purchase Taxes and Charges", - "category": "Valuation and Total", - "charge_type": "Actual", - "account_head": shipping_rule.account, - "cost_center": shipping_rule.cost_center, - "tax_amount": shipping_amount, - "description": shipping_rule.name, - "add_deduct_tax": "Add" - } - pi.append("taxes", shipping_charge) pi.save() self.assertEqual(pi.net_total, 1250) - self.assertEqual(pi.total_taxes_and_charges, 462.3) - self.assertEqual(pi.grand_total, 1712.3) + self.assertEqual(pi.total_taxes_and_charges, 354.1) + self.assertEqual(pi.grand_total, 1604.1) def test_make_pi_without_terms(self): pi = make_purchase_invoice(do_not_save=1) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 969756addf2..02e2416c261 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1603,28 +1603,12 @@ class TestSalesInvoice(unittest.TestCase): si.shipping_rule = shipping_rule.name si.insert() - - shipping_amount = 0.0 - for condition in shipping_rule.get("conditions"): - if not condition.to_value or (flt(condition.from_value) <= si.net_total <= flt(condition.to_value)): - shipping_amount = condition.shipping_amount - - shipping_charge = { - "doctype": "Sales Taxes and Charges", - "category": "Valuation and Total", - "charge_type": "Actual", - "account_head": shipping_rule.account, - "cost_center": shipping_rule.cost_center, - "tax_amount": shipping_amount, - "description": shipping_rule.name - } - si.append("taxes", shipping_charge) si.save() self.assertEqual(si.net_total, 1250) - self.assertEqual(si.total_taxes_and_charges, 577.05) - self.assertEqual(si.grand_total, 1827.05) + self.assertEqual(si.total_taxes_and_charges, 468.85) + self.assertEqual(si.grand_total, 1718.85) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index e30a0a1967c..746c6fd9a42 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -50,6 +50,7 @@ class calculate_taxes_and_totals(object): self.initialize_taxes() self.determine_exclusive_rate() self.calculate_net_total() + self.calculate_shipping_charges() self.calculate_taxes() self.manipulate_grand_total_for_inclusive_tax() self.calculate_totals() @@ -258,6 +259,7 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) + def calculate_shipping_charges(self): if hasattr(self.doc, "shipping_rule") and self.doc.shipping_rule: shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule) shipping_rule.apply(self.doc) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 0ada6601bf5..f4c4c6be95f 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -81,6 +81,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); + calculate_shipping_charges(); this.calculate_taxes(); this.manipulate_grand_total_for_inclusive_tax(); this.calculate_totals(); @@ -267,6 +268,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { me.frm.doc.base_net_total += item.base_net_amount; }); + } + calculate_shipping_charges() { frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); if(frappe.meta.get_docfield(this.frm.doc.doctype,"shipping_rule",this.frm.doc.name)) { this.shipping_rule() From e7b4204c35abb33d0101c96d0ade9ca85acb875a Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 16 Nov 2021 20:39:58 +0530 Subject: [PATCH 15/68] fix: sider issues --- erpnext/public/js/controllers/taxes_and_totals.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index f4c4c6be95f..7c1c8c7e46e 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -81,7 +81,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); - calculate_shipping_charges(); + this.calculate_shipping_charges(); this.calculate_taxes(); this.manipulate_grand_total_for_inclusive_tax(); this.calculate_totals(); @@ -267,12 +267,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { me.frm.doc.net_total += item.net_amount; me.frm.doc.base_net_total += item.base_net_amount; }); + } - } calculate_shipping_charges() { frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); - if(frappe.meta.get_docfield(this.frm.doc.doctype,"shipping_rule",this.frm.doc.name)) { - this.shipping_rule() + if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) { + this.shipping_rule(); } } From 488d34af1f88724dd5a6bd298c949ac3c39e5e1b Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Thu, 18 Nov 2021 13:57:13 +0530 Subject: [PATCH 16/68] fix: unchecking the "With Operations" in BOM clears operations table #28446 --- erpnext/manufacturing/doctype/bom/bom.js | 6 ------ erpnext/manufacturing/doctype/bom/bom.json | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 5f5c20a595a..6d35d65bea9 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -680,12 +680,6 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) { erpnext.bom.calculate_total(frm.doc); }); -frappe.ui.form.on("BOM", "with_operations", function(frm) { - if(!cint(frm.doc.with_operations)) { - frm.set_value("operations", []); - } -}); - frappe.tour['BOM'] = [ { fieldname: "item", diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 62187077f3d..218ac64d8da 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -237,6 +237,7 @@ "options": "Price List" }, { + "depends_on": "with_operations", "fieldname": "operations_section", "fieldtype": "Section Break", "hide_border": 1, @@ -539,7 +540,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-10-27 14:52:04.500251", + "modified": "2021-11-18 13:04:16.271975", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", From f8e371af14ebc4d25786ab88d33be7a574231cec Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 11 Nov 2021 20:04:11 +0530 Subject: [PATCH 17/68] fix(ux): ordering fields for better tab order --- .../course_scheduling_tool.json | 773 ++++-------------- 1 file changed, 140 insertions(+), 633 deletions(-) diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json index 2926fe8af34..13dfe38eead 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.json @@ -1,661 +1,168 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2015-09-23 15:37:38.108475", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "allow_copy": 1, + "creation": "2015-09-23 15:37:38.108475", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "student_group", + "course", + "program", + "column_break_3", + "academic_year", + "academic_term", + "section_break_6", + "instructor", + "instructor_name", + "column_break_9", + "room", + "section_break_7", + "course_start_date", + "course_end_date", + "day", + "reschedule", + "column_break_15", + "from_time", + "to_time" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_group", - "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": "Student Group", - "length": 0, - "no_copy": 0, - "options": "Student Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "student_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Group", + "options": "Student Group", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "course", - "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": "Course", - "length": 0, - "no_copy": 0, - "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course", + "options": "Course", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "program", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Program", - "length": 0, - "no_copy": 0, - "options": "Program", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "program", + "fieldtype": "Link", + "label": "Program", + "options": "Program", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "academic_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Academic Year", - "length": 0, - "no_copy": 0, - "options": "Academic Year", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "academic_term", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Academic Term", - "length": 0, - "no_copy": 0, - "options": "Academic Term", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "instructor", - "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": "Instructor", - "length": 0, - "no_copy": 0, - "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "instructor", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instructor", + "options": "Instructor", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "instructor.instructor_name", - "fieldname": "instructor_name", - "fieldtype": "Read Only", - "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": "Instructor Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "instructor_name", + "fieldtype": "Read Only", + "label": "Instructor Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "room", - "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": "Room", - "length": 0, - "no_copy": 0, - "options": "Room", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "room", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Room", + "options": "Room", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "from_time", - "fieldtype": "Time", - "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": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "from_time", + "fieldtype": "Time", + "label": "From Time", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "course_start_date", - "fieldtype": "Date", - "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": "Course Start Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_start_date", + "fieldtype": "Date", + "label": "Course Start Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "day", - "fieldtype": "Select", - "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": "Day", - "length": 0, - "no_copy": 0, - "options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "day", + "fieldtype": "Select", + "label": "Day", + "options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reschedule", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reschedule", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "reschedule", + "fieldtype": "Check", + "label": "Reschedule" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_15", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_time", - "fieldtype": "Time", - "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": "To TIme", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "to_time", + "fieldtype": "Time", + "label": "To TIme", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "course_end_date", - "fieldtype": "Date", - "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": "Course End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "course_end_date", + "fieldtype": "Date", + "label": "Course End Date", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 1, - "hide_toolbar": 1, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-05-16 22:43:29.363798", - "modified_by": "Administrator", - "module": "Education", - "name": "Course Scheduling Tool", - "name_case": "", - "owner": "Administrator", + ], + "hide_toolbar": 1, + "issingle": 1, + "links": [], + "modified": "2021-11-11 09:33:18.874445", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Scheduling Tool", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Academics User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "read": 1, + "role": "Academics User", "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From a38aca5a548fa297ba5cff60b2ac1b8d6aae564e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Nov 2021 11:03:13 +0530 Subject: [PATCH 18/68] fix(India): GST category not getting auto updated (cherry picked from commit f8a26a9fac2c91b036f1dd4033f3fb4797285043) --- erpnext/regional/india/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 54d592a6500..4bd91951916 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -74,11 +74,11 @@ def validate_tax_category(doc, method): frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state)) def update_gst_category(doc, method): - if hasattr(doc, 'gst_category'): - for link in doc.links: - if link.link_doctype in ['Customer', 'Supplier']: - if doc.get('gstin'): - frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular') + for link in doc.links: + if link.link_doctype in ['Customer', 'Supplier']: + meta = frappe.get_meta(link.link_doctype) + if doc.get('gstin') and meta.has_field('gst_category'): + frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular') def set_gst_state_and_state_number(doc): if not doc.gst_state: From ff65399ae9c7ead884cfcfcc7289efd2baff6a95 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 19 Nov 2021 11:58:44 +0530 Subject: [PATCH 19/68] fix: Add test for gst category check (cherry picked from commit cdbc991e3f97c7ed3e120b2e4720298402d1de77) --- .../gstr_3b_report/test_gstr_3b_report.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 0f0c0b99158..e12e3d7b800 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -103,6 +103,45 @@ class TestGSTR3BReport(unittest.TestCase): gst_settings.round_off_gst_values = 1 gst_settings.save() + def test_gst_category_auto_update(self): + if not frappe.db.exists("Customer", "_Test GST Customer With GSTIN"): + customer = frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test GST Customer With GSTIN", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + self.assertEqual(customer.gst_category, 'Unregistered') + + if not frappe.db.exists('Address', '_Test GST Category-1-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test GST Category-1", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gstin": "29AZWPS7135H1ZG", + "gst_state": "Karnataka", + "gst_state_number": "29" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test GST Customer With GSTIN" + }) + + address.save() + + customer.load_from_db() + self.assertEqual(customer.gst_category, 'Registered Regular') + + def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", customer = '_Test GST Customer', From 350ed1a6c21a3ca63de02b1a8ed741c51b254ad1 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Fri, 19 Nov 2021 21:05:25 +0530 Subject: [PATCH 20/68] fix: FY query returning None for new company --- erpnext/regional/india/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 316bb6b1977..58654240285 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -791,7 +791,7 @@ def set_tax_withholding_category(company): accounts = [dict(company=company, account=tds_account)] try: - fiscal_year_details = get_fiscal_year(today(), verbose=0, company=company) + fiscal_year_details = get_fiscal_year(today(), verbose=0) except FiscalYearError: pass From ac27391a1f5e3cd31baa4c372e3b24ecdd0e05a3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 20 Nov 2021 11:22:19 +0530 Subject: [PATCH 21/68] fix: bug with qrcode generation due to default print format name --- erpnext/regional/saudi_arabia/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index cc6c0af7a56..0c036f905ce 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -28,14 +28,22 @@ def create_qr_code(doc, method): for field in meta.get_image_fields(): if field.fieldname == 'qr_code': + from urllib.parse import urlencode + # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") # System Language language = frappe.get_system_settings('language') + params = urlencode({ + 'format': default_print_format or 'Standard', + '_lang': language, + 'key': doc.get_signature() + }) + # creating qr code for the url - url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }" qr_image = io.BytesIO() url = qr_create(url, error='L') url.png(qr_image, scale=2, quiet_zone=1) From 08ad93d82f875e1098b54e58f77d46061f82381b Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 20 Nov 2021 15:15:46 +0530 Subject: [PATCH 22/68] fix: coa balance rendering bug (#28468) --- erpnext/accounts/doctype/account/account_tree.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index a4b6e0b45ae..b9ebb58b43e 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -78,6 +78,7 @@ frappe.treeview_settings["Account"] = { const format = (value, currency) => format_currency(Math.abs(value), currency); if (account.balance!==undefined) { + node.parent && node.parent.find('.balance-area').remove(); $('' + (account.balance_in_account_currency ? (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") From 624e58d1dee7f35bcd99c57baa46b2bf3f1d4aa3 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 22 Nov 2021 11:36:35 +0530 Subject: [PATCH 23/68] fix: Filter out cancelled and non-depreciable Assets in Asset Value Adjustment (#28443) --- .../asset_value_adjustment/asset_value_adjustment.js | 8 ++++++++ .../asset_value_adjustment/asset_value_adjustment.py | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js index 79c8861bcdc..36f510b18ee 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js @@ -14,6 +14,14 @@ frappe.ui.form.on('Asset Value Adjustment', { } } }); + frm.set_query('asset', function() { + return { + filters: { + calculate_depreciation: 1, + docstatus: 1 + } + }; + }); }, onload: function(frm) { diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index b93f474a789..0b646ed4ede 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -10,7 +10,11 @@ from frappe.utils import cint, date_diff, flt, formatdate, getdate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) +from erpnext.assets.doctype.asset.asset import get_depreciation_amount from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts +from erpnext.regional.india.utils import ( + get_depreciation_amount as get_depreciation_amount_for_india, +) class AssetValueAdjustment(Document): @@ -90,6 +94,7 @@ class AssetValueAdjustment(Document): def reschedule_depreciations(self, asset_value): asset = frappe.get_doc('Asset', self.asset) + country = frappe.get_value('Company', self.company, 'country') for d in asset.finance_books: d.value_after_depreciation = asset_value @@ -111,8 +116,10 @@ class AssetValueAdjustment(Document): depreciation_amount = days * rate_per_day from_date = data.schedule_date else: - depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, - no_of_depreciations, d) + if country == "India": + depreciation_amount = get_depreciation_amount_for_india(asset, value_after_depreciation, d) + else: + depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d) if depreciation_amount: value_after_depreciation -= flt(depreciation_amount) From 3a2074ce05d3b89b5f740ddca9d2edbfb39a6c06 Mon Sep 17 00:00:00 2001 From: Ahmed Shareef Date: Mon, 22 Nov 2021 10:16:58 +0400 Subject: [PATCH 24/68] fix: add child button in coa tree (#28413) --- erpnext/accounts/doctype/account/account_tree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index b9ebb58b43e..a3ef38465ec 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -176,7 +176,7 @@ frappe.treeview_settings["Account"] = { && node.expandable && !node.hide_add; }, click: function() { - var me = frappe.treeview_settings['Account'].treeview; + var me = frappe.views.trees['Account']; me.new_node(); }, btnClass: "hidden-xs" From 78851ecb70a46024b7a1995252f5312dfd6e8aac Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 22 Nov 2021 12:23:23 +0530 Subject: [PATCH 25/68] feat: create party link from customer/supplier (#28387) * feat: create party link from customer/supplier * test: create_party_link method --- .../accounts_settings/accounts_settings.py | 3 ++ .../accounts/doctype/party_link/party_link.py | 14 ++++++ .../sales_invoice/test_sales_invoice.py | 9 +--- erpnext/buying/doctype/supplier/supplier.js | 43 +++++++++++++++++++ erpnext/selling/doctype/customer/customer.js | 43 +++++++++++++++++++ 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index aa132a07d0c..745191712b2 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -19,6 +19,9 @@ class AccountsSettings(Document): frappe.db.set_default("add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)) + frappe.db.set_default("enable_common_party_accounting", + self.get("enable_common_party_accounting", 0)) + self.validate_stale_days() self.enable_payment_schedule_in_print() self.toggle_discount_accounting_fields() diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py index daf667caf0c..e9f813c17c6 100644 --- a/erpnext/accounts/doctype/party_link/party_link.py +++ b/erpnext/accounts/doctype/party_link/party_link.py @@ -25,3 +25,17 @@ class PartyLink(Document): if existing_party_link: frappe.throw(_('{} {} is already linked with another {}') .format(self.primary_role, self.primary_party, existing_party_link[0])) + + +@frappe.whitelist() +def create_party_link(primary_role, primary_party, secondary_party): + party_link = frappe.new_doc('Party Link') + party_link.primary_role = primary_role + party_link.primary_party = primary_party + party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier' + party_link.secondary_party = secondary_party + + party_link.save(ignore_permissions=True) + + return party_link + diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 02e2416c261..b5453ac56eb 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2300,6 +2300,7 @@ class TestSalesInvoice(unittest.TestCase): from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( make_customer, ) + from erpnext.accounts.doctype.party_link.party_link import create_party_link from erpnext.buying.doctype.supplier.test_supplier import create_supplier # create a customer @@ -2308,13 +2309,7 @@ class TestSalesInvoice(unittest.TestCase): supplier = create_supplier(supplier_name="_Test Common Supplier").name # create a party link between customer & supplier - # set primary role as supplier - party_link = frappe.new_doc("Party Link") - party_link.primary_role = "Supplier" - party_link.primary_party = supplier - party_link.secondary_role = "Customer" - party_link.secondary_party = customer - party_link.save() + party_link = create_party_link("Supplier", supplier, customer) # enable common party accounting frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1) diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 7ee91961ca5..f0899b06b57 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -83,6 +83,12 @@ frappe.ui.form.on("Supplier", { frm.trigger("get_supplier_group_details"); }, __('Actions')); + if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) { + frm.add_custom_button(__('Link with Customer'), function () { + frm.trigger('show_party_link_dialog'); + }, __('Actions')); + } + // indicators erpnext.utils.set_party_dashboard_indicators(frm); } @@ -128,5 +134,42 @@ frappe.ui.form.on("Supplier", { else { frm.toggle_reqd("represents_company", false); } + }, + show_party_link_dialog: function(frm) { + const dialog = new frappe.ui.Dialog({ + title: __('Select a Customer'), + fields: [{ + fieldtype: 'Link', label: __('Customer'), + options: 'Customer', fieldname: 'customer', reqd: 1 + }], + primary_action: function({ customer }) { + frappe.call({ + method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link', + args: { + primary_role: 'Supplier', + primary_party: frm.doc.name, + secondary_party: customer + }, + freeze: true, + callback: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Successfully linked to Customer'), + alert: true + }); + }, + error: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Linking to Customer Failed. Please try again.'), + title: __('Linking Failed'), + indicator: 'red' + }); + } + }); + }, + primary_action_label: __('Create Link') + }); + dialog.show(); } }); diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 4b0bbd5a114..107e4a4759d 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -134,6 +134,12 @@ frappe.ui.form.on("Customer", { frm.trigger("get_customer_group_details"); }, __('Actions')); + if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) { + frm.add_custom_button(__('Link with Supplier'), function () { + frm.trigger('show_party_link_dialog'); + }, __('Actions')); + } + // indicator erpnext.utils.set_party_dashboard_indicators(frm); @@ -158,5 +164,42 @@ frappe.ui.form.on("Customer", { } }); + }, + show_party_link_dialog: function(frm) { + const dialog = new frappe.ui.Dialog({ + title: __('Select a Supplier'), + fields: [{ + fieldtype: 'Link', label: __('Supplier'), + options: 'Supplier', fieldname: 'supplier', reqd: 1 + }], + primary_action: function({ supplier }) { + frappe.call({ + method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link', + args: { + primary_role: 'Customer', + primary_party: frm.doc.name, + secondary_party: supplier + }, + freeze: true, + callback: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Successfully linked to Supplier'), + alert: true + }); + }, + error: function() { + dialog.hide(); + frappe.msgprint({ + message: __('Linking to Supplier Failed. Please try again.'), + title: __('Linking Failed'), + indicator: 'red' + }); + } + }); + }, + primary_action_label: __('Create Link') + }); + dialog.show(); } }); From cb52e6dd6bb437b3cfe34d22a7b27b24e00b0580 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 22 Nov 2021 12:24:32 +0530 Subject: [PATCH 26/68] fix: cost center wise ledger posting for pcv (#28477) --- .../period_closing_voucher.py | 3 ++- .../test_period_closing_voucher.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 34572fdfbf6..d0e555e40c7 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -88,9 +88,10 @@ class PeriodClosingVoucher(AccountsController): for acc in pl_accounts: if flt(acc.bal_in_company_currency): + cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center gl_entry = self.get_gl_dict({ "account": self.closing_account_head, - "cost_center": acc.cost_center or company_cost_center, + "cost_center": cost_center, "finance_book": acc.finance_book, "account_currency": acc.account_currency, "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 0e29755aedd..030b4caf7ca 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -66,8 +66,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): company = create_company() surplus_account = create_account() - cost_center1 = create_cost_center("Test Cost Center 1") - cost_center2 = create_cost_center("Test Cost Center 2") + cost_center1 = create_cost_center("Main") + cost_center2 = create_cost_center("Western Branch") create_sales_invoice( company=company, @@ -86,7 +86,10 @@ class TestPeriodClosingVoucher(unittest.TestCase): debit_to="Debtors - TPC" ) - pcv = self.make_period_closing_voucher() + pcv = self.make_period_closing_voucher(submit=False) + pcv.cost_center_wise_pnl = 1 + pcv.save() + pcv.submit() surplus_account = pcv.closing_account_head expected_gle = ( @@ -149,7 +152,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertEqual(pcv_gle, expected_gle) - def make_period_closing_voucher(self): + def make_period_closing_voucher(self, submit=True): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc({ @@ -163,7 +166,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): "remarks": "test" }) pcv.insert() - pcv.submit() + if submit: + pcv.submit() return pcv From 7d0558e202e1ef3f3a9adf540e3eb32bff986506 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 22 Nov 2021 12:34:46 +0530 Subject: [PATCH 27/68] fix: Add extra column to display Invoice or Item when grouped by Invoice (#28380) --- .../accounts/report/gross_profit/gross_profit.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index a6fb6f505b5..20bc3ec1158 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -19,7 +19,7 @@ def execute(filters=None): data = [] group_wise_columns = frappe._dict({ - "invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \ + "invoice": ["invoice_or_item", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", "warehouse", "qty", "base_rate", "buying_rate", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent", "project"], "item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate", @@ -85,6 +85,7 @@ def get_columns(group_wise_columns, filters): columns = [] column_map = frappe._dict({ "parent": _("Sales Invoice") + ":Link/Sales Invoice:120", + "invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120", "posting_date": _("Posting Date") + ":Date:100", "posting_time": _("Posting Time") + ":Data:100", "item_code": _("Item Code") + ":Link/Item:100", @@ -123,7 +124,7 @@ def get_columns(group_wise_columns, filters): def get_column_names(): return frappe._dict({ - 'parent': 'sales_invoice', + 'invoice_or_item': 'sales_invoice', 'customer': 'customer', 'customer_group': 'customer_group', 'posting_date': 'posting_date', @@ -456,7 +457,7 @@ class GrossProfitGenerator(object): if not row.indent: row.indent = 1.0 row.parent_invoice = row.parent - row.parent = row.item_code + row.invoice_or_item = row.item_code if frappe.db.exists('Product Bundle', row.item_code): self.add_bundle_items(row, index) @@ -465,7 +466,8 @@ class GrossProfitGenerator(object): return frappe._dict({ 'parent_invoice': "", 'indent': 0.0, - 'parent': row.parent, + 'invoice_or_item': row.parent, + 'parent': None, 'posting_date': row.posting_date, 'posting_time': row.posting_time, 'project': row.project, @@ -509,7 +511,8 @@ class GrossProfitGenerator(object): return frappe._dict({ 'parent_invoice': product_bundle.item_code, 'indent': product_bundle.indent + 1, - 'parent': item.item_code, + 'parent': None, + 'invoice_or_item': item.item_code, 'posting_date': product_bundle.posting_date, 'posting_time': product_bundle.posting_time, 'project': product_bundle.project, From 42f1dd98dde623f42d7eae431b22cbde83fd357f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Nov 2021 12:48:36 +0530 Subject: [PATCH 28/68] fix: restrict repost valuation to manager roles --- .../repost_item_valuation.json | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index a800bf87013..3ff0f60b3e4 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -177,10 +177,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-07-22 18:59:43.057878", + "modified": "2021-11-18 02:18:10.524560", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -197,20 +198,6 @@ "submit": 1, "write": 1 }, - { - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "share": 1, - "submit": 1, - "write": 1 - }, { "cancel": 1, "create": 1, @@ -226,7 +213,6 @@ "write": 1 }, { - "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -234,7 +220,7 @@ "print": 1, "read": 1, "report": 1, - "role": "Accounts User", + "role": "Accounts Manager", "share": 1, "submit": 1, "write": 1 From aa024fc9da3e3f93baebb7ccf9ddf220c782157e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Nov 2021 12:51:26 +0530 Subject: [PATCH 29/68] fix: ignore permissions while creating repost If user reached this code then they already have permission to create stock transaction, hence ignore permission checks while creating/cancelling repost item valuation entries. --- erpnext/controllers/stock_controller.py | 1 + erpnext/stock/stock_ledger.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 08d422d3bcd..aba15b47e3a 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -676,5 +676,6 @@ def create_repost_item_valuation_entry(args): repost_entry.company = args.company repost_entry.allow_zero_rate = args.allow_zero_rate repost_entry.flags.ignore_links = True + repost_entry.flags.ignore_permissions = True repost_entry.save() repost_entry.submit() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9c4c6761927..9d409827db1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -111,6 +111,7 @@ def validate_cancellation(args): frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet.")) if repost_entry.status == 'Queued': doc = frappe.get_doc("Repost Item Valuation", repost_entry.name) + doc.flags.ignore_permissions = True doc.cancel() doc.delete() From c436e30ee6895797abec3649e5f08b1003164b52 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Nov 2021 12:56:49 +0530 Subject: [PATCH 30/68] fix: don't use cached doc while reposting --- .../doctype/repost_item_valuation/repost_item_valuation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 170aa7f76c1..59d191fa07f 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -133,7 +133,7 @@ def repost_entries(): riv_entries = get_repost_item_valuation_entries() for row in riv_entries: - doc = frappe.get_cached_doc('Repost Item Valuation', row.name) + doc = frappe.get_doc('Repost Item Valuation', row.name) repost(doc) riv_entries = get_repost_item_valuation_entries() From aa689874e371d29c4295146a2eefb48acb847b23 Mon Sep 17 00:00:00 2001 From: Sagar Sharma <63660334+s-aga-r@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:06:59 +0530 Subject: [PATCH 31/68] fix: Closed status error in Work Order Summary (#28460) * fix: Closed status error in Work Order Summary * chore: use get_meta to get status options * refactor: simplify code Co-authored-by: Ankush Menat --- .../work_order_summary/work_order_summary.js | 2 +- .../work_order_summary/work_order_summary.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index eb23f17c477..832be2301c1 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -51,7 +51,7 @@ frappe.query_reports["Work Order Summary"] = { label: __("Status"), fieldname: "status", fieldtype: "Select", - options: ["", "Not Started", "In Process", "Completed", "Stopped"] + options: ["", "Not Started", "In Process", "Completed", "Stopped", "Closed"] }, { label: __("Sales Orders"), 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 6207904a0ff..d7469ddfdd6 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -1,6 +1,7 @@ # 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 _ @@ -58,21 +59,16 @@ def get_chart_data(data, filters): return get_chart_based_on_qty(data, filters) def get_chart_based_on_status(data): - labels = ["Completed", "In Process", "Stopped", "Not Started"] + labels = frappe.get_meta("Work Order").get_options("status").split("\n") + if "" in labels: + labels.remove("") - status_wise_data = { - "Not Started": 0, - "In Process": 0, - "Stopped": 0, - "Completed": 0, - "Draft": 0 - } + status_wise_data = defaultdict(int) for d in data: status_wise_data[d.status] += 1 - values = [status_wise_data["Completed"], status_wise_data["In Process"], - status_wise_data["Stopped"], status_wise_data["Not Started"]] + values = [status_wise_data[label] for label in labels] chart = { "data": { From 42c40b33bcd2906d806a2e60a81037a73fb32989 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Nov 2021 13:20:43 +0530 Subject: [PATCH 32/68] fix: KSA Invoice print format for multicurrency invoices --- .../print_format/ksa_vat_invoice/ksa_vat_invoice.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json index 36d653616ba..681f72fd309 100644 --- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json +++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json @@ -2,7 +2,7 @@ "absolute_value": 0, "align_labels_right": 0, "creation": "2021-10-29 22:46:26.039023", - "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n color: #888a8e;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n background-color: #265e4a !important;\n color: #fff;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", + "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", "custom_format": 1, "default_print_language": "en", "disabled": 0, @@ -10,14 +10,14 @@ "docstatus": 0, "doctype": "Print Format", "font_size": 14, - "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format(data_object[item.item_code][1], {'fieldtype': 'Currency'}) }}\n {% set total.amount = total.amount + data_object[item.item_code][1] %}\n {%- endif -%}\n
\n
{{ frappe.format(total.amount, {'fieldtype': 'Currency'}) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", + "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set tax_amount = frappe.utils.flt(data_object[item.item_code][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n
\n
{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2021-11-08 09:19:18.660806", + "modified": "2021-11-22 10:40:24.716932", "modified_by": "Administrator", "module": "Regional", "name": "KSA VAT Invoice", From fce4f34325375c47f80c0870e43e384d404e77d9 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:34:26 +0530 Subject: [PATCH 33/68] fix: POS string translation (#28381) --- .../page/point_of_sale/pos_item_cart.js | 44 +++++++++---------- .../page/point_of_sale/pos_item_details.js | 5 ++- .../page/point_of_sale/pos_item_selector.js | 2 +- .../page/point_of_sale/pos_past_order_list.js | 2 +- .../point_of_sale/pos_past_order_summary.js | 19 ++++---- .../selling/page/point_of_sale/pos_payment.js | 10 ++--- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 9d8338e5fed..b652fdcb350 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -49,11 +49,11 @@ erpnext.PointOfSale.ItemCart = class { this.$component.append( `
-
Item Cart
+
${__('Item Cart')}
-
Item
-
Qty
-
Amount
+
${__('Item')}
+
${__('Quantity')}
+
${__('Amount')}
@@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemCart = class { make_no_items_placeholder() { this.$cart_header.css('display', 'none'); this.$cart_items_wrapper.html( - `
No items in cart
` + `
${__('No items in cart')}
` ); } @@ -98,19 +98,19 @@ erpnext.PointOfSale.ItemCart = class { this.$totals_section.append( `
- ${this.get_discount_icon()} Add Discount + ${this.get_discount_icon()} ${__('Add Discount')}
-
Net Total
+
${__("Net Total")}
0.00
-
Grand Total
+
${__('Grand Total')}
0.00
-
Checkout
-
Edit Cart
` +
${__('Checkout')}
+
${__('Edit Cart')}
` ) this.$add_discount_elem = this.$component.find(".add-discount-wrapper"); @@ -126,10 +126,10 @@ erpnext.PointOfSale.ItemCart = class { }, cols: 5, keys: [ - [ 1, 2, 3, 'Quantity' ], - [ 4, 5, 6, 'Discount' ], - [ 7, 8, 9, 'Rate' ], - [ '.', 0, 'Delete', 'Remove' ] + [ 1, 2, 3, __('Quantity') ], + [ 4, 5, 6, __('Discount') ], + [ 7, 8, 9, __('Rate') ], + [ '.', 0, __('Delete'), __('Remove') ] ], css_classes: [ [ '', '', '', 'col-span-2' ], @@ -148,7 +148,7 @@ erpnext.PointOfSale.ItemCart = class { ) this.$numpad_section.append( - `
Checkout
` + `
${__('Checkout')}
` ) } @@ -386,7 +386,7 @@ erpnext.PointOfSale.ItemCart = class { 'border': '1px dashed var(--gray-500)', 'padding': 'var(--padding-sm) var(--padding-md)' }); - me.$add_discount_elem.html(`${me.get_discount_icon()} Add Discount`); + me.$add_discount_elem.html(`${me.get_discount_icon()} ${__('Add Discount')}`); me.discount_field = undefined; } }, @@ -411,7 +411,7 @@ erpnext.PointOfSale.ItemCart = class { }); this.$add_discount_elem.html( `
- ${this.get_discount_icon()} Additional ${String(discount).bold()}% discount applied + ${this.get_discount_icon()} ${__("Additional")} ${String(discount).bold()}% ${__("discount applied")}
` ); } @@ -445,7 +445,7 @@ erpnext.PointOfSale.ItemCart = class { function get_customer_description() { if (!email_id && !mobile_no) { - return `
Click to add email / phone
`; + return `
${__('Click to add email / phone')}
`; } else if (email_id && !mobile_no) { return `
${email_id}
`; } else if (mobile_no && !email_id) { @@ -479,22 +479,22 @@ erpnext.PointOfSale.ItemCart = class { render_net_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.net-total-container').html( - `
Net Total
${format_currency(value, currency)}
` + `
${__('Net Total')}
${format_currency(value, currency)}
` ) this.$numpad_section.find('.numpad-net-total').html( - `
Net Total: ${format_currency(value, currency)}
` + `
${__('Net Total')}: ${format_currency(value, currency)}
` ); } render_grand_total(value) { const currency = this.events.get_frm().doc.currency; this.$totals_section.find('.grand-total-container').html( - `
Grand Total
${format_currency(value, currency)}
` + `
${__('Grand Total')}
${format_currency(value, currency)}
` ) this.$numpad_section.find('.numpad-grand-total').html( - `
Grand Total: ${format_currency(value, currency)}
` + `
${__('Grand Total')}: ${format_currency(value, currency)}
` ); } 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 ec861d7c531..fb69b63f82a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -28,7 +28,7 @@ erpnext.PointOfSale.ItemDetails = class { init_child_components() { this.$component.html( `
-
Item Details
+
${__('Item Details')}
@@ -201,8 +201,9 @@ erpnext.PointOfSale.ItemDetails = class { `
` ); } + const label = __('Auto Fetch Serial Numbers'); this.$form_container.append( - `
Auto Fetch Serial Numbers
` + `
${label}
` ); this.$form_container.find('.serial_no-control').find('textarea').css('height', '6rem'); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 8352b148acc..496385248c4 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -24,7 +24,7 @@ erpnext.PointOfSale.ItemSelector = class { this.wrapper.append( `
-
All Items
+
${__('All Items')}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index e0993e2e342..a0475c70d0d 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -16,7 +16,7 @@ erpnext.PointOfSale.PastOrderList = class { this.wrapper.append( `
-
Recent Orders
+
${__('Recent Orders')}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index dd9e05a0e6d..eeb8523f19c 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -17,16 +17,16 @@ erpnext.PointOfSale.PastOrderSummary = class { this.wrapper.append( `
- Select an invoice to load summary data + ${__('Select an invoice to load summary data')}
-
Items
+
${__('Items')}
-
Totals
+
${__('Totals')}
-
Payments
+
${__('Payments')}
@@ -82,7 +82,7 @@ erpnext.PointOfSale.PastOrderSummary = class { return `
${doc.customer}
${this.customer_email}
-
Sold by: ${doc.owner}
+
${__('Sold by')}: ${doc.owner}
@@ -121,7 +121,7 @@ erpnext.PointOfSale.PastOrderSummary = class { get_net_total_html(doc) { return `
-
Net Total
+
${__('Net Total')}
${format_currency(doc.net_total, doc.currency)}
`; } @@ -144,14 +144,14 @@ erpnext.PointOfSale.PastOrderSummary = class { get_grand_total_html(doc) { return `
-
Grand Total
+
${__('Grand Total')}
${format_currency(doc.grand_total, doc.currency)}
`; } get_payment_html(doc, payment) { return `
-
${payment.mode_of_payment}
+
${__(payment.mode_of_payment)}
${format_currency(payment.amount, doc.currency)}
`; } @@ -285,8 +285,9 @@ erpnext.PointOfSale.PastOrderSummary = class { if (m.condition) { m.visible_btns.forEach(b => { const class_name = b.split(' ')[0].toLowerCase(); + const btn = __(b); this.$summary_btns.append( - `
${b}
` + `
${btn}
` ); }); } diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 7ddbf45fdb8..b9b65591dc7 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -18,11 +18,11 @@ erpnext.PointOfSale.Payment = class { prepare_dom() { this.wrapper.append( `
- +
- +
@@ -30,7 +30,7 @@ erpnext.PointOfSale.Payment = class {
-
Complete Order
+
${__("Complete Order")}
` ); this.$component = this.wrapper.find('.payment-container'); @@ -518,12 +518,12 @@ erpnext.PointOfSale.Payment = class { this.$totals.html( `
-
Grand Total
+
${__('Grand Total')}
${format_currency(grand_total, currency)}
-
Paid Amount
+
${__('Paid Amount')}
${format_currency(paid_amount, currency)}
From 73c56510d8fc197188612f1714144fb175af3960 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Nov 2021 14:21:53 +0530 Subject: [PATCH 34/68] fix: Delete KSA vat setting on deleting company --- erpnext/hooks.py | 3 ++- erpnext/regional/saudi_arabia/utils.py | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a277ee0350..e8aac1d1990 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -306,7 +306,8 @@ doc_events = { 'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"] }, "Company": { - "on_trash": "erpnext.regional.india.utils.delete_gst_settings_for_company" + "on_trash": ["erpnext.regional.india.utils.delete_gst_settings_for_company", + "erpnext.regional.saudi_arabia.utils.delete_vat_settings_for_company"] }, "Integration Request": { "validate": "erpnext.accounts.doctype.payment_request.payment_request.validate_payment" diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index cc6c0af7a56..031cf03d07b 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -74,4 +74,11 @@ def delete_qr_code_file(doc, method): 'file_url': doc.get('qr_code') }) if len(file_doc): - frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file + frappe.delete_doc('File', file_doc[0].name) + +def delete_vat_settings_for_company(doc, method): + if doc.country != 'Saudi Arabia': + return + + settings_doc = frappe.get_doc('KSA VAT Setting', {'company': doc.name}) + settings_doc.delete() \ No newline at end of file From e7eef9601d2348094d16a92736c30aca8686734e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 22 Nov 2021 20:13:15 +0530 Subject: [PATCH 35/68] fix(UX): Allocated Amount in Advances not updated on updating expense amount in Expense Claim (#28497) * fix(UX): Allocated Amount in Advances not updated on updating expense amount in Expense Claim * fix: Expense Claim Advance form labels --- erpnext/hr/doctype/expense_claim/expense_claim.js | 2 ++ erpnext/hr/doctype/expense_claim/expense_claim.json | 3 ++- .../expense_claim_advance/expense_claim_advance.json | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 218e97d7fc2..665556301bb 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -389,7 +389,9 @@ frappe.ui.form.on("Expense Claim Detail", { sanctioned_amount: function(frm, cdt, cdn) { cur_frm.cscript.calculate_total(frm.doc, cdt, cdn); frm.trigger("get_taxes"); + frm.trigger("calculate_grand_total"); }, + cost_center: function(frm, cdt, cdn) { erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "expenses", "cost_center"); } diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index a268c15c70b..45b78bfb54e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -379,11 +379,12 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-04 05:35:12.040199", + "modified": "2021-11-22 16:26:57.787838", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json index 45509257c11..aa479c83084 100644 --- a/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json +++ b/erpnext/hr/doctype/expense_claim_advance/expense_claim_advance.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2017-10-09 16:53:26.410762", "doctype": "DocType", "document_type": "Document", @@ -50,7 +51,7 @@ "fieldname": "unclaimed_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Unclaimed amount", + "label": "Unclaimed Amount", "no_copy": 1, "oldfieldname": "advance_amount", "oldfieldtype": "Currency", @@ -65,7 +66,7 @@ "fieldname": "allocated_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Allocated amount", + "label": "Allocated Amount", "no_copy": 1, "oldfieldname": "allocated_amount", "oldfieldtype": "Currency", @@ -87,7 +88,7 @@ ], "istable": 1, "links": [], - "modified": "2019-12-17 13:53:22.111766", + "modified": "2021-11-22 16:33:58.515819", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Advance", From 5ba1bc15728541f7251c0b643152eeeb34166d85 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 23 Nov 2021 09:30:30 +0530 Subject: [PATCH 36/68] fix: Employee link formatter showing incorrect value for Employee Name (#28504) --- erpnext/public/js/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 0323a426f0e..f0facdd3a10 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -751,9 +751,13 @@ frappe.form.link_formatters['Item'] = function(value, doc) { } frappe.form.link_formatters['Employee'] = function(value, doc) { - if(doc && doc.employee_name && doc.employee_name !== value) { - return value? value + ': ' + doc.employee_name: doc.employee_name; + if (doc && value && doc.employee_name && doc.employee_name !== value && doc.employee === value) { + return value + ': ' + doc.employee_name; + } else if (!value && doc.doctype && doc.employee_name) { + // format blank value in child table + return doc.employee; } else { + // if value is blank in report view or project name and name are the same, return as is return value; } } From 1909bb569ec2652a89cc6bf063bb93824b1eab96 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:35:43 +0530 Subject: [PATCH 37/68] fix: POS Item cart only taxes with amount displayed (#28501) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index b652fdcb350..a5b2d500414 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -502,6 +502,7 @@ erpnext.PointOfSale.ItemCart = class { if (taxes.length) { const currency = this.events.get_frm().doc.currency; const taxes_html = taxes.map(t => { + if (t.tax_amount_after_discount_amount == 0.0) return; const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; return `
${description}
From 5ef23300229a6af013d4599c8af07dd8889599ce Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 23 Nov 2021 12:28:41 +0530 Subject: [PATCH 38/68] fix: Replace 'parent' with 'invoice_or_item' (#28513) --- 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 20bc3ec1158..84effc0f467 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -265,7 +265,7 @@ class GrossProfitGenerator(object): if self.filters.get("group_by") == "Invoice": self.totals.indent = 0.0 self.totals.parent_invoice = "" - self.totals.parent = "Total" + self.totals.invoice_or_item = "Total" self.si_list.append(self.totals) else: self.grouped_data.append(self.totals) From 0df9cf9526ea440044a6c3ffc10a778ea4d6050b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Nov 2021 15:08:51 +0530 Subject: [PATCH 39/68] chore: remove dead/irrelevant links from workspace (#28519) * chore: remove dead link to shopify settings * chore: unlink "debug" reports from stock dashboard --- .../erpnext_integrations_settings.json | 15 +----- erpnext/stock/workspace/stock/stock.json | 52 +------------------ 2 files changed, 3 insertions(+), 64 deletions(-) diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json index 5fe5afa2c4c..5efafd67fe8 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json @@ -29,17 +29,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Shopify Settings", - "link_count": 0, - "link_to": "Shopify Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "dependencies": "", "hidden": 0, @@ -74,7 +63,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:58.951705", + "modified": "2021-11-23 04:30:33.106991", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations Settings", @@ -86,4 +75,4 @@ "sequence_id": 11, "shortcuts": [], "title": "ERPNext Integrations Settings" -} +} \ No newline at end of file diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 9c805150f19..4df27f5dbff 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -704,59 +704,9 @@ "link_type": "Report", "onboard": 0, "type": "Link" - }, - { - "dependencies": "Stock Ledger Entry", - "hidden": 0, - "is_query_report": 1, - "label": "Stock and Account Value Comparison", - "link_count": 0, - "link_to": "Stock and Account Value Comparison", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Incorrect Data Report", - "link_count": 0, - "link_type": "DocType", - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Incorrect Serial No Qty and Valuation", - "link_count": 0, - "link_to": "Incorrect Serial No Valuation", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Incorrect Balance Qty After Transaction", - "link_count": 0, - "link_to": "Incorrect Balance Qty After Transaction", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Stock and Account Value Comparison", - "link_count": 0, - "link_to": "Stock and Account Value Comparison", - "link_type": "Report", - "onboard": 0, - "type": "Link" } ], - "modified": "2021-08-05 12:16:02.361519", + "modified": "2021-11-23 04:34:00.420870", "modified_by": "Administrator", "module": "Stock", "name": "Stock", From 34d1fec7ded836fb683a0a79293e1641cf6d674f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 17:53:26 +0530 Subject: [PATCH 40/68] fix: broken bom tree view and remove duplicate button (#28512) (#28527) * fix: broken bom tree view and remove duplicate button (cherry picked from commit 7ae1369d64ecaa829965d89915475fbd3211b5c2) Co-authored-by: Bhavesh Maheshwari <34086262+bhavesh95863@users.noreply.github.com> Co-authored-by: Ankush Menat --- .../doctype/bom/bom_item_preview.html | 17 +++-------------- erpnext/manufacturing/doctype/bom/bom_tree.js | 1 + 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index e614a7ebaa1..eb4135e03ac 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -16,26 +16,15 @@

- {% if data.value %} - + {% if data.value && data.value != "BOM" %} + {{ __("Open BOM {0}", [data.value.bold()]) }} {% endif %} {% if data.item_code %} - + {{ __("Open Item {0}", [data.item_code.bold()]) }} {% endif %}

-
-

- {% if data.value %} - - {{ __("Open BOM {0}", [data.value.bold()]) }} - {% endif %} - {% if data.item_code %} - - {{ __("Open Item {0}", [data.item_code.bold()]) }} - {% endif %} -

diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 6e2599e41bc..fb99add12cf 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -66,6 +66,7 @@ frappe.treeview_settings["BOM"] = { var bom = frappe.model.get_doc("BOM", node.data.value); node.data.image = escape(bom.image) || ""; node.data.description = bom.description || ""; + node.data.item_code = bom.item || ""; }); } }, From ab2c1f62a1b598529fd279aa56ecab23ccfd1dc3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Nov 2021 18:21:00 +0530 Subject: [PATCH 41/68] fix: correct module for reloading doc (#28523) --- .../item_reposting_for_incorrect_sl_and_gl.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index e4cb9ae7cdc..0f2ac4b4514 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -6,10 +6,19 @@ from erpnext.stock.stock_ledger import update_entries_after def execute(): - for doctype in ('repost_item_valuation', 'stock_entry_detail', 'purchase_receipt_item', - 'purchase_invoice_item', 'delivery_note_item', 'sales_invoice_item', 'packed_item'): - frappe.reload_doc('stock', 'doctype', doctype) - frappe.reload_doc('buying', 'doctype', 'purchase_receipt_item_supplied') + doctypes_to_reload = [ + ("stock", "repost_item_valuation"), + ("stock", "stock_entry_detail"), + ("stock", "purchase_receipt_item"), + ("stock", "delivery_note_item"), + ("stock", "packed_item"), + ("accounts", "sales_invoice_item"), + ("accounts", "purchase_invoice_item"), + ("buying", "purchase_receipt_item_supplied") + ] + + for module, doctype in doctypes_to_reload: + frappe.reload_doc(module, 'doctype', doctype) reposting_project_deployed_on = get_creation_time() posting_date = getdate(reposting_project_deployed_on) From 8d2abc4b86fe7d029f6e02eedb776ca0fbdbc35f Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Wed, 24 Nov 2021 16:23:12 +0530 Subject: [PATCH 42/68] fix: fixes in work order doctype (#28217) * fix: fixes in work order doctype * fix: sider issues and disabled set only once property * fix: set default qty to manufacture * fix: dont manually collapse sections * fix: remove unnecessary messages * fix: make dependent fields read only Co-authored-by: Ankush Menat --- .../doctype/work_order/work_order.js | 2 +- .../doctype/work_order/work_order.json | 6 +- .../work_order_operation.json | 68 ++++++++----------- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index bfce1b8cbec..5ffbb0374e9 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -442,7 +442,7 @@ frappe.ui.form.on("Work Order", { additional_operating_cost: function(frm) { erpnext.work_order.calculate_cost(frm.doc); erpnext.work_order.calculate_total_cost(frm); - } + }, }); frappe.ui.form.on("Work Order Item", { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index df7ee53b92d..12cd58f418b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -326,6 +326,7 @@ "label": "Expected Delivery Date" }, { + "collapsible": 1, "fieldname": "operations_section", "fieldtype": "Section Break", "label": "Operations", @@ -337,7 +338,7 @@ "fieldname": "transfer_material_against", "fieldtype": "Select", "label": "Transfer Material Against", - "options": "\nWork Order\nJob Card" + "options": "Work Order\nJob Card" }, { "fieldname": "operations", @@ -573,8 +574,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "migration_hash": "a18118963f4fcdb7f9d326de5f4063ba", - "modified": "2021-10-29 15:12:32.203605", + "modified": "2021-11-08 17:36:07.016300", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index f7b8787a0b3..647c14b33d9 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -6,27 +6,27 @@ "field_order": [ "details", "operation", - "bom", - "column_break_4", - "description", - "sequence_id", - "col_break1", - "completed_qty", "status", + "completed_qty", + "column_break_4", + "bom", "workstation", + "sequence_id", + "section_break_10", + "description", "estimated_time_and_cost", "planned_start_time", - "planned_end_time", - "column_break_10", - "time_in_mins", "hour_rate", + "time_in_mins", + "column_break_10", + "planned_end_time", "batch_size", "planned_operating_cost", "section_break_9", "actual_start_time", - "actual_end_time", - "column_break_11", "actual_operation_time", + "column_break_11", + "actual_end_time", "actual_operating_cost" ], "fields": [ @@ -42,7 +42,6 @@ "oldfieldname": "operation_no", "oldfieldtype": "Data", "options": "Operation", - "read_only": 1, "reqd": 1 }, { @@ -52,20 +51,14 @@ "label": "BOM", "no_copy": 1, "options": "BOM", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "description", "fieldtype": "Text Editor", "label": "Operation Description", "oldfieldname": "opn_description", - "oldfieldtype": "Text", - "read_only": 1 - }, - { - "fieldname": "col_break1", - "fieldtype": "Column Break" + "oldfieldtype": "Text" }, { "columns": 1, @@ -74,19 +67,16 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Completed Qty", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "columns": 1, "default": "Pending", "fieldname": "status", "fieldtype": "Select", - "in_list_view": 1, "label": "Status", "no_copy": 1, - "options": "Pending\nWork in Progress\nCompleted", - "read_only": 1 + "options": "Pending\nWork in Progress\nCompleted" }, { "fieldname": "workstation", @@ -106,15 +96,13 @@ "fieldname": "planned_start_time", "fieldtype": "Datetime", "label": "Planned Start Time", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "fieldname": "planned_end_time", "fieldtype": "Datetime", "label": "Planned End Time", - "no_copy": 1, - "read_only": 1 + "no_copy": 1 }, { "fieldname": "column_break_10", @@ -122,7 +110,7 @@ }, { "columns": 1, - "description": "in Minutes", + "description": "In Minutes", "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, @@ -152,6 +140,7 @@ "label": "Actual Time and Cost" }, { + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_start_time", "fieldtype": "Datetime", "label": "Actual Start Time", @@ -159,7 +148,7 @@ "read_only": 1 }, { - "description": "Updated via 'Time Log'", + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_end_time", "fieldtype": "Datetime", "label": "Actual End Time", @@ -171,7 +160,7 @@ "fieldtype": "Column Break" }, { - "description": "in Minutes\nUpdated via 'Time Log'", + "description": "Updated via 'Time Log' (In Minutes)", "fieldname": "actual_operation_time", "fieldtype": "Float", "label": "Actual Operation Time", @@ -190,25 +179,28 @@ { "fieldname": "batch_size", "fieldtype": "Int", - "label": "Batch Size", - "read_only": 1 + "label": "Batch Size" }, { "fieldname": "sequence_id", "fieldtype": "Int", + "hidden": 1, "label": "Sequence ID", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-24 14:36:12.835543", + "modified": "2021-11-24 04:52:54.295168", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", @@ -217,4 +209,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From 42395af22ad3f55a7bbef64f1202768abca77042 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 25 Nov 2021 13:28:52 +0530 Subject: [PATCH 43/68] fix: Remove commented code --- .../cost_of_poor_quality_report.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py index e6666f00bf5..77418235b07 100644 --- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py +++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py @@ -29,7 +29,6 @@ def get_data(report_filters): for row in job_cards: row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0) - # update_raw_material_cost(row, report_filters) data.append(row) return data @@ -45,14 +44,6 @@ def get_filters(report_filters, operations): return filters -# Check PR #28123 as to why this is commented - -# def update_raw_material_cost(row, filters): -# row.rm_cost = 0.0 -# for data in frappe.get_all("Job Card Item", fields = ["amount"], -# filters={"parent": row.name, "docstatus": 1}): -# row.rm_cost += data.amount - def get_columns(filters): return [ { @@ -114,12 +105,6 @@ def get_columns(filters): "fieldname": "operating_cost", "width": "150" }, - # { - # "label": _("Raw Material Cost"), - # "fieldtype": "Currency", - # "fieldname": "rm_cost", - # "width": "100" - # }, { "label": _("Total Time (in Mins)"), "fieldtype": "Float", From 5ba3b28d69c88675f41830b8a490882f5298dc07 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Nov 2021 15:42:30 +0530 Subject: [PATCH 44/68] fix(refactor): Advance tds allocation to purchase invoice --- .../accounts/doctype/advance_tax/__init__.py | 0 .../doctype/advance_tax/advance_tax.json | 56 +++++++++++++ .../doctype/advance_tax/advance_tax.py | 9 ++ .../doctype/payment_entry/payment_entry.json | 12 +-- .../doctype/payment_entry/payment_entry.py | 33 +++----- .../purchase_invoice/purchase_invoice.json | 11 ++- .../purchase_invoice/purchase_invoice.py | 42 +++++++++- .../doctype/sales_invoice/sales_invoice.py | 2 - .../tax_withholding_category.py | 33 +++++++- erpnext/accounts/general_ledger.py | 20 +++++ erpnext/controllers/accounts_controller.py | 82 +------------------ 11 files changed, 179 insertions(+), 121 deletions(-) create mode 100644 erpnext/accounts/doctype/advance_tax/__init__.py create mode 100644 erpnext/accounts/doctype/advance_tax/advance_tax.json create mode 100644 erpnext/accounts/doctype/advance_tax/advance_tax.py diff --git a/erpnext/accounts/doctype/advance_tax/__init__.py b/erpnext/accounts/doctype/advance_tax/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/advance_tax/advance_tax.json b/erpnext/accounts/doctype/advance_tax/advance_tax.json new file mode 100644 index 00000000000..68706aba88f --- /dev/null +++ b/erpnext/accounts/doctype/advance_tax/advance_tax.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-11-25 10:24:39.836195", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "reference_detail", + "account_head", + "allocated_amount" + ], + "fields": [ + { + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "options": "DocType" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "options": "reference_type" + }, + { + "fieldname": "reference_detail", + "fieldtype": "Data", + "label": "Reference Detail" + }, + { + "fieldname": "account_head", + "fieldtype": "Link", + "label": "Account Head", + "options": "Account" + }, + { + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "label": "Allocated Amount", + "options": "party_account_currency" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-11-25 10:27:51.712286", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Advance Tax", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/advance_tax/advance_tax.py b/erpnext/accounts/doctype/advance_tax/advance_tax.py new file mode 100644 index 00000000000..2e784efd8f3 --- /dev/null +++ b/erpnext/accounts/doctype/advance_tax/advance_tax.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class AdvanceTax(Document): + pass diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index ee2e319a6fc..c8d1db91f54 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -61,7 +61,6 @@ "taxes_and_charges_section", "purchase_taxes_and_charges_template", "sales_taxes_and_charges_template", - "advance_tax_account", "column_break_55", "apply_tax_withholding_amount", "tax_withholding_category", @@ -685,15 +684,6 @@ "fieldtype": "Section Break", "hide_border": 1 }, - { - "depends_on": "eval:doc.apply_tax_withholding_amount", - "description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices", - "fieldname": "advance_tax_account", - "fieldtype": "Link", - "label": "Advance Tax Account", - "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount", - "options": "Account" - }, { "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "fieldname": "received_amount_after_tax", @@ -730,7 +720,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-10-22 17:50:24.632806", + "modified": "2021-11-24 18:58:24.919764", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 26fd16a4fd5..e524592382d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -20,7 +20,7 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) -from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices from erpnext.controllers.accounts_controller import ( @@ -433,9 +433,6 @@ class PaymentEntry(AccountsController): if not self.apply_tax_withholding_amount: return - if not self.advance_tax_account: - frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction")) - net_total = self.paid_amount for reference in self.get("references"): @@ -455,13 +452,12 @@ class PaymentEntry(AccountsController): 'net_total': net_total }) - tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category) + tax_withholding_details, tax_deducted_on_advances = get_party_tax_withholding_details(args, self.tax_withholding_category) if not tax_withholding_details: return tax_withholding_details.update({ - 'add_deduct_tax': 'Add', 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -689,6 +685,7 @@ class PaymentEntry(AccountsController): self.add_deductions_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries) + gl_entries = process_gl_map(gl_entries) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) def add_party_gl_entries(self, gl_entries): @@ -752,7 +749,8 @@ class PaymentEntry(AccountsController): "against": self.party if self.payment_type=="Pay" else self.paid_to, "credit_in_account_currency": self.paid_amount, "credit": self.base_paid_amount, - "cost_center": self.cost_center + "cost_center": self.cost_center, + "post_net_value": True }, item=self) ) if self.payment_type in ("Receive", "Internal Transfer"): @@ -782,14 +780,10 @@ class PaymentEntry(AccountsController): rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" against = self.party or self.paid_to - payment_or_advance_account = self.get_party_account_for_taxes() + payment_account = self.get_party_account_for_taxes() tax_amount = d.tax_amount base_tax_amount = d.base_tax_amount - if self.advance_tax_account: - tax_amount = -1 * tax_amount - base_tax_amount = -1 * base_tax_amount - gl_entries.append( self.get_gl_dict({ "account": d.account_head, @@ -798,19 +792,21 @@ class PaymentEntry(AccountsController): dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, - "cost_center": d.cost_center + "cost_center": d.cost_center, + "post_net_value": True, }, account_currency, item=d)) - if not d.included_in_paid_amount or self.advance_tax_account: + if not d.included_in_paid_amount: gl_entries.append( self.get_gl_dict({ - "account": payment_or_advance_account, + "account": payment_account, "against": against, rev_dr_or_cr: tax_amount, rev_dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, + "post_net_value": True, }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -832,9 +828,7 @@ class PaymentEntry(AccountsController): ) def get_party_account_for_taxes(self): - if self.advance_tax_account: - return self.advance_tax_account - elif self.payment_type == 'Receive': + if self.payment_type == 'Receive': return self.paid_to elif self.payment_type in ('Pay', 'Internal Transfer'): return self.paid_from @@ -1603,9 +1597,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.apply_tax_withholding_amount = 1 pe.tax_withholding_category = doc.tax_withholding_category - if not pe.advance_tax_account: - pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account') - return pe def get_bank_cash_account(doc, bank_account): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 03cbc4acbc4..bd0116443ff 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -130,6 +130,7 @@ "allocate_advances_automatically", "get_advances", "advances", + "advance_tax", "payment_schedule_section", "payment_terms_template", "ignore_default_payment_terms_template", @@ -1408,13 +1409,21 @@ { "fieldname": "column_break_147", "fieldtype": "Column Break" + }, + { + "fieldname": "advance_tax", + "fieldtype": "Table", + "hidden": 1, + "label": "Advance Tax", + "options": "Advance Tax", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-10-12 20:55:16.145651", + "modified": "2021-11-25 13:31:02.716727", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 62e3dc83468..516133ab635 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -427,6 +427,7 @@ class PurchaseInvoice(BuyingController): self.update_project() update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) + self.update_advance_tax_references() self.process_common_party_accounting() @@ -472,8 +473,6 @@ class PurchaseInvoice(BuyingController): self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) - self.allocate_advance_taxes(gl_entries) - gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = merge_similar_entries(gl_entries) @@ -1074,6 +1073,7 @@ class PurchaseInvoice(BuyingController): unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') + self.update_advance_tax_references(cancel=1) def update_project(self): project_list = [] @@ -1150,7 +1150,10 @@ class PurchaseInvoice(BuyingController): if not self.tax_withholding_category: return - tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) + tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category) + + # Adjust TDS paid on advances + self.allocate_advance_tds(tax_withholding_details, advance_taxes) if not tax_withholding_details: return @@ -1174,6 +1177,39 @@ class PurchaseInvoice(BuyingController): # calculate totals again after applying TDS self.calculate_taxes_and_totals() + def allocate_advance_tds(self, tax_withholding_details, advance_taxes): + self.set('advance_tax', []) + for tax in advance_taxes: + allocated_amount = 0 + pending_amount = flt(tax.tax_amount - tax.allocated_amount) + if flt(tax_withholding_details.get('tax_amount')) >= pending_amount: + tax_withholding_details['tax_amount'] -= pending_amount + allocated_amount = pending_amount + elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount: + allocated_amount = tax_withholding_details['tax_amount'] + tax_withholding_details['tax_amount'] = 0 + + self.append('advance_tax', { + 'reference_type': 'Payment Entry', + 'reference_name': tax.parent, + 'reference_detail': tax.name, + 'account_head': tax.account_head, + 'allocated_amount': allocated_amount + }) + + def update_advance_tax_references(self, cancel=0): + for tax in self.get('advance_tax'): + at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") + + if cancel: + frappe.qb.update(at).set( + at.allocated_amount, at.allocated_amount - tax.allocated_amount + ).where(at.name == tax.reference_detail).run() + else: + frappe.qb.update(at).set( + at.allocated_amount, at.allocated_amount + tax.allocated_amount + ).where(at.name == tax.reference_detail).run() + def set_status(self, update=False, status=None, update_modified=True): if self.is_new(): if self.get('amended_from'): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 59d46fc2e87..c4d59f1ef71 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -842,8 +842,6 @@ class SalesInvoice(SellingController): self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) - self.allocate_advance_taxes(gl_entries) - self.make_item_gl_entries(gl_entries) self.make_discount_gl_entries(gl_entries) 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 dc1818a052a..fe156cb029e 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -95,7 +95,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.') .format(tax_withholding_category, inv.company, party)) - tax_amount, tax_deducted = get_tax_amount( + tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount( party_type, parties, inv, tax_details, posting_date, pan_no @@ -106,7 +106,10 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): else: tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) - return tax_row + if inv.doctype == 'Purchase Invoice': + return tax_row, tax_deducted_on_advances + else: + return tax_row def get_tax_withholding_details(tax_withholding_category, posting_date, company): tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) @@ -194,6 +197,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, to_date=tax_details.to_date, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers + tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details) tax_deducted = 0 if taxable_vouchers: @@ -223,7 +227,7 @@ 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 + return tax_amount, tax_deducted, tax_deducted_on_advances def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' @@ -281,6 +285,29 @@ def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, pa return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] +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) + + return tax_info + + def get_deducted_tax(taxable_vouchers, tax_details): # check if TDS / TCS account is already charged on taxable vouchers filters = { diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 8ef7d7e103c..1836db6477f 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -73,8 +73,28 @@ def process_gl_map(gl_map, merge_entries=True, precision=None): flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency) entry.credit_in_account_currency = 0.0 + update_net_values(entry) + return gl_map +def update_net_values(entry): + # In some scenarios net value needs to be shown in the ledger + # This method updates net values as debit or credit + if entry.post_net_value and entry.debit and entry.credit: + if entry.debit > entry.credit: + entry.debit = entry.debit - entry.credit + entry.debit_in_account_currency = entry.debit_in_account_currency \ + - entry.credit_in_account_currency + entry.credit = 0 + entry.credit_in_account_currency = 0 + else: + entry.credit = entry.credit - entry.debit + entry.credit_in_account_currency = entry.credit_in_account_currency \ + - entry.debit_in_account_currency + + entry.debit = 0 + entry.debit_in_account_currency = 0 + def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 77503a8ee53..9cf2e339168 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -524,7 +524,8 @@ class AccountsController(TransactionBase): 'is_opening': self.get("is_opening") or "No", 'party_type': None, 'party': None, - 'project': self.get("project") + 'project': self.get("project"), + 'post_net_value': args.get('post_net_value') }) accounting_dimensions = get_accounting_dimensions() @@ -805,7 +806,6 @@ class AccountsController(TransactionBase): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries if self.doctype in ["Sales Invoice", "Purchase Invoice"]: - self.update_allocated_advance_taxes_on_cancel() if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): unlink_ref_doc_from_payment_entries(self) @@ -853,29 +853,6 @@ class AccountsController(TransactionBase): return tax_map - def update_allocated_advance_taxes_on_cancel(self): - if self.get('advances'): - tax_accounts = [d.account_head for d in self.get('taxes')] - allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'], - filters={'voucher_no': self.name, 'account': ('in', tax_accounts)}, - group_by='account', as_list=1)) - - tax_map = self.get_tax_map() - - for pe in self.get('advances'): - if pe.reference_type == 'Payment Entry': - pe = frappe.get_doc('Payment Entry', pe.reference_name) - for tax in pe.get('taxes'): - allocated_amount = flt(tax_map.get(tax.account_head)) - flt(allocated_tax_map.get(tax.account_head)) - if allocated_amount > tax.tax_amount: - allocated_amount = tax.tax_amount - - if allocated_amount: - frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount', - tax.allocated_amount - allocated_amount) - tax_map[tax.account_head] -= allocated_amount - allocated_tax_map[tax.account_head] -= allocated_amount - def get_amount_and_base_amount(self, item, enable_discount_accounting): amount = item.net_amount base_amount = item.base_net_amount @@ -959,61 +936,6 @@ class AccountsController(TransactionBase): }, item=self) ) - def allocate_advance_taxes(self, gl_entries): - tax_map = self.get_tax_map() - for pe in self.get("advances"): - if pe.reference_type == "Payment Entry" and \ - frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'): - pe = frappe.get_doc("Payment Entry", pe.reference_name) - advance_tax_account = pe.advance_tax_account - - for tax in pe.get("taxes"): - account_currency = get_account_currency(tax.account_head) - - if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - advance_tax_account = pe.advance_tax_account if pe.paid_from != pe.advance_tax_account \ - else self.credit_to - else: - dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - - party = self.supplier if self.doctype == "Purchase Invoice" else self.customer - unallocated_amount = tax.tax_amount - tax.allocated_amount - if tax_map.get(tax.account_head): - amount = tax_map.get(tax.account_head) - if amount < unallocated_amount: - unallocated_amount = amount - - gl_entries.append( - self.get_gl_dict({ - "account": tax.account_head, - "against": party, - dr_or_cr: unallocated_amount, - dr_or_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - "cost_center": tax.cost_center - }, account_currency, item=tax)) - - gl_entries.append( - self.get_gl_dict({ - "account": advance_tax_account, - "against": party, - rev_dr_cr: unallocated_amount, - rev_dr_cr + "_in_account_currency": unallocated_amount - if account_currency==self.company_currency - else unallocated_amount, - "cost_center": self.get('cost_center') if advance_tax_account == self.get('credit_to') else tax.cost_center, - "party_type": 'Supplier' if advance_tax_account == self.get('credit_to') else '', - "party": self.get('supplier') if advance_tax_account == self.get('credit_to') else '', - }, account_currency, item=tax)) - - frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount", - tax.allocated_amount + unallocated_amount) - - tax_map[tax.account_head] -= unallocated_amount def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for From 6dc9b822bce20fc4520f6f5fe597a20c554ba025 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 15:53:18 +0530 Subject: [PATCH 45/68] refactor: item-wh wise reposting by default --- erpnext/controllers/stock_controller.py | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index aba15b47e3a..6431388208b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -544,7 +544,7 @@ class StockController(AccountsController): "company": self.company }) if future_sle_exists(args): - create_repost_item_valuation_entry(args) + create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) @frappe.whitelist() def make_quality_inspections(doctype, docname, items): @@ -679,3 +679,39 @@ def create_repost_item_valuation_entry(args): repost_entry.flags.ignore_permissions = True repost_entry.save() repost_entry.submit() + + +def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=False): + """Using a voucher create repost item valuation records for all item-warehouse pairs.""" + + stock_ledger_entries = frappe.db.get_all("Stock Ledger Entry", + filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, + fields=["item_code", "warehouse", "posting_date", "posting_time", "creation", "company"], + order_by="creation asc", + group_by="item_code, warehouse" + ) + distinct_item_warehouses = set() + + repost_entries = [] + + for sle in stock_ledger_entries: + item_wh = (sle.item_code, sle.warehouse) + if item_wh in distinct_item_warehouses: + continue + distinct_item_warehouses.add(item_wh) + + repost_entry = frappe.new_doc("Repost Item Valuation") + repost_entry.based_on = "Item and Warehouse" + repost_entry.voucher_type = voucher_type + repost_entry.voucher_no = voucher_no + + repost_entry.item_code = sle.item_code + repost_entry.warehouse = sle.warehouse + repost_entry.posting_date = sle.posting_date + repost_entry.posting_time = sle.posting_time + repost_entry.allow_zero_rate = allow_zero_rate + repost_entry.flags.ignore_links = True + repost_entry.submit() + repost_entries.append(repost_entry) + + return repost_entries From a36c249d3dcb413a4dff3d9552b6eaadf56d844e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 16:03:37 +0530 Subject: [PATCH 46/68] test: item-wh repost creation --- erpnext/controllers/stock_controller.py | 1 - .../test_repost_item_valuation.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 6431388208b..d241f3f7d0c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -691,7 +691,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa group_by="item_code, warehouse" ) distinct_item_warehouses = set() - repost_entries = [] for sle in stock_ledger_entries: 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 c086f938b5d..7abda61a830 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -5,6 +5,8 @@ import unittest import frappe +from erpnext.controllers.stock_controller import create_item_wise_repost_entries +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import ( in_configured_timeslot, ) @@ -70,3 +72,15 @@ class TestRepostItemValuation(unittest.TestCase): in_configured_timeslot(repost_settings, case.get("current_time")), msg=f"Exepcted false from : {case}", ) + + def test_create_item_wise_repost_item_valuation_entries(self): + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", get_multiple_items = True) + + rivs = create_item_wise_repost_entries(pr.doctype, pr.name) + self.assertGreaterEqual(len(rivs), 2) + self.assertIn("_Test Item", [d.item_code for d in rivs]) + + for riv in rivs: + self.assertEqual(riv.company, "_Test Company with perpetual inventory") + self.assertEqual(riv.warehouse, "Stores - TCP1") From d220e08ba4c55c334a5586481dec192c4896ea13 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 17:47:00 +0530 Subject: [PATCH 47/68] refactor: reuse get_items_to_be_repost function --- erpnext/controllers/stock_controller.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d241f3f7d0c..fe5d0c70cfc 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -17,7 +17,7 @@ from erpnext.accounts.general_ledger import ( from erpnext.accounts.utils import get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock import get_warehouse_account_map -from erpnext.stock.stock_ledger import get_valuation_rate +from erpnext.stock.stock_ledger import get_items_to_be_repost, get_valuation_rate class QualityInspectionRequiredError(frappe.ValidationError): pass @@ -684,12 +684,8 @@ def create_repost_item_valuation_entry(args): def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=False): """Using a voucher create repost item valuation records for all item-warehouse pairs.""" - stock_ledger_entries = frappe.db.get_all("Stock Ledger Entry", - filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, - fields=["item_code", "warehouse", "posting_date", "posting_time", "creation", "company"], - order_by="creation asc", - group_by="item_code, warehouse" - ) + stock_ledger_entries = get_items_to_be_repost(voucher_type, voucher_no) + distinct_item_warehouses = set() repost_entries = [] From 45dd46be3d92201eb0195ae62fa5b2ba15616e5b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 10:50:52 +0530 Subject: [PATCH 48/68] feat: option to select reposting method In current implementation selecting Item-Warehouse based reposting is better for few users, who don't use depenent SLEs but have frequent transactions involving same items. This change lets them switch to item-warehouse based reposting if required. Only use this if you understand technicalities of stock reposting. This is experimental but will become mainstream in coming days. --- erpnext/controllers/stock_controller.py | 7 ++++++- .../stock_reposting_settings.json | 12 ++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index fe5d0c70cfc..ca567fdc583 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -544,7 +544,12 @@ class StockController(AccountsController): "company": self.company }) if future_sle_exists(args): - create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) + item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")) + if item_based_reposting: + create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) + else: + create_repost_item_valuation_entry(args) + @frappe.whitelist() def make_quality_inspections(doctype, docname, items): diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json index 24740590037..0facae8d3b8 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -1,6 +1,7 @@ { "actions": [], "allow_rename": 1, + "beta": 1, "creation": "2021-10-01 10:56:30.814787", "doctype": "DocType", "editable_grid": 1, @@ -10,7 +11,8 @@ "limit_reposting_timeslot", "start_time", "end_time", - "limits_dont_apply_on" + "limits_dont_apply_on", + "item_based_reposting" ], "fields": [ { @@ -44,12 +46,18 @@ "fieldname": "limit_reposting_timeslot", "fieldtype": "Check", "label": "Limit timeslot for Stock Reposting" + }, + { + "default": "0", + "fieldname": "item_based_reposting", + "fieldtype": "Check", + "label": "Use Item based reposting" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-10-01 11:27:28.981594", + "modified": "2021-11-02 01:22:45.155841", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reposting Settings", From a5a8c9104fbb5279e6b25c529b6b4c0fc7ccef97 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 11:08:36 +0530 Subject: [PATCH 49/68] perf: index for item-sh on repost item valuation Item-WH based reposting requires querying existing similar repost. Assuming there is only 1 max extra entry with same params just indexing item-WH is sufficient to speed up the query. --- .../doctype/repost_item_valuation/repost_item_valuation.json | 4 ++-- .../doctype/repost_item_valuation/repost_item_valuation.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 3ff0f60b3e4..794c15ef336 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -177,7 +177,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-11-18 02:18:10.524560", + "modified": "2021-11-24 02:18:10.524560", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", @@ -228,4 +228,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} 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 59d191fa07f..475f5251574 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -58,6 +58,11 @@ class RepostItemValuation(Document): frappe.enqueue(repost, timeout=1800, queue='long', job_name='repost_sle', now=True, doc=self) + +def on_doctype_update(): + frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse") + + def repost(doc): try: if not frappe.db.exists("Repost Item Valuation", doc.name): From 1d3842f03a8f90d2110e43008091ea761f83fcb4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 15:22:39 +0530 Subject: [PATCH 50/68] fix: dont erase voucher_type and voucher_no for item_wh repost kept for tracability. --- .../doctype/repost_item_valuation/repost_item_valuation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 475f5251574..64cea1db346 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -27,15 +27,12 @@ class RepostItemValuation(Document): if self.based_on == 'Transaction': self.item_code = None self.warehouse = None - else: - self.voucher_type = None - self.voucher_no = None self.allow_negative_stock = self.allow_negative_stock or \ cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) def set_company(self): - if self.voucher_type and self.voucher_no: + if self.based_on == "Transaction": self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") From 0d0e24a5f51e566dfa04364787dfb883a9b7ad30 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 11:13:37 +0530 Subject: [PATCH 51/68] perf: skip unnecessary item-wh reposts Using basic idea that repost with older posting date will also take care of subsequent posting dates... When Item-WH reposts are queued: 1. If another repost with same item-wh but older posting date exists then skip current one. 2. If another repost with same item-wh but newer posting date exists then skip another one. --- .../repost_item_valuation.json | 2 +- .../repost_item_valuation.py | 58 +++++++++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 794c15ef336..cd7e63b18b2 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -64,7 +64,7 @@ "in_standard_filter": 1, "label": "Status", "no_copy": 1, - "options": "Queued\nIn Progress\nCompleted\nFailed", + "options": "Queued\nIn Progress\nCompleted\nSkipped\nFailed", "read_only": 1 }, { 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 64cea1db346..c0cb2c54b1b 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -2,10 +2,21 @@ # For license information, please see license.txt +import datetime + import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime, today +from frappe.utils import ( + cint, + get_datetime, + get_link_to_form, + get_time, + get_weekday, + now, + nowtime, + today, +) from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -19,7 +30,7 @@ from erpnext.stock.stock_ledger import repost_future_sle class RepostItemValuation(Document): def validate(self): - self.set_status() + self.set_status(write=False) self.reset_field_values() self.set_company() @@ -37,12 +48,17 @@ class RepostItemValuation(Document): elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") - def set_status(self, status=None): + def set_status(self, status=None, write=True): + status = status or self.status if not status: - status = 'Queued' - self.db_set('status', status) + self.status = 'Queued' + else: + self.status = status + if write: + self.db_set('status', self.status) def on_submit(self): + self.deduplicate_similar_repost() if not frappe.flags.in_test: return @@ -55,6 +71,35 @@ class RepostItemValuation(Document): frappe.enqueue(repost, timeout=1800, queue='long', job_name='repost_sle', now=True, doc=self) + def deduplicate_similar_repost(self): + """ Deduplicate similar reposts based on item-warehouse-posting combination.""" + if self.based_on != "Item and Warehouse": + return + + queued = frappe.db.get_value( + "Repost Item Valuation", + filters={ + "docstatus": 1, + "status": "Queued", + "item_code": self.item_code, + "warehouse": self.warehouse, + "based_on": self.based_on, + "name": ("!=", self.name) + }, + fieldname=["name", "posting_date", "posting_time"], + as_dict=True + ) + if not queued: + return + + posting_timestamp = datetime.datetime.combine(get_datetime(self.posting_date), get_time(self.posting_time)) + queued_timestamp = datetime.datetime.combine(get_datetime(queued.posting_date), get_time(queued.posting_time)) + + if posting_timestamp > queued_timestamp: + self.set_status("Skipped") + else: + frappe.db.set_value("Repost Item Valuation", queued.name, "status", "Skipped") + def on_doctype_update(): frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse") @@ -136,7 +181,8 @@ def repost_entries(): for row in riv_entries: doc = frappe.get_doc('Repost Item Valuation', row.name) - repost(doc) + if doc.status in ('Queued', 'In Progress'): + repost(doc) riv_entries = get_repost_item_valuation_entries() if riv_entries: From 55631dd0d68fb3306bf285fa205e55e567b73837 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Nov 2021 18:03:43 +0530 Subject: [PATCH 52/68] test: item-wh deduplication in reposting --- .../repost_item_valuation.py | 2 +- .../test_repost_item_valuation.py | 55 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) 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 c0cb2c54b1b..ff490f8ecc3 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -59,7 +59,7 @@ class RepostItemValuation(Document): def on_submit(self): self.deduplicate_similar_repost() - if not frappe.flags.in_test: + if not frappe.flags.in_test or self.flags.dont_run_in_test: return frappe.enqueue(repost, timeout=1800, queue='long', 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 7abda61a830..ea79572bc53 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 @@ -74,8 +74,11 @@ class TestRepostItemValuation(unittest.TestCase): ) def test_create_item_wise_repost_item_valuation_entries(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", - warehouse = "Stores - TCP1", get_multiple_items = True) + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + get_multiple_items=True, + ) rivs = create_item_wise_repost_entries(pr.doctype, pr.name) self.assertGreaterEqual(len(rivs), 2) @@ -84,3 +87,51 @@ class TestRepostItemValuation(unittest.TestCase): for riv in rivs: self.assertEqual(riv.company, "_Test Company with perpetual inventory") self.assertEqual(riv.warehouse, "Stores - TCP1") + + def test_deduplication(self): + def _assert_status(doc, status): + doc.load_from_db() + self.assertEqual(doc.status, status) + + riv_args = frappe._dict( + doctype="Repost Item Valuation", + item_code="_Test Item", + warehouse="_Test Warehouse - _TC", + based_on="Item and Warehouse", + voucher_type="Sales Invoice", + voucher_no="SI-1", + posting_date="2021-01-02", + posting_time="00:01:00", + ) + + # new repost without any duplicates + riv1 = frappe.get_doc(riv_args) + riv1.flags.dont_run_in_test = True + riv1.submit() + _assert_status(riv1, "Queued") + self.assertEqual(riv1.voucher_type, "Sales Invoice") # traceability + self.assertEqual(riv1.voucher_no, "SI-1") + + # newer than existing duplicate - riv1 + riv2 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-03"})) + riv2.flags.dont_run_in_test = True + riv2.submit() + _assert_status(riv2, "Skipped") + + # older than exisitng duplicate - riv1 + riv3 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-01"})) + riv3.flags.dont_run_in_test = True + riv3.submit() + _assert_status(riv3, "Queued") + _assert_status(riv1, "Skipped") + + # unrelated reposts, shouldn't do anything to others. + riv4 = frappe.get_doc(riv_args.update({"warehouse": "Stores - _TC"})) + riv4.flags.dont_run_in_test = True + riv4.submit() + _assert_status(riv4, "Queued") + _assert_status(riv3, "Queued") + + # to avoid breaking other tests accidentaly + riv4.set_status("Skipped") + riv3.set_status("Skipped") From ed94f0f3f26ecf71f9113d3d2d10aed05cf31265 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 24 Nov 2021 13:57:39 +0530 Subject: [PATCH 53/68] refactor: deduplicate during repost background job --- .../repost_item_valuation.py | 59 +++++++------------ .../test_repost_item_valuation.py | 3 + 2 files changed, 25 insertions(+), 37 deletions(-) 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 ff490f8ecc3..965a32d92d8 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -1,22 +1,10 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -import datetime - import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import ( - cint, - get_datetime, - get_link_to_form, - get_time, - get_weekday, - now, - nowtime, - today, -) +from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime, today from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -58,7 +46,6 @@ class RepostItemValuation(Document): self.db_set('status', self.status) def on_submit(self): - self.deduplicate_similar_repost() if not frappe.flags.in_test or self.flags.dont_run_in_test: return @@ -76,30 +63,27 @@ class RepostItemValuation(Document): if self.based_on != "Item and Warehouse": return - queued = frappe.db.get_value( - "Repost Item Valuation", - filters={ - "docstatus": 1, - "status": "Queued", - "item_code": self.item_code, - "warehouse": self.warehouse, - "based_on": self.based_on, - "name": ("!=", self.name) - }, - fieldname=["name", "posting_date", "posting_time"], - as_dict=True - ) - if not queued: - return - - posting_timestamp = datetime.datetime.combine(get_datetime(self.posting_date), get_time(self.posting_time)) - queued_timestamp = datetime.datetime.combine(get_datetime(queued.posting_date), get_time(queued.posting_time)) - - if posting_timestamp > queued_timestamp: - self.set_status("Skipped") - else: - frappe.db.set_value("Repost Item Valuation", queued.name, "status", "Skipped") + filters = { + "item_code": self.item_code, + "warehouse": self.warehouse, + "name": self.name, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + } + frappe.db.sql(""" + update `tabRepost Item Valuation` + set status = 'Skipped' + WHERE item_code = %(item_code)s + and warehouse = %(warehouse)s + and name != %(name)s + and TIMESTAMP(posting_date, posting_time) > TIMESTAMP(%(posting_date)s, %(posting_time)s) + and docstatus = 1 + and status = 'Queued' + and based_on = 'Item and Warehouse' + """, + filters + ) def on_doctype_update(): frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse") @@ -182,6 +166,7 @@ def repost_entries(): for row in riv_entries: doc = frappe.get_doc('Repost Item Valuation', row.name) if doc.status in ('Queued', 'In Progress'): + doc.deduplicate_similar_repost() repost(doc) riv_entries = get_repost_item_valuation_entries() 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 ea79572bc53..de793163fdb 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 @@ -116,12 +116,14 @@ class TestRepostItemValuation(unittest.TestCase): riv2 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-03"})) riv2.flags.dont_run_in_test = True riv2.submit() + riv1.deduplicate_similar_repost() _assert_status(riv2, "Skipped") # older than exisitng duplicate - riv1 riv3 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-01"})) riv3.flags.dont_run_in_test = True riv3.submit() + riv3.deduplicate_similar_repost() _assert_status(riv3, "Queued") _assert_status(riv1, "Skipped") @@ -129,6 +131,7 @@ class TestRepostItemValuation(unittest.TestCase): riv4 = frappe.get_doc(riv_args.update({"warehouse": "Stores - _TC"})) riv4.flags.dont_run_in_test = True riv4.submit() + riv4.deduplicate_similar_repost() _assert_status(riv4, "Queued") _assert_status(riv3, "Queued") From 0a2964dc826d6f027a20f8d16ab62a1a59238853 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 24 Nov 2021 15:55:31 +0530 Subject: [PATCH 54/68] fix: ignore permissions while creating reposts --- erpnext/controllers/stock_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ca567fdc583..ae170945aa6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -711,6 +711,7 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entry.posting_time = sle.posting_time repost_entry.allow_zero_rate = allow_zero_rate repost_entry.flags.ignore_links = True + repost_entry.flags.ignore_permissions = True repost_entry.submit() repost_entries.append(repost_entry) From 8b33358660fac5ee66c2b56e966faca1f6ba0522 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Nov 2021 17:24:59 +0530 Subject: [PATCH 55/68] fix: patch failure due to new doctype --- .../v12_0/move_target_distribution_from_parent_to_child.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py index 1e230a787c2..36fe18d8b71 100644 --- a/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py +++ b/erpnext/patches/v12_0/move_target_distribution_from_parent_to_child.py @@ -7,6 +7,7 @@ import frappe def execute(): frappe.reload_doc("setup", "doctype", "target_detail") + frappe.reload_doc("core", "doctype", "prepared_report") for d in ['Sales Person', 'Sales Partner', 'Territory']: frappe.db.sql(""" From 87f2dcfb597e45d9a7f970fbe4a644e1e70bca92 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Nov 2021 19:38:44 +0530 Subject: [PATCH 56/68] fix: total stock summary UI glitch #28564 fix: total stock summary UI glitch --- .../total_stock_summary.js | 22 ++++--------------- .../total_stock_summary.py | 11 +++------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js index 90648f1b249..88054aaea73 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js @@ -10,23 +10,8 @@ frappe.query_reports["Total Stock Summary"] = { "fieldtype": "Select", "width": "80", "reqd": 1, - "options": ["", "Warehouse", "Company"], - "change": function() { - let group_by = frappe.query_report.get_filter_value("group_by") - let company_filter = frappe.query_report.get_filter("company") - if (group_by == "Company") { - company_filter.df.reqd = 0; - company_filter.df.hidden = 1; - frappe.query_report.set_filter_value("company", ""); - company_filter.refresh(); - } - else { - company_filter.df.reqd = 1; - company_filter.df.hidden = 0; - company_filter.refresh(); - frappe.query_report.refresh(); - } - } + "options": ["Warehouse", "Company"], + "default": "Warehouse", }, { "fieldname": "company", @@ -34,8 +19,9 @@ frappe.query_reports["Total Stock Summary"] = { "fieldtype": "Link", "width": "80", "options": "Company", + "reqd": 1, "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 + "depends_on": "eval: doc.group_by != 'Company'", }, ] } 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 7e47b50b856..6f27558b887 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py @@ -7,8 +7,9 @@ from frappe import _ def execute(filters=None): - if not filters: filters = {} - validate_filters(filters) + + if not filters: + filters = {} columns = get_columns() stock = get_total_stock(filters) @@ -53,9 +54,3 @@ def get_total_stock(filters): ON warehouse.name = ledger.warehouse WHERE ledger.actual_qty != 0 %s""" % (columns, conditions)) - -def validate_filters(filters): - if filters.get("group_by") == 'Company' and \ - filters.get("company"): - - frappe.throw(_("Please set Company filter blank if Group By is 'Company'")) From f07f010962d51f43bc8f17048b4f3bb74cd925f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Nov 2021 23:58:16 +0530 Subject: [PATCH 57/68] fix: Add tests --- .../advance_taxes_and_charges.json | 11 ++-------- .../doctype/payment_entry/payment_entry.py | 16 ++------------ .../purchase_invoice/test_purchase_invoice.py | 22 ++++++++++--------- .../tax_withholding_category.py | 5 ++++- erpnext/controllers/accounts_controller.py | 9 ++++---- 5 files changed, 25 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 4d634994319..05b284ae16f 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -25,8 +25,7 @@ "allocated_amount", "column_break_13", "base_tax_amount", - "base_total", - "base_allocated_amount" + "base_total" ], "fields": [ { @@ -168,12 +167,6 @@ "label": "Allocated Amount", "options": "currency" }, - { - "fieldname": "base_allocated_amount", - "fieldtype": "Currency", - "label": "Allocated Amount (Company Currency)", - "options": "Company:company:default_currency" - }, { "fetch_from": "account_head.account_currency", "fieldname": "currency", @@ -186,7 +179,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-09 11:46:58.373170", + "modified": "2021-11-25 11:10:10.945027", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e524592382d..7aeb872b284 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -435,24 +435,16 @@ class PaymentEntry(AccountsController): net_total = self.paid_amount - for reference in self.get("references"): - net_total_for_tds = 0 - if reference.reference_doctype == 'Purchase Order': - net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total')) - - if net_total_for_tds: - net_total = net_total_for_tds - # Adding args as purchase invoice to get TDS amount args = frappe._dict({ 'company': self.company, - 'doctype': 'Purchase Invoice', + 'doctype': 'Payment Entry', 'supplier': self.party, 'posting_date': self.posting_date, 'net_total': net_total }) - tax_withholding_details, tax_deducted_on_advances = get_party_tax_withholding_details(args, self.tax_withholding_category) + tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category) if not tax_withholding_details: return @@ -1593,10 +1585,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= }) pe.set_difference_amount() - if doc.doctype == 'Purchase Order' and doc.apply_tds: - pe.apply_tax_withholding_amount = 1 - pe.tax_withholding_category = doc.tax_withholding_category - return pe def get_bank_cash_account(doc, bank_account): diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 78396a5e589..aa2408e092e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1160,25 +1160,21 @@ class TestPurchaseInvoice(unittest.TestCase): # Create Purchase Order with TDS applied po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item', posting_date='2021-09-15') - po.apply_tds = 1 - po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' po.save() po.submit() - # Update Unrealized Profit / Loss Account which is used as default advance tax account - frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC') - # Create Payment Entry Against the order payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name) payment_entry.paid_from = 'Cash - _TC' + payment_entry.apply_tax_withholding_amount = 1 + payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual' payment_entry.save() payment_entry.submit() # Check GLE for Payment Entry expected_gle = [ - ['_Test Account Excise Duty - _TC', 3000, 0], ['Cash - _TC', 0, 27000], - ['Creditors - _TC', 27000, 0], + ['Creditors - _TC', 30000, 0], ['TDS Payable - _TC', 0, 3000], ] @@ -1204,9 +1200,7 @@ class TestPurchaseInvoice(unittest.TestCase): # Zero net effect on final TDS Payable on invoice expected_gle = [ ['_Test Account Cost for Goods Sold - _TC', 30000], - ['_Test Account Excise Duty - _TC', -3000], - ['Creditors - _TC', -27000], - ['TDS Payable - _TC', 0] + ['Creditors - _TC', -30000] ] gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount @@ -1219,6 +1213,14 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][1], gle.amount) + payment_entry.load_from_db() + self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000) + + purchase_invoice.cancel() + + payment_entry.load_from_db() + self.assertEqual(payment_entry.taxes[0].allocated_amount, 0) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql("""select account, debit, credit, posting_date from `tabGL Entry` 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 fe156cb029e..5bb9b931854 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -197,7 +197,10 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, to_date=tax_details.to_date, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers - tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details) + tax_deducted_on_advances = 0 + + if inv.doctype == 'Purchase Invoice': + tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details) tax_deducted = 0 if taxable_vouchers: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9cf2e339168..c5260295c4e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -145,15 +145,16 @@ class AccountsController(TransactionBase): self.validate_party() self.validate_currency() + if self.doctype in ['Purchase Invoice', 'Sales Invoice']: + pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" + if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): + self.set_advances() + if self.doctype == 'Purchase Invoice': self.calculate_paid_amount() # apply tax withholding only if checked and applicable self.set_tax_withholding() - if self.doctype in ['Purchase Invoice', 'Sales Invoice']: - pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" - if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): - self.set_advances() self.set_advance_gain_or_loss() From 7f06c8ca5768fcd16c077888324cb59244ccf032 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 26 Nov 2021 10:27:57 +0530 Subject: [PATCH 58/68] fix: Incorrect indentation --- erpnext/controllers/accounts_controller.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c5260295c4e..56e03e15ec2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -150,12 +150,6 @@ class AccountsController(TransactionBase): if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() - if self.doctype == 'Purchase Invoice': - self.calculate_paid_amount() - # apply tax withholding only if checked and applicable - self.set_tax_withholding() - - self.set_advance_gain_or_loss() if self.is_return: @@ -165,6 +159,11 @@ class AccountsController(TransactionBase): self.set_inter_company_account() + if self.doctype == 'Purchase Invoice': + self.calculate_paid_amount() + # apply tax withholding only if checked and applicable + self.set_tax_withholding() + validate_regional(self) if self.doctype != 'Material Request': From 73bfd59846de9a65b5d95f6204d4301f9e22e80b Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 26 Nov 2021 11:53:35 +0530 Subject: [PATCH 59/68] fix: checkbox triggers get_items and sub_assembly buttons (#28558) --- .../doctype/production_plan/production_plan.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index b171086b7ce..0babf875e75 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -238,6 +238,12 @@ frappe.ui.form.on('Production Plan', { method: "get_items", freeze: true, doc: frm.doc, + callback: function() { + frm.refresh_field("po_items"); + if (frm.doc.sub_assembly_items.length > 0) { + frm.trigger("get_sub_assembly_items"); + } + } }); }, From 9c913c9b2dfbe8c87b9e08b16a4427d89bde689e Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 26 Nov 2021 12:00:13 +0530 Subject: [PATCH 60/68] fix: over billing validation (#28218) --- .../sales_invoice/test_sales_invoice.py | 26 +++++++++++ erpnext/controllers/accounts_controller.py | 45 ++++++++++++++++--- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b5453ac56eb..37bea70f167 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2397,6 +2397,32 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + def test_over_billing_case_against_delivery_note(self): + ''' + Test a case where duplicating the item with qty = 1 in the invoice + allows overbilling even if it is disabled + ''' + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance') + frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0) + + dn = create_delivery_note() + dn.submit() + + si = make_sales_invoice(dn.name) + # make a copy of first item and add it to invoice + item_copy = frappe.copy_doc(si.items[0]) + si.append('items', item_copy) + si.save() + + with self.assertRaises(frappe.ValidationError) as err: + si.submit() + + self.assertTrue("cannot overbill" in str(err.exception).lower()) + + frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3190feac705..1654ac9c390 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1011,6 +1011,7 @@ class AccountsController(TransactionBase): def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): from erpnext.controllers.status_updater import get_allowance_for + item_allowance = {} global_qty_allowance, global_amount_allowance = None, None @@ -1031,12 +1032,7 @@ class AccountsController(TransactionBase): .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange") continue - already_billed = frappe.db.sql(""" - select sum(%s) - from `tab%s` - where %s=%s and docstatus=1 and parent != %s - """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on) total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)) @@ -1064,6 +1060,43 @@ class AccountsController(TransactionBase): frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") .format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True) + def get_billed_amount_for_item(self, item, item_ref_dn, based_on): + ''' + Returns Sum of Amount of + Sales/Purchase Invoice Items + that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`) + that are submitted OR not submitted but are under current invoice + ''' + + from frappe.query_builder import Criterion + from frappe.query_builder.functions import Sum + + item_doctype = frappe.qb.DocType(item.doctype) + based_on_field = frappe.qb.Field(based_on) + join_field = frappe.qb.Field(item_ref_dn) + + result = ( + frappe.qb.from_(item_doctype) + .select(Sum(based_on_field)) + .where( + join_field == item.get(item_ref_dn) + ).where( + Criterion.any([ # select all items from other invoices OR current invoices + Criterion.all([ # for selecting items from other invoices + item_doctype.docstatus == 1, + item_doctype.parent != self.name + ]), + Criterion.all([ # for selecting items from current invoice, that are linked to same reference + item_doctype.docstatus == 0, + item_doctype.parent == self.name, + item_doctype.name != item.name + ]) + ]) + ) + ).run() + + return result[0][0] if result else 0 + def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) From 2a5f663a1e9a63c6a3ac46de4643cfd038329c8a Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:58:43 +0530 Subject: [PATCH 61/68] fix: Customer, Supplier heatmap data not rendering (#28553) * fix: adding get_timeline_data import on supplier.py, customer.py --- erpnext/buying/doctype/supplier/supplier.py | 6 +++++- erpnext/selling/doctype/customer/customer.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 32659d607b0..14d2ccdb50d 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -11,7 +11,11 @@ from frappe.contacts.address_and_contact import ( ) from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options -from erpnext.accounts.party import get_dashboard_info, validate_party_accounts +from erpnext.accounts.party import ( # noqa + get_dashboard_info, + get_timeline_data, + validate_party_accounts, +) from erpnext.utilities.transaction_base import TransactionBase diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 2e2b8b77d39..0c8c53aabe3 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -18,7 +18,11 @@ from frappe.model.rename_doc import update_linked_doctypes from frappe.utils import cint, cstr, flt, get_formatted_email, today from frappe.utils.user import get_users_with_role -from erpnext.accounts.party import get_dashboard_info, validate_party_accounts +from erpnext.accounts.party import ( # noqa + get_dashboard_info, + get_timeline_data, + validate_party_accounts, +) from erpnext.utilities.transaction_base import TransactionBase From ca8dec0cf2d8a74eed1db939f5865f7536431a25 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Nov 2021 13:24:32 +0530 Subject: [PATCH 62/68] fix: Use `get_all` instead of `get_list` for child doctype (#28538) * fix(Student Attendance Tool): Use `get_all` instead of `get_list` for child doctype * fix(Course Schedule): incorrect fetch from value * fix: sider * fix(Gratuity): Use `get_all` instead of `get_list` for child doctype --- erpnext/education/api.py | 14 +- .../course_schedule/course_schedule.json | 449 ++---------------- .../student_attendance_tool.py | 30 +- erpnext/payroll/doctype/gratuity/gratuity.py | 2 +- .../payroll/doctype/gratuity/test_gratuity.py | 4 +- 5 files changed, 67 insertions(+), 432 deletions(-) diff --git a/erpnext/education/api.py b/erpnext/education/api.py index 53d1482f951..d9013b08161 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -125,7 +125,7 @@ def get_student_guardians(student): :param student: Student. """ - guardians = frappe.get_list("Student Guardian", fields=["guardian"] , + guardians = frappe.get_all("Student Guardian", fields=["guardian"] , filters={"parent": student}) return guardians @@ -137,10 +137,10 @@ def get_student_group_students(student_group, include_inactive=0): :param student_group: Student Group. """ if include_inactive: - students = frappe.get_list("Student Group Student", fields=["student", "student_name"] , + students = frappe.get_all("Student Group Student", fields=["student", "student_name"] , filters={"parent": student_group}, order_by= "group_roll_number") else: - students = frappe.get_list("Student Group Student", fields=["student", "student_name"] , + students = frappe.get_all("Student Group Student", fields=["student", "student_name"] , filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") return students @@ -164,7 +164,7 @@ def get_fee_components(fee_structure): :param fee_structure: Fee Structure. """ if fee_structure: - fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") + fs = frappe.get_all("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") return fs @@ -175,7 +175,7 @@ def get_fee_schedule(program, student_category=None): :param program: Program. :param student_category: Student Category """ - fs = frappe.get_list("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] , + fs = frappe.get_all("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] , filters={"parent": program, "student_category": student_category }, order_by= "idx") return fs @@ -220,7 +220,7 @@ def get_assessment_criteria(course): :param Course: Course """ - return frappe.get_list("Course Assessment Criteria", \ + return frappe.get_all("Course Assessment Criteria", fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx") @@ -253,7 +253,7 @@ def get_assessment_details(assessment_plan): :param Assessment Plan: Assessment Plan """ - return frappe.get_list("Assessment Plan Criteria", \ + return frappe.get_all("Assessment Plan Criteria", fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx") diff --git a/erpnext/education/doctype/course_schedule/course_schedule.json b/erpnext/education/doctype/course_schedule/course_schedule.json index 8c6746bda8c..38d9b508f04 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.json +++ b/erpnext/education/doctype/course_schedule/course_schedule.json @@ -1,520 +1,143 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2015-09-09 16:34:04.960369", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Document", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "student_group", + "instructor", + "instructor_name", + "column_break_2", + "naming_series", + "course", + "color", + "section_break_6", + "schedule_date", + "room", + "column_break_9", + "from_time", + "to_time", + "title" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "student_group", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Student Group", - "length": 0, - "no_copy": 0, "options": "Student Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "instructor", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Instructor", - "length": 0, - "no_copy": 0, "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "instructor.Instructor_name", + "fetch_from": "instructor.instructor_name", "fieldname": "instructor_name", "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Instructor Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "fieldname": "naming_series", "fieldtype": "Select", - "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": "Naming Series", - "length": 0, - "no_copy": 0, "options": "EDU-CSH-.YYYY.-", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "course", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Course", - "length": 0, - "no_copy": 0, "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "color", "fieldtype": "Color", - "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": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Today", "fieldname": "schedule_date", "fieldtype": "Date", - "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": "Schedule Date", - "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": "Schedule Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "room", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Room", - "length": 0, - "no_copy": 0, "options": "Room", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_time", "fieldtype": "Time", - "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": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "to_time", "fieldtype": "Time", - "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": "To Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "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": "Title" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-08-21 14:44:51.827225", + "links": [], + "modified": "2021-11-24 11:57:08.164449", "modified_by": "Administrator", "module": "Education", "name": "Course Schedule", - "name_case": "", + "naming_rule": "By \"Naming Series\" field", "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": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "restrict_to_domain": "Education", - "show_name_in_global_search": 0, "sort_field": "schedule_date", "sort_order": "DESC", - "title_field": "title", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "title" } \ No newline at end of file diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py index 4e3f98d6558..7deb6b18da5 100644 --- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py +++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py @@ -14,24 +14,36 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour student_list = [] student_attendance_list = [] - if based_on=="Course Schedule": + if based_on == "Course Schedule": student_group = frappe.db.get_value("Course Schedule", course_schedule, "student_group") if student_group: - student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , \ + student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"], filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") if not student_list: - student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , + student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"], filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") + table = frappe.qb.DocType("Student Attendance") + if course_schedule: - student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ - course_schedule= %s''', (course_schedule), as_dict=1) + student_attendance_list = ( + frappe.qb.from_(table) + .select(table.student, table.status) + .where( + (table.course_schedule == course_schedule) + ) + ).run(as_dict=True) else: - student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ - student_group= %s and date= %s and \ - (course_schedule is Null or course_schedule='')''', - (student_group, date), as_dict=1) + student_attendance_list = ( + frappe.qb.from_(table) + .select(table.student, table.status) + .where( + (table.student_group == student_group) + & (table.date == date) + & (table.course_schedule == "") | (table.course_schedule.isnull()) + ) + ).run(as_dict=True) for attendance in student_attendance_list: for student in student_list: diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index f563c08a047..476990a88e1 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -193,7 +193,7 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen sal_slip = get_last_salary_slip(employee) if not sal_slip: frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) - component_and_amounts = frappe.get_list("Salary Detail", + component_and_amounts = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 78355cae645..93cba06da15 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -48,7 +48,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation - component_amount = frappe.get_list("Salary Detail", + component_amount = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, @@ -84,7 +84,7 @@ class TestGratuity(unittest.TestCase): self.assertEqual(floor(experience), gratuity.current_work_experience) #amount Calculation - component_amount = frappe.get_list("Salary Detail", + component_amount = frappe.get_all("Salary Detail", filters={ "docstatus": 1, 'parent': sal_slip, From efec85d5cd59eacd8cce4d519caf914740cc6c12 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 26 Nov 2021 16:29:21 +0530 Subject: [PATCH 63/68] chore: correct __version__ on develop branch. (#28582) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 9d99ebb7bb6..a5de50fb06f 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import frappe from erpnext.hooks import regional_overrides -__version__ = '13.9.0' +__version__ = '14.0.0-dev' def get_default_company(user=None): '''Get default company for user''' From 7ff30a4b2b64df191836f0d7695c7007167fb076 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 26 Nov 2021 17:33:57 +0530 Subject: [PATCH 64/68] fix: incorrect balance for warehouses (#28583) --- .../warehouse_wise_item_balance_age_and_value.py | 4 ++-- 1 file changed, 2 insertions(+), 2 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 d3af5f61e33..4d1491b1b55 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 @@ -46,8 +46,8 @@ def execute(filters=None): item_balance.setdefault((item, item_map[item]["item_group"]), []) total_stock_value = 0.00 for wh in warehouse_list: - row += [qty_dict.bal_qty] if wh.name in warehouse else [0.00] - total_stock_value += qty_dict.bal_val if wh.name in warehouse else 0.00 + row += [qty_dict.bal_qty] if wh.name == warehouse else [0.00] + total_stock_value += qty_dict.bal_val if wh.name == warehouse else 0.00 item_balance[(item, item_map[item]["item_group"])].append(row) item_value.setdefault((item, item_map[item]["item_group"]),[]) From 69a17b9e51cdf4c3214b8d70a2fed3e4e13a81b4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 27 Nov 2021 20:07:02 +0530 Subject: [PATCH 65/68] refactor: move Bin queries to qb/orm (#28522) --- erpnext/stock/doctype/bin/bin.py | 135 ++++++++++++++++++------------- 1 file changed, 81 insertions(+), 54 deletions(-) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 48b1cc53967..da9c66d9962 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -4,6 +4,8 @@ import frappe from frappe.model.document import Document +from frappe.query_builder import Case +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import flt, nowdate @@ -19,34 +21,42 @@ class Bin(Document): - flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract)) def get_first_sle(self): - sle = frappe.db.sql(""" - select * from `tabStock Ledger Entry` - where item_code = %s - and warehouse = %s - order by timestamp(posting_date, posting_time) asc, creation asc - limit 1 - """, (self.item_code, self.warehouse), as_dict=1) - return sle and sle[0] or None + sle = frappe.qb.DocType("Stock Ledger Entry") + first_sle = ( + frappe.qb.from_(sle) + .select("*") + .where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse)) + .orderby(sle.posting_date, sle.posting_time, sle.creation) + .limit(1) + ).run(as_dict=True) + + return first_sle and first_sle[0] or None def update_reserved_qty_for_production(self): '''Update qty reserved for production from Production Item tables in open work orders''' - self.reserved_qty_for_production = frappe.db.sql(''' - SELECT - CASE WHEN ifnull(skip_transfer, 0) = 0 THEN - SUM(item.required_qty - item.transferred_qty) - ELSE - SUM(item.required_qty - item.consumed_qty) - END - FROM `tabWork Order` pro, `tabWork Order Item` item - WHERE - item.item_code = %s - and item.parent = pro.name - and pro.docstatus = 1 - and item.source_warehouse = %s - and pro.status not in ("Stopped", "Completed") - and (item.required_qty > item.transferred_qty or item.required_qty > item.consumed_qty) - ''', (self.item_code, self.warehouse))[0][0] + + wo = frappe.qb.DocType("Work Order") + wo_item = frappe.qb.DocType("Work Order Item") + + self.reserved_qty_for_production = ( + frappe.qb + .from_(wo) + .from_(wo_item) + .select(Case() + .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty)) + .else_(Sum(wo_item.required_qty - wo_item.consumed_qty)) + ) + .where( + (wo_item.item_code == self.item_code) + & (wo_item.parent == wo.name) + & (wo.docstatus == 1) + & (wo_item.source_warehouse == self.warehouse) + & (wo.status.notin(["Stopped", "Completed"])) + & ((wo_item.required_qty > wo_item.transferred_qty) + | (wo_item.required_qty > wo_item.consumed_qty)) + ) + ).run()[0][0] or 0.0 self.set_projected_qty() @@ -55,36 +65,53 @@ class Bin(Document): def update_reserved_qty_for_sub_contracting(self): #reserved qty - reserved_qty_for_sub_contract = frappe.db.sql(''' - select ifnull(sum(itemsup.required_qty),0) - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup - where - itemsup.rm_item_code = %s - and itemsup.parent = po.name - and po.docstatus = 1 - and po.is_subcontracted = 'Yes' - and po.status != 'Closed' - and po.per_received < 100 - and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0] - #Get Transferred Entries - materials_transferred = frappe.db.sql(""" - select - ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0) - from - `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po - where - se.docstatus=1 - and se.purpose='Send to Subcontractor' - and ifnull(se.purchase_order, '') !='' - and (sed.item_code = %(item)s or sed.original_item = %(item)s) - and se.name = sed.parent - and se.purchase_order = po.name - and po.docstatus = 1 - and po.is_subcontracted = 'Yes' - and po.status != 'Closed' - and po.per_received < 100 - """, {'item': self.item_code})[0][0] + po = frappe.qb.DocType("Purchase Order") + supplied_item = frappe.qb.DocType("Purchase Order Item Supplied") + + reserved_qty_for_sub_contract = ( + frappe.qb + .from_(po) + .from_(supplied_item) + .select(Sum(Coalesce(supplied_item.required_qty, 0))) + .where( + (supplied_item.rm_item_code == self.item_code) + & (po.name == supplied_item.parent) + & (po.docstatus == 1) + & (po.is_subcontracted == "Yes") + & (po.status != "Closed") + & (po.per_received < 100) + & (supplied_item.reserve_warehouse == self.warehouse) + ) + ).run()[0][0] or 0.0 + + se = frappe.qb.DocType("Stock Entry") + se_item = frappe.qb.DocType("Stock Entry Detail") + + materials_transferred = ( + frappe.qb + .from_(se) + .from_(se_item) + .from_(po) + .select(Sum( + Case() + .when(se.is_return == 1, se_item.transfer_qty * -1) + .else_(se_item.transfer_qty) + )) + .where( + (se.docstatus == 1) + & (se.purpose == "Send to Subcontractor") + & (Coalesce(se.purchase_order, "") != "") + & ((se_item.item_code == self.item_code) + | (se_item.original_item == self.item_code)) + & (se.name == se_item.parent) + & (po.name == se.purchase_order) + & (po.docstatus == 1) + & (po.is_subcontracted == "Yes") + & (po.status != "Closed") + & (po.per_received < 100) + ) + ).run()[0][0] or 0.0 if reserved_qty_for_sub_contract > materials_transferred: reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred @@ -160,4 +187,4 @@ def update_qty(bin_name, args): 'indented_qty': indented_qty, 'planned_qty': planned_qty, 'projected_qty': projected_qty - }) \ No newline at end of file + }) From cdaf0a04cf92d27a5fae7a298ee8397b0b321491 Mon Sep 17 00:00:00 2001 From: xdlumertz Date: Sat, 27 Nov 2021 12:03:35 -0300 Subject: [PATCH 66/68] fix(ux): allow translations (#28455) * Translation * Translations Co-authored-by: xdlumertz --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++--- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 6 +++--- erpnext/controllers/stock_controller.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7aeb872b284..c1b056b9c70 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -339,7 +339,7 @@ class PaymentEntry(AccountsController): for k, v in no_oustanding_refs.items(): frappe.msgprint( _("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.") - .format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount")) + .format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount"))) + "

" + _("If this is undesirable please cancel the corresponding Payment Entry."), title=_("Warning"), indicator="orange") @@ -611,7 +611,7 @@ class PaymentEntry(AccountsController): if not total_negative_outstanding: frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice") - .format(self.payment_type, ("to" if self.party_type=="Customer" else "from"), + .format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")), self.party_type), InvalidPaymentEntry) elif paid_amount - additional_charges > total_negative_outstanding: @@ -1092,7 +1092,7 @@ def get_outstanding_reference_documents(args): if not data: frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.") - .format(args.get("party_type").lower(), frappe.bold(args.get("party")))) + .format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))) return data diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 516133ab635..48b5cb9459c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -728,7 +728,7 @@ class PurchaseInvoice(BuyingController): "account": self.stock_received_but_not_billed, "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), - "remarks": self.remarks or "Accounting Entry for Stock", + "remarks": self.remarks or _("Accounting Entry for Stock"), "cost_center": self.cost_center, "project": item.project or self.project }, item=item) @@ -936,7 +936,7 @@ class PurchaseInvoice(BuyingController): "cost_center": tax.cost_center, "against": self.supplier, "credit": valuation_tax[tax.name], - "remarks": self.remarks or "Accounting Entry for Stock" + "remarks": self.remarks or _("Accounting Entry for Stock") }, item=tax)) @property diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a1f3ee4b063..4538675d073 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -978,7 +978,7 @@ frappe.ui.form.on('Sales Invoice', { } if (frm.doc.is_debit_note) { - frm.set_df_property('return_against', 'label', 'Adjustment Against'); + frm.set_df_property('return_against', 'label', __('Adjustment Against')); } if (frappe.boot.active_domains.includes("Healthcare")) { @@ -988,10 +988,10 @@ frappe.ui.form.on('Sales Invoice', { if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) { frm.add_custom_button(__('Healthcare Services'), function() { get_healthcare_services_to_invoice(frm); - },"Get Items From"); + },__("Get Items From")); frm.add_custom_button(__('Prescriptions'), function() { get_drugs_to_invoice(frm); - },"Get Items From"); + },__("Get Items From")); } } else { diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index ae170945aa6..7073e32f536 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -134,7 +134,7 @@ class StockController(AccountsController): "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", + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": flt(sle.stock_value_difference, precision), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", }, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) @@ -143,7 +143,7 @@ class StockController(AccountsController): "account": expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, - "remarks": self.get("remarks") or "Accounting Entry for Stock", + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(sle.stock_value_difference, precision), "project": item_row.get("project") or self.get("project"), "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No" From 43038aab7952a9e613ed92b14f5b14212d6667ca Mon Sep 17 00:00:00 2001 From: hrwx Date: Sat, 27 Nov 2021 21:46:13 +0000 Subject: [PATCH 67/68] fix: do not add gst fields if no indian company --- erpnext/patches.txt | 2 +- .../v13_0/create_gst_payment_entry_fields.py | 45 ++++++++++--------- erpnext/www/shop-by-category/__init__.py | 0 3 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e475229125f..897e70ce256 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -287,7 +287,7 @@ erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field -erpnext.patches.v13_0.create_gst_payment_entry_fields +erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021 erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.fix_invoice_statuses erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py index 7e6d67ce931..416694559cb 100644 --- a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py +++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py @@ -9,24 +9,29 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges') frappe.reload_doc('accounts', 'doctype', 'payment_entry') - custom_fields = { - 'Payment Entry': [ - dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', - print_hide=1, collapsible=1), - dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', - print_hide=1, options='Address'), - dict(fieldname='company_gstin', label='Company GSTIN', - fieldtype='Data', insert_after='company_address', - fetch_from='company_address.gstin', print_hide=1, read_only=1), - dict(fieldname='place_of_supply', label='Place of Supply', - fieldtype='Data', insert_after='company_gstin', - print_hide=1, read_only=1), - dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply', - print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), - dict(fieldname='customer_gstin', label='Customer GSTIN', - fieldtype='Data', insert_after='customer_address', - fetch_from='customer_address.gstin', print_hide=1, read_only=1) - ] - } + if frappe.db.exists('Company', {'country': 'India'}): + custom_fields = { + 'Payment Entry': [ + dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', + print_hide=1, collapsible=1), + dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', + print_hide=1, options='Address'), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='company_gstin', + print_hide=1, read_only=1), + dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply', + print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='customer_address', + fetch_from='customer_address.gstin', print_hide=1, read_only=1) + ] + } - create_custom_fields(custom_fields, update=True) \ No newline at end of file + create_custom_fields(custom_fields, update=True) + else: + fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin'] + for field in fields: + frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}") \ No newline at end of file diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From de6f104b740d4854663a6246a4a3b2a31b1034be Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 27 Nov 2021 23:16:34 +0000 Subject: [PATCH 68/68] Delete __init__.py --- erpnext/www/shop-by-category/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000