From d6208d2e456ebe4242380c96ce510bdd273a37e5 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 6 Jun 2023 12:07:46 +0530 Subject: [PATCH 01/63] fix(ux): serial and batch bundle status --- .../serial_and_batch_bundle_list.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle_list.js diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle_list.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle_list.js new file mode 100644 index 0000000000..355fcd0aaa --- /dev/null +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle_list.js @@ -0,0 +1,11 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.listview_settings["Serial and Batch Bundle"] = { + add_fields: ["is_cancelled"], + get_indicator: function (doc) { + if (doc.is_cancelled) { + return [__("Cancelled"), "red", "is_cancelled,=,1"]; + } + }, +}; From 47ce6de57d248f085070b723c51feb312dc21272 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 6 Jun 2023 15:19:04 +0530 Subject: [PATCH 02/63] feat: ability to create quotation against a prospect --- erpnext/accounts/party.py | 6 +++--- erpnext/public/js/utils/party.js | 4 ++-- erpnext/selling/doctype/quotation/quotation.js | 13 +++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index f86dd8f57e..e606308a1b 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -647,12 +647,12 @@ def set_taxes( else: args.update(get_party_details(party, party_type)) - if party_type in ("Customer", "Lead"): + if party_type in ("Customer", "Lead", "Prospect"): args.update({"tax_type": "Sales"}) - if party_type == "Lead": + if party_type in ["Lead", "Prospect"]: args["customer"] = None - del args["lead"] + del args[frappe.scrub(party_type)] else: args.update({"tax_type": "Purchase"}) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 644adff1e2..5c41aa0680 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -16,8 +16,8 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { || (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) { let party_type = "Customer"; - if (frm.doc.quotation_to && frm.doc.quotation_to === "Lead") { - party_type = "Lead"; + if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) { + party_type = frm.doc.quotation_to; } args = { diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 83fa472d68..2d5c3fa961 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Quotation', { frm.set_query("quotation_to", function() { return{ "filters": { - "name": ["in", ["Customer", "Lead"]], + "name": ["in", ["Customer", "Lead", "Prospect"]], } } }); @@ -160,19 +160,16 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. } set_dynamic_field_label(){ - if (this.frm.doc.quotation_to == "Customer") - { + if (this.frm.doc.quotation_to == "Customer") { this.frm.set_df_property("party_name", "label", "Customer"); this.frm.fields_dict.party_name.get_query = null; - } - - if (this.frm.doc.quotation_to == "Lead") - { + } else if (this.frm.doc.quotation_to == "Lead") { this.frm.set_df_property("party_name", "label", "Lead"); - this.frm.fields_dict.party_name.get_query = function() { return{ query: "erpnext.controllers.queries.lead_query" } } + } else if (this.frm.doc.quotation_to == "Prospect") { + this.frm.set_df_property("party_name", "label", "Prospect"); } } From 5a0aacc0b6e7a04da18044b1bee1256f2eb2f1d4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 6 Jun 2023 15:39:22 +0530 Subject: [PATCH 03/63] fix: get party details --- erpnext/controllers/selling_controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index d3195332d1..6f1a50dab1 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -43,7 +43,6 @@ class SellingController(StockController): self.set_serial_and_batch_bundle(table_field) def set_missing_values(self, for_validate=False): - super(SellingController, self).set_missing_values(for_validate) # set contact and address details for customer, if they are not mentioned @@ -62,7 +61,7 @@ class SellingController(StockController): elif self.doctype == "Quotation" and self.party_name: if self.quotation_to == "Customer": customer = self.party_name - else: + elif self.quotation_to == "Lead": lead = self.party_name if customer: From 2ffcca6f10c71582b8bb39cfe61c57cd3f3e4463 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Jun 2023 10:06:13 +0530 Subject: [PATCH 04/63] fix: Interest Accrual on Loan Topup (#35555) * fix: Interest Accrual on Loan Topup * chore: CI * chore: Ignore test --- .../test_loan_disbursement.py | 1 - .../loan_interest_accrual.py | 26 ++++++++++++++++--- .../doctype/loan_repayment/loan_repayment.py | 4 +-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index 4daa2edb28..9cc6ec9d4b 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase): interest = per_day_interest * 15 self.assertEqual(amounts["pending_principal_amount"], 1500000) - self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2)) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index cac3f1f0f3..ced63942ba 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController): frappe.throw(_("Interest Amount or Principal Amount is mandatory")) if not self.last_accrual_date: - self.last_accrual_date = get_last_accrual_date(self.loan) + self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date) def on_submit(self): self.make_gl_entries() @@ -274,14 +274,14 @@ def make_loan_interest_accrual_entry(args): def get_no_of_days_for_interest_accural(loan, posting_date): - last_interest_accrual_date = get_last_accrual_date(loan.name) + last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date) no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1 return no_of_days -def get_last_accrual_date(loan): +def get_last_accrual_date(loan, posting_date): last_posting_date = frappe.db.sql( """ SELECT MAX(posting_date) from `tabLoan Interest Accrual` WHERE loan = %s and docstatus = 1""", @@ -289,12 +289,30 @@ def get_last_accrual_date(loan): ) if last_posting_date[0][0]: + last_interest_accrual_date = last_posting_date[0][0] # interest for last interest accrual date is already booked, so add 1 day - return add_days(last_posting_date[0][0], 1) + last_disbursement_date = get_last_disbursement_date(loan, posting_date) + + if last_disbursement_date and getdate(last_disbursement_date) > getdate( + last_interest_accrual_date + ): + last_interest_accrual_date = last_disbursement_date + + return add_days(last_interest_accrual_date, 1) else: return frappe.db.get_value("Loan", loan, "disbursement_date") +def get_last_disbursement_date(loan, posting_date): + last_disbursement_date = frappe.db.get_value( + "Loan Disbursement", + {"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)}, + "MAX(posting_date)", + ) + + return last_disbursement_date + + def days_in_year(year): days = 365 diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 8a185f8683..82aab4a882 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -101,7 +101,7 @@ class LoanRepayment(AccountsController): if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision): if not self.is_term_loan: # get last loan interest accrual date - last_accrual_date = get_last_accrual_date(self.against_loan) + last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date) # get posting date upto which interest has to be accrued per_day_interest = get_per_day_interest( @@ -725,7 +725,7 @@ def get_amounts(amounts, against_loan, posting_date): if due_date: pending_days = date_diff(posting_date, due_date) + 1 else: - last_accrual_date = get_last_accrual_date(against_loan_doc.name) + last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date) pending_days = date_diff(posting_date, last_accrual_date) + 1 if pending_days > 0: From 76197cc437f2750531cbc0482d0d3774087225e3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Jun 2023 10:11:32 +0530 Subject: [PATCH 05/63] chore: Default role profiles (#35584) --- erpnext/setup/install.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index cf9600eb49..28e5e5cdd2 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -25,6 +25,7 @@ def after_install(): create_default_success_action() create_default_energy_point_rules() create_incoterms() + create_default_role_profiles() add_company_to_session_defaults() add_standard_navbar_items() add_app_name() @@ -202,3 +203,16 @@ def setup_log_settings(): def hide_workspaces(): for ws in ["Integration", "Settings"]: frappe.db.set_value("Workspace", ws, "public", 0) + + +def create_default_role_profiles(): + for module in ["Accounts", "Stock", "Manufacturing"]: + create_role_profile(module) + + +def create_role_profile(module): + role_profile = frappe.new_doc("Role Profile") + role_profile.role_profile = _("{0} User").format(module) + role_profile.append("roles", {"role": module + " User"}) + role_profile.append("roles", {"role": module + " Manager"}) + role_profile.insert() From 4507cb3cd724207f6e0080e00b122113c13f826a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 7 Jun 2023 11:58:36 +0530 Subject: [PATCH 06/63] fix: `enqueue_after_commit` wherever it makes sense (#35588) --- .../doctype/accounting_dimension/accounting_dimension.py | 6 ++++-- .../period_closing_voucher/period_closing_voucher.py | 1 + .../manufacturing/doctype/bom_update_log/bom_update_log.py | 2 ++ erpnext/stock/doctype/item/item.py | 1 + erpnext/stock/doctype/stock_settings/stock_settings.py | 1 + 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index ce1ed336d0..81ff6a52db 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -50,13 +50,15 @@ class AccountingDimension(Document): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) else: - frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long") + frappe.enqueue( + make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True + ) def on_trash(self): if frappe.flags.in_test: delete_accounting_dimension(doc=self) else: - frappe.enqueue(delete_accounting_dimension, doc=self, queue="long") + frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True) def set_fieldname_and_label(self): if not self.label: 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 9d636adc57..641f4528c5 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -44,6 +44,7 @@ class PeriodClosingVoucher(AccountsController): voucher_type="Period Closing Voucher", voucher_no=self.name, queue="long", + enqueue_after_commit=True, ) frappe.msgprint( _("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py index 7477f9528e..17b5aae966 100644 --- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py +++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py @@ -88,12 +88,14 @@ class BOMUpdateLog(Document): boms=boms, timeout=40000, now=frappe.flags.in_test, + enqueue_after_commit=True, ) else: frappe.enqueue( method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise", update_doc=self, now=frappe.flags.in_test, + enqueue_after_commit=True, ) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 3cc59bed19..f91a991173 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -714,6 +714,7 @@ class Item(Document): template=self, now=frappe.flags.in_test, timeout=600, + enqueue_after_commit=True, ) def validate_has_variants(self): diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index e25c8439ca..3b6db64a30 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -94,6 +94,7 @@ class StockSettings(Document): frappe.enqueue( "erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions", now=frappe.flags.in_test, + enqueue_after_commit=True, ) def validate_pending_reposts(self): From 0166f69b319c1a6722324f3316a47ade77de76f0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 7 Jun 2023 15:09:49 +0530 Subject: [PATCH 07/63] chore: extend default role profiles --- erpnext/setup/install.py | 42 ++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 28e5e5cdd2..1d5428a40e 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -206,13 +206,39 @@ def hide_workspaces(): def create_default_role_profiles(): - for module in ["Accounts", "Stock", "Manufacturing"]: - create_role_profile(module) + for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items(): + role_profile = frappe.new_doc("Role Profile") + role_profile.role_profile = role_profile_name + for role in roles: + role_profile.append("roles", {"role": role}) + + role_profile.insert(ignore_permissions=True) -def create_role_profile(module): - role_profile = frappe.new_doc("Role Profile") - role_profile.role_profile = _("{0} User").format(module) - role_profile.append("roles", {"role": module + " User"}) - role_profile.append("roles", {"role": module + " Manager"}) - role_profile.insert() +DEFAULT_ROLE_PROFILES = { + "Inventory": [ + "Stock User", + "Stock Manager", + "Item Manager", + ], + "Manufacturing": [ + "Stock User", + "Manufacturing User", + "Manufacturing Manager", + ], + "Accounts": [ + "Accounts User", + "Accounts Manager", + ], + "Sales": [ + "Sales User", + "Stock User", + "Sales Manager", + ], + "Purchase": [ + "Item Manager", + "Stock User", + "Purchase User", + "Purchase Manager", + ], +} From 0108b1abe256710f787a08657d80c0e58f5d38b8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Jun 2023 21:33:36 +0530 Subject: [PATCH 08/63] fix: Improve validation message (#35489) * fix: Improve validation message * Update erpnext/selling/doctype/customer/customer.py Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/selling/doctype/customer/customer.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index f15ac1257b..6367e3cb6a 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -454,12 +454,12 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, customer_outstanding += flt(extra_amount) if credit_limit > 0 and flt(customer_outstanding) > credit_limit: - msgprint( - _("Credit limit has been crossed for customer {0} ({1}/{2})").format( - customer, customer_outstanding, credit_limit - ) + message = _("Credit limit has been crossed for customer {0} ({1}/{2})").format( + customer, customer_outstanding, credit_limit ) + message += "

" + # If not authorized person raise exception credit_controller_role = frappe.db.get_single_value("Accounts Settings", "credit_controller") if not credit_controller_role or credit_controller_role not in frappe.get_roles(): @@ -480,7 +480,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, "
  • ".join(credit_controller_users_formatted) ) - message = _( + message += _( "Please contact any of the following users to extend the credit limits for {0}: {1}" ).format(customer, user_list) @@ -488,7 +488,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, # prompt them to send out an email to the controller users frappe.msgprint( message, - title="Notify", + title=_("Credit Limit Crossed"), raise_exception=1, primary_action={ "label": "Send Email", @@ -519,7 +519,6 @@ def get_customer_outstanding( customer, company, ignore_outstanding_sales_order=False, cost_center=None ): # Outstanding based on GL Entries - cond = "" if cost_center: lft, rgt = frappe.get_cached_value("Cost Center", cost_center, ["lft", "rgt"]) From a9a47a51e49aeb84770ce94b62b5966952b56040 Mon Sep 17 00:00:00 2001 From: HarryPaulo Date: Wed, 7 Jun 2023 13:05:52 -0300 Subject: [PATCH 09/63] =?UTF-8?q?fix:=20based=20on=20status=5Fupdate.py=20?= =?UTF-8?q?update=20opportunity=20status=20to=20converted=E2=80=A6=20(#351?= =?UTF-8?q?45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: based on status_update.py update opportunity status to converted on sales submit --- erpnext/selling/doctype/sales_order/sales_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d3c2347216..58b9df8dab 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -230,6 +230,7 @@ class SalesOrder(SellingController): frappe.throw(_("Quotation {0} is cancelled").format(quotation)) doc.set_status(update=True) + doc.update_opportunity("Converted" if flag == "submit" else "Quotation") def validate_drop_ship(self): for d in self.get("items"): From e1f3b7cbc8c7f2d5f7b2e8786905cbe74afabe2b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:19:02 +0200 Subject: [PATCH 10/63] fix: pass translated label to change button type (#35564) fix: change button type for translated labels Co-authored-by: Marica --- .../doctype/bank_clearance/bank_clearance.js | 10 +++++----- .../bank_reconciliation_tool.js | 2 +- .../payment_reconciliation/payment_reconciliation.js | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js index 71f2dcca1b..7af635bdd6 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js @@ -41,7 +41,7 @@ frappe.ui.form.on("Bank Clearance", { frm.trigger("get_payment_entries") ); - frm.change_custom_button_type('Get Payment Entries', null, 'primary'); + frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary'); }, update_clearance_date: function(frm) { @@ -53,8 +53,8 @@ frappe.ui.form.on("Bank Clearance", { frm.refresh_fields(); if (!frm.doc.payment_entries.length) { - frm.change_custom_button_type('Get Payment Entries', null, 'primary'); - frm.change_custom_button_type('Update Clearance Date', null, 'default'); + frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary'); + frm.change_custom_button_type(__('Update Clearance Date'), null, 'default'); } } }); @@ -72,8 +72,8 @@ frappe.ui.form.on("Bank Clearance", { frm.trigger("update_clearance_date") ); - frm.change_custom_button_type('Get Payment Entries', null, 'default'); - frm.change_custom_button_type('Update Clearance Date', null, 'primary'); + frm.change_custom_button_type(__('Get Payment Entries'), null, 'default'); + frm.change_custom_button_type(__('Update Clearance Date'), null, 'primary'); } } }); diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index d977261441..0647a5ccf3 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -81,7 +81,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { frm.add_custom_button(__('Get Unreconciled Entries'), function() { frm.trigger("make_reconciliation_tool"); }); - frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary'); + frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary'); }, diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08d38dde47..2283677634 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -65,22 +65,22 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.add_custom_button(__('Get Unreconciled Entries'), () => this.frm.trigger("get_unreconciled_entries") ); - this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary'); + this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary'); } if (this.frm.doc.invoices.length && this.frm.doc.payments.length) { this.frm.add_custom_button(__('Allocate'), () => this.frm.trigger("allocate") ); - this.frm.change_custom_button_type('Allocate', null, 'primary'); - this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default'); + this.frm.change_custom_button_type(__('Allocate'), null, 'primary'); + this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default'); } if (this.frm.doc.allocation.length) { this.frm.add_custom_button(__('Reconcile'), () => this.frm.trigger("reconcile") ); - this.frm.change_custom_button_type('Reconcile', null, 'primary'); - this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default'); - this.frm.change_custom_button_type('Allocate', null, 'default'); + this.frm.change_custom_button_type(__('Reconcile'), null, 'primary'); + this.frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'default'); + this.frm.change_custom_button_type(__('Allocate'), null, 'default'); } // check for any running reconciliation jobs From e9d7b9f0f44c94a2cd73757c2fd20250e7e81d66 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:21:49 +0200 Subject: [PATCH 11/63] fix: column formatting in Bank Reconciliation Tool (#35540) * fix(Bank Reconciliation): format Date column * fix(Bank Reconciliation): format Party column * fix(Bank Reconciliation): actions button - wrong closing tag - explicitly quote data-name * fix(Bank Reco): format date and link in dialog --- .../data_table_manager.js | 10 +++---- .../dialog_manager.js | 27 +++++++++---------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js index 0cda93880f..5e5742af8c 100644 --- a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js @@ -40,8 +40,8 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { name: __("Date"), editable: false, width: 100, + format: frappe.form.formatters.Date, }, - { name: __("Party Type"), editable: false, @@ -117,17 +117,13 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { return [ row["date"], row["party_type"], - row["party"], + frappe.form.formatters.Link(row["party"], {options: row["party_type"]}), row["description"], row["deposit"], row["withdrawal"], row["unallocated_amount"], row["reference_number"], - ` - ` ]; } diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index 1271e38049..cbb64ca61b 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -76,30 +76,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { callback: (result) => { const data = result.message; - if (data && data.length > 0) { const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper; proposals_wrapper.show(); this.dialog.fields_dict.no_matching_vouchers.$wrapper.hide(); - this.data = []; - data.forEach((row) => { - const reference_date = row[5] ? row[5] : row[8]; - this.data.push([ - row[1], - row[2], - reference_date, - format_currency(row[3], row[9]), - row[4], - row[6], - ]); - }); + this.data = data.map((row) => this.format_row(row)); this.get_dt_columns(); this.get_datatable(proposals_wrapper); } else { const proposals_wrapper = this.dialog.fields_dict.payment_proposals.$wrapper; proposals_wrapper.hide(); this.dialog.fields_dict.no_matching_vouchers.$wrapper.show(); - } this.dialog.show(); }, @@ -122,6 +109,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { name: __("Reference Date"), editable: false, width: 120, + format: frappe.form.formatters.Date, }, { name: __("Remaining"), @@ -141,6 +129,17 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { ]; } + format_row(row) { + return [ + row[1], // Document Type + frappe.form.formatters.Link(row[2], {options: row[1]}), // Document Name + row[5] || row[8], // Reference Date + format_currency(row[3], row[9]), // Remaining + row[4], // Reference Number + row[6], // Party + ]; + } + get_datatable(proposals_wrapper) { if (!this.datatable) { const datatable_options = { From 3b409af9a0621a308b941bb1e58c60cb0307a708 Mon Sep 17 00:00:00 2001 From: Dirk van der Laarse Date: Wed, 7 Jun 2023 18:31:44 +0200 Subject: [PATCH 12/63] fix: exclude disabled customers when fetching customers on process statement of accounts (#35539) fix: exclude disabled when fetching customers --- .../process_statement_of_accounts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index b36f33be3b..c9d03012b8 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -158,7 +158,10 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll return frappe.get_list( "Customer", fields=["name", "customer_name", "email_id"], - filters=[[fields_dict[customer_collection], "IN", selected]], + filters=[ + ["disabled", "=", 0], + [fields_dict[customer_collection], "IN", selected] + ], ) From 0dde4d4c69f673565696711834f85853dd8673b8 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:32:38 +0200 Subject: [PATCH 13/63] refactor: use delete_contact_and_address (#34497) Co-authored-by: Deepesh Garg --- erpnext/crm/doctype/lead/lead.py | 31 +++++------------------- erpnext/crm/doctype/prospect/prospect.py | 28 ++++----------------- 2 files changed, 11 insertions(+), 48 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 2a588d8d13..a98886c648 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -3,7 +3,10 @@ import frappe from frappe import _ -from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.contacts.address_and_contact import ( + delete_contact_and_address, + load_address_and_contact, +) from frappe.email.inbox import link_communication_to_document from frappe.model.mapper import get_mapped_doc from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address @@ -40,9 +43,8 @@ class Lead(SellingController, CRMNote): self.update_prospect() def on_trash(self): - frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name) - - self.unlink_dynamic_links() + frappe.db.set_value("Issue", {"lead": self.name}, "lead", None) + delete_contact_and_address(self.doctype, self.name) self.remove_link_from_prospect() def set_full_name(self): @@ -119,27 +121,6 @@ class Lead(SellingController, CRMNote): ) lead_row.db_update() - def unlink_dynamic_links(self): - links = frappe.get_all( - "Dynamic Link", - filters={"link_doctype": self.doctype, "link_name": self.name}, - fields=["parent", "parenttype"], - ) - - for link in links: - linked_doc = frappe.get_doc(link["parenttype"], link["parent"]) - - if len(linked_doc.get("links")) == 1: - linked_doc.delete(ignore_permissions=True) - else: - to_remove = None - for d in linked_doc.get("links"): - if d.link_doctype == self.doctype and d.link_name == self.name: - to_remove = d - if to_remove: - linked_doc.remove(to_remove) - linked_doc.save(ignore_permissions=True) - def remove_link_from_prospect(self): prospects = self.get_linked_prospects() diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py index fbb115883f..8b66a83f2a 100644 --- a/erpnext/crm/doctype/prospect/prospect.py +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -2,7 +2,10 @@ # For license information, please see license.txt import frappe -from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.contacts.address_and_contact import ( + delete_contact_and_address, + load_address_and_contact, +) from frappe.model.mapper import get_mapped_doc from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events @@ -16,7 +19,7 @@ class Prospect(CRMNote): self.link_with_lead_contact_and_address() def on_trash(self): - self.unlink_dynamic_links() + delete_contact_and_address(self.doctype, self.name) def after_insert(self): carry_forward_communication_and_comments = frappe.db.get_single_value( @@ -54,27 +57,6 @@ class Prospect(CRMNote): linked_doc.append("links", {"link_doctype": self.doctype, "link_name": self.name}) linked_doc.save(ignore_permissions=True) - def unlink_dynamic_links(self): - links = frappe.get_all( - "Dynamic Link", - filters={"link_doctype": self.doctype, "link_name": self.name}, - fields=["parent", "parenttype"], - ) - - for link in links: - linked_doc = frappe.get_doc(link["parenttype"], link["parent"]) - - if len(linked_doc.get("links")) == 1: - linked_doc.delete(ignore_permissions=True) - else: - to_remove = None - for d in linked_doc.get("links"): - if d.link_doctype == self.doctype and d.link_name == self.name: - to_remove = d - if to_remove: - linked_doc.remove(to_remove) - linked_doc.save(ignore_permissions=True) - @frappe.whitelist() def make_customer(source_name, target_doc=None): From f732cac6780e0f9c9aead4df381a0bbe5774354c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Jun 2023 22:06:05 +0530 Subject: [PATCH 14/63] fix: Project in item-wise sales register (#35596) --- .../item_wise_sales_register/item_wise_sales_register.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index dd9c073612..0ebe13f4f3 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None): `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.unrealized_profit_loss_account, `tabSales Invoice`.is_internal_customer, - `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, + `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, + `tabSales Invoice Item`.project, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, From 781548e46e4a8135c46b2fc995da5bb6636eaf57 Mon Sep 17 00:00:00 2001 From: Trusted Computer <75872475+trustedcomputer@users.noreply.github.com> Date: Wed, 7 Jun 2023 09:41:49 -0700 Subject: [PATCH 15/63] Fix: CSS not applied to product title (#35582) In an erpnext website, the /all-products route shows website items that have been published to the web site. In the list view (erpnext/e_commerce/product_ui/list.js), the css class is null for the product title. Instead, inline style statements have been added in that can not be modified by overriding CSS. This fix uses a similar approach to that which is taken in the grid view (erpnext/e_commerce/product_ui/grid.js). It removes the null CSS parameter in the product title link as well as the inline style statement. Then, as in the grid view, the product title is wrapped in a div tag with the product_title CSS class. This makes it possible to style the product title as desired with a CSS override. Closes #35580 --- erpnext/e_commerce/product_ui/list.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js index 894a7cb3d8..c8fd7672c8 100644 --- a/erpnext/e_commerce/product_ui/list.js +++ b/erpnext/e_commerce/product_ui/list.js @@ -78,9 +78,10 @@ erpnext.ProductList = class { let title_html = `
    `; title_html += ` `; @@ -201,4 +202,4 @@ erpnext.ProductList = class { } } -}; \ No newline at end of file +}; From 0c12d4d3c5ebb7d5bd6cf70dfde026c810a7b66a Mon Sep 17 00:00:00 2001 From: Didiman1998 <118364772+Didiman1998@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:05:44 +0200 Subject: [PATCH 16/63] fix: remove code that causes upscrolling (#35140) Co-authored-by: Dietmar Fischer --- erpnext/stock/doctype/item/item.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 9a9ddf4404..6f1f981e2b 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -772,12 +772,6 @@ $.extend(erpnext.item, { if (modal) { $(modal).removeClass("modal-dialog-scrollable"); } - }) - .on("awesomplete-close", () => { - let modal = field.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).addClass("modal-dialog-scrollable"); - } }); }); }, From 992d61bd90bf026ad060caba4d627d33f7eeba29 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 8 Jun 2023 15:11:46 +0530 Subject: [PATCH 17/63] chore: fix travis --- .../process_statement_of_accounts.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index c9d03012b8..67dbe09d0d 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -158,10 +158,7 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll return frappe.get_list( "Customer", fields=["name", "customer_name", "email_id"], - filters=[ - ["disabled", "=", 0], - [fields_dict[customer_collection], "IN", selected] - ], + filters=[["disabled", "=", 0], [fields_dict[customer_collection], "IN", selected]], ) From b91bb17779eb04a5d14bbc065ec2ed72d97baa5a Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Jun 2023 16:19:09 +0200 Subject: [PATCH 18/63] refactor: get default contact or address (#35248) * refactor: get_party_shipping_address * refactor: get_default_contact * chore: adding docstrings * fix: keep original order * fix: use get_all instead of get_list --------- Co-authored-by: ruthra kumar --- erpnext/accounts/party.py | 80 ++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e606308a1b..07b865e66c 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -2,6 +2,8 @@ # License: GNU General Public License v3. See license.txt +from typing import Optional + import frappe from frappe import _, msgprint, scrub from frappe.contacts.doctype.address.address import ( @@ -850,7 +852,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None): return company_wise_info -def get_party_shipping_address(doctype, name): +def get_party_shipping_address(doctype: str, name: str) -> Optional[str]: """ Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true. and/or `is_shipping_address = 1`. @@ -861,22 +863,23 @@ def get_party_shipping_address(doctype, name): :param name: Party name :return: String """ - out = frappe.db.sql( - "SELECT dl.parent " - "from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name " - "where " - "dl.link_doctype=%s " - "and dl.link_name=%s " - "and dl.parenttype='Address' " - "and ifnull(ta.disabled, 0) = 0 and" - "(ta.address_type='Shipping' or ta.is_shipping_address=1) " - "order by ta.is_shipping_address desc, ta.address_type desc limit 1", - (doctype, name), + shipping_addresses = frappe.get_all( + "Address", + filters=[ + ["Dynamic Link", "link_doctype", "=", doctype], + ["Dynamic Link", "link_name", "=", name], + ["disabled", "=", 0], + ], + or_filters=[ + ["is_shipping_address", "=", 1], + ["address_type", "=", "Shipping"], + ], + pluck="name", + limit=1, + order_by="is_shipping_address DESC", ) - if out: - return out[0][0] - else: - return "" + + return shipping_addresses[0] if shipping_addresses else None def get_partywise_advanced_payment_amount( @@ -910,31 +913,32 @@ def get_partywise_advanced_payment_amount( return frappe._dict(data) -def get_default_contact(doctype, name): +def get_default_contact(doctype: str, name: str) -> Optional[str]: """ - Returns default contact for the given doctype and name. - Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact. + Returns contact name only if there is a primary contact for given doctype and name. + + Else returns None + + :param doctype: Party Doctype + :param name: Party name + :return: String """ - out = frappe.db.sql( - """ - SELECT dl.parent, c.is_primary_contact, c.is_billing_contact - FROM `tabDynamic Link` dl - INNER JOIN `tabContact` c ON c.name = dl.parent - WHERE - dl.link_doctype=%s AND - dl.link_name=%s AND - dl.parenttype = 'Contact' - ORDER BY is_primary_contact DESC, is_billing_contact DESC - """, - (doctype, name), + contacts = frappe.get_all( + "Contact", + filters=[ + ["Dynamic Link", "link_doctype", "=", doctype], + ["Dynamic Link", "link_name", "=", name], + ], + or_filters=[ + ["is_primary_contact", "=", 1], + ["is_billing_contact", "=", 1], + ], + pluck="name", + limit=1, + order_by="is_primary_contact DESC, is_billing_contact DESC", ) - if out: - try: - return out[0][0] - except Exception: - return None - else: - return None + + return contacts[0] if contacts else None def add_party_account(party_type, party, company, account): From 65b2e1fc33b920a0dbcca840845b181f4622bfe2 Mon Sep 17 00:00:00 2001 From: Christian Werner Date: Thu, 8 Jun 2023 16:30:40 +0200 Subject: [PATCH 19/63] fix: set parent_project when creating a new timesheet (#35607) fix "When Creating a new Timesheet from an Task - parent_project is empty" #35578 --- erpnext/projects/doctype/task/task.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index b9f4ec6ad1..333d4d9b6a 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -304,6 +304,7 @@ def set_tasks_as_overdue(): @frappe.whitelist() def make_timesheet(source_name, target_doc=None, ignore_permissions=False): def set_missing_values(source, target): + target.parent_project = source.project target.append( "time_logs", { From 446253ff399151709b3e4db2d186001b17b247b6 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 7 Jun 2023 09:30:23 +0530 Subject: [PATCH 20/63] fix: `TypeError` in Closing Stock Balance --- .../doctype/closing_stock_balance/closing_stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index a7963726ae..295d979b83 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -51,7 +51,7 @@ class ClosingStockBalance(Document): for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]: if self.get(fieldname): - query = query.where(table.get(fieldname) == self.get(fieldname)) + query = query.where(table[fieldname] == self.get(fieldname)) query = query.run(as_dict=True) From 93e3fe8445f4e22e524fce88b75857eebd71ef3e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 9 Jun 2023 10:23:45 +0530 Subject: [PATCH 21/63] fix: reset entries qty to `1` for serial item --- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 7e5cac986e..cc55bd6ea8 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -133,7 +133,7 @@ class SerialandBatchBundle(Document): def calculate_total_qty(self, save=True): self.total_qty = 0.0 for d in self.entries: - d.qty = abs(d.qty) if d.qty else 0 + d.qty = 1 if self.has_serial_no and abs(d.qty) > 1 else abs(d.qty) if d.qty else 0 d.stock_value_difference = abs(d.stock_value_difference) if d.stock_value_difference else 0 if self.type_of_transaction == "Outward": d.qty *= -1 From 1d904c0a86131a23a5c9e86ef474cd63483a452e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 9 Jun 2023 12:05:02 +0530 Subject: [PATCH 22/63] fix(ux): make qty field read-only for serial item --- .../serial_and_batch_bundle/serial_and_batch_bundle.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index b02ad71b16..614b4662ab 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -134,6 +134,10 @@ frappe.ui.form.on('Serial and Batch Bundle', { frm.fields_dict.entries.grid.update_docfield_property( 'batch_no', 'read_only', !frm.doc.has_batch_no ); + + frm.fields_dict.entries.grid.update_docfield_property( + 'qty', 'read_only', frm.doc.has_serial_no + ); }, set_queries(frm) { From 1f28ca717ef125b0d5c09f8c57a5f0ac72806295 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 9 Jun 2023 12:06:46 +0530 Subject: [PATCH 23/63] fix(ux): set entries qty to `1` before making the field read-only --- .../serial_and_batch_bundle/serial_and_batch_bundle.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index 614b4662ab..f28eed35cb 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -127,6 +127,14 @@ frappe.ui.form.on('Serial and Batch Bundle', { }, toggle_fields(frm) { + if (frm.doc.has_serial_no) { + frm.doc.entries.forEach(row => { + if (Math.abs(row.qty) !== 1) { + frappe.model.set_value(row.doctype, row.name, "qty", 1); + } + }) + } + frm.fields_dict.entries.grid.update_docfield_property( 'serial_no', 'read_only', !frm.doc.has_serial_no ); From b5c5a90f71bfd2246605ff5fd7233bda4b5cc87b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 9 Jun 2023 12:20:35 +0530 Subject: [PATCH 24/63] fix(ux): set warehouse for new row --- .../serial_and_batch_bundle/serial_and_batch_bundle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index f28eed35cb..d50bdba3c9 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -210,9 +210,9 @@ frappe.ui.form.on('Serial and Batch Bundle', { frappe.ui.form.on("Serial and Batch Entry", { - ledgers_add(frm, cdt, cdn) { + entries_add(frm, cdt, cdn) { if (frm.doc.warehouse) { - locals[cdt][cdn].warehouse = frm.doc.warehouse; + frappe.model.set_value(cdt, cdn, 'warehouse', frm.doc.warehouse); } }, }) \ No newline at end of file From 7bd369c49bede687cb5f99873dd78cd6e8baa82e Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Fri, 9 Jun 2023 12:53:10 +0530 Subject: [PATCH 25/63] fix: calculate wdv depr schedule properly for existing assets [dev] (#35614) * fix: calculate wdv depr schedule properly for existing assets * fix: calculate wdv depr schedule properly for existing assets properly * chore: properly call _get_pro_rata_amt --- erpnext/assets/doctype/asset/test_asset.py | 4 +-- .../asset_depreciation_schedule.py | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c64f29699d..0dfcee4325 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -812,14 +812,14 @@ class TestDepreciationMethods(AssetSetup): number_of_depreciations_booked=1, opening_accumulated_depreciation=50000, expected_value_after_useful_life=10000, - depreciation_start_date="2030-12-31", + depreciation_start_date="2031-12-31", total_number_of_depreciations=3, frequency_of_depreciation=12, ) self.assertEqual(asset.status, "Draft") - expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]] + expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]] schedules = [ [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 982d376ae4..deae8c7891 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -10,6 +10,7 @@ from frappe.utils import ( cint, date_diff, flt, + get_first_day, get_last_day, getdate, is_last_day_of_the_month, @@ -271,8 +272,14 @@ class AssetDepreciationSchedule(Document): break # For first row - if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation: - from_date = add_days(asset_doc.available_for_use_date, -1) + if ( + n == 0 + and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) + and not self.opening_accumulated_depreciation + ): + from_date = add_days( + asset_doc.available_for_use_date, -1 + ) # needed to calc depr amount for available_for_use_date too depreciation_amount, days, months = _get_pro_rata_amt( row, depreciation_amount, @@ -281,10 +288,18 @@ class AssetDepreciationSchedule(Document): has_wdv_or_dd_non_yearly_pro_rata, ) elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: - from_date = add_months( - getdate(asset_doc.available_for_use_date), - (self.number_of_depreciations_booked * row.frequency_of_depreciation), - ) + if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)): + from_date = get_last_day( + add_months( + getdate(asset_doc.available_for_use_date), + ((self.number_of_depreciations_booked - 1) * row.frequency_of_depreciation), + ) + ) + else: + from_date = add_months( + getdate(add_days(asset_doc.available_for_use_date, -1)), + (self.number_of_depreciations_booked * row.frequency_of_depreciation), + ) depreciation_amount, days, months = _get_pro_rata_amt( row, depreciation_amount, @@ -702,3 +717,9 @@ def get_asset_depr_schedule_name(asset_name, status, finance_book=None): ["status", "=", status], ], ) + + +def is_first_day_of_the_month(date): + first_day_of_the_month = get_first_day(date) + + return getdate(first_day_of_the_month) == getdate(date) From e9a6191af97a37acc6ab90f91677d072fa3c94b1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 9 Jun 2023 20:33:46 +0530 Subject: [PATCH 26/63] fix: added process loss in job card --- .../doctype/job_card/job_card.js | 12 +++- .../doctype/job_card/job_card.json | 9 ++- .../doctype/job_card/job_card.py | 57 +++++++++++++++---- .../doctype/work_order/work_order.js | 15 +++-- .../doctype/work_order/work_order.json | 5 +- .../doctype/work_order/work_order.py | 14 +++-- .../work_order_operation.json | 20 ++++++- .../stock/doctype/stock_entry/stock_entry.js | 15 +++++ .../doctype/stock_entry/stock_entry.json | 15 +++-- .../stock/doctype/stock_entry/stock_entry.py | 31 ++++++++-- 10 files changed, 155 insertions(+), 38 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 5305db318b..4a46d57744 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -83,7 +83,7 @@ frappe.ui.form.on('Job Card', { // and if stock mvt for WIP is required if (frm.doc.work_order) { frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => { - if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0) { + if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0 || !frm.doc.items.length) { frm.trigger("prepare_timer_buttons"); } }); @@ -411,6 +411,16 @@ frappe.ui.form.on('Job Card', { } }); + if (frm.doc.total_completed_qty && frm.doc.for_quantity > frm.doc.total_completed_qty) { + let flt_precision = precision('for_quantity', frm.doc); + let process_loss_qty = ( + flt(frm.doc.for_quantity, flt_precision) + - flt(frm.doc.total_completed_qty, flt_precision) + ); + + frm.set_value('process_loss_qty', process_loss_qty); + } + refresh_field("total_completed_qty"); } }); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index f49f018d20..5d912faca9 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -39,6 +39,7 @@ "time_logs", "section_break_13", "total_completed_qty", + "process_loss_qty", "column_break_15", "total_time_in_mins", "section_break_8", @@ -448,11 +449,17 @@ "no_copy": 1, "options": "Serial and Batch Bundle", "print_hide": 1 + }, + { + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "label": "Process Loss Qty", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2023-05-23 09:56:43.826602", + "modified": "2023-06-09 12:04:55.534264", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index fcaa3fd276..496cbfd0a6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -161,7 +161,7 @@ class JobCard(Document): self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) for row in self.sub_operations: - self.total_completed_qty += row.completed_qty + self.c += row.completed_qty def get_overlap_for(self, args, check_next_available_slot=False): production_capacity = 1 @@ -451,6 +451,9 @@ class JobCard(Document): }, ) + def before_save(self): + self.set_process_loss() + def on_submit(self): self.validate_transfer_qty() self.validate_job_card() @@ -487,19 +490,35 @@ class JobCard(Document): ) ) - if self.for_quantity and self.total_completed_qty != self.for_quantity: + precision = self.precision("total_completed_qty") + total_completed_qty = flt( + flt(self.total_completed_qty, precision) + flt(self.process_loss_qty, precision) + ) + + if self.for_quantity and flt(total_completed_qty, precision) != flt( + self.for_quantity, precision + ): total_completed_qty = bold(_("Total Completed Qty")) qty_to_manufacture = bold(_("Qty to Manufacture")) frappe.throw( _("The {0} ({1}) must be equal to {2} ({3})").format( total_completed_qty, - bold(self.total_completed_qty), + bold(flt(total_completed_qty, precision)), qty_to_manufacture, bold(self.for_quantity), ) ) + def set_process_loss(self): + precision = self.precision("total_completed_qty") + + self.process_loss_qty = 0.0 + if self.total_completed_qty and self.for_quantity > self.total_completed_qty: + self.process_loss_qty = flt(self.for_quantity, precision) - flt( + self.total_completed_qty, precision + ) + def update_work_order(self): if not self.work_order: return @@ -511,7 +530,7 @@ class JobCard(Document): ): return - for_quantity, time_in_mins = 0, 0 + for_quantity, time_in_mins, process_loss_qty = 0, 0, 0 from_time_list, to_time_list = [], [] field = "operation_id" @@ -519,6 +538,7 @@ class JobCard(Document): if data and len(data) > 0: for_quantity = flt(data[0].completed_qty) time_in_mins = flt(data[0].time_in_mins) + process_loss_qty = flt(data[0].process_loss_qty) wo = frappe.get_doc("Work Order", self.work_order) @@ -526,8 +546,8 @@ class JobCard(Document): self.update_corrective_in_work_order(wo) elif self.operation_id: - self.validate_produced_quantity(for_quantity, wo) - self.update_work_order_data(for_quantity, time_in_mins, wo) + self.validate_produced_quantity(for_quantity, process_loss_qty, wo) + self.update_work_order_data(for_quantity, process_loss_qty, time_in_mins, wo) def update_corrective_in_work_order(self, wo): wo.corrective_operation_cost = 0.0 @@ -542,11 +562,11 @@ class JobCard(Document): wo.flags.ignore_validate_update_after_submit = True wo.save() - def validate_produced_quantity(self, for_quantity, wo): + def validate_produced_quantity(self, for_quantity, process_loss_qty, wo): if self.docstatus < 2: return - if wo.produced_qty > for_quantity: + if wo.produced_qty > for_quantity + process_loss_qty: first_part_msg = _( "The {0} {1} is used to calculate the valuation cost for the finished good {2}." ).format( @@ -561,7 +581,7 @@ class JobCard(Document): _("{0} {1}").format(first_part_msg, second_part_msg), JobCardCancelError, title=_("Error") ) - def update_work_order_data(self, for_quantity, time_in_mins, wo): + def update_work_order_data(self, for_quantity, process_loss_qty, time_in_mins, wo): workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate") jc = frappe.qb.DocType("Job Card") jctl = frappe.qb.DocType("Job Card Time Log") @@ -582,6 +602,7 @@ class JobCard(Document): for data in wo.operations: if data.get("name") == self.operation_id: data.completed_qty = for_quantity + data.process_loss_qty = process_loss_qty data.actual_operation_time = time_in_mins data.actual_start_time = time_data[0].start_time if time_data else None data.actual_end_time = time_data[0].end_time if time_data else None @@ -599,7 +620,11 @@ class JobCard(Document): def get_current_operation_data(self): return frappe.get_all( "Job Card", - fields=["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], + fields=[ + "sum(total_time_in_mins) as time_in_mins", + "sum(total_completed_qty) as completed_qty", + "sum(process_loss_qty) as process_loss_qty", + ], filters={ "docstatus": 1, "work_order": self.work_order, @@ -777,7 +802,7 @@ class JobCard(Document): data = frappe.get_all( "Work Order Operation", - fields=["operation", "status", "completed_qty"], + fields=["operation", "status", "completed_qty", "sequence_id"], filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)}, order_by="sequence_id, idx", ) @@ -795,6 +820,16 @@ class JobCard(Document): OperationSequenceError, ) + if row.completed_qty < current_operation_qty: + msg = f"""The completed quantity {bold(current_operation_qty)} + of an operation {bold(self.operation)} cannot be greater + than the completed quantity {bold(row.completed_qty)} + of a previous operation + {bold(row.operation)}. + """ + + frappe.throw(_(msg)) + 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 closed.")) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index d0c9966f8b..c1a078d65e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -139,7 +139,7 @@ frappe.ui.form.on("Work Order", { } if (frm.doc.status != "Closed") { - if (frm.doc.docstatus === 1 + if (frm.doc.docstatus === 1 && frm.doc.status !== "Completed" && frm.doc.operations && frm.doc.operations.length) { const not_completed = frm.doc.operations.filter(d => { @@ -256,6 +256,12 @@ frappe.ui.form.on("Work Order", { label: __('Batch Size'), read_only: 1 }, + { + fieldtype: 'Int', + fieldname: 'sequence_id', + label: __('Sequence Id'), + read_only: 1 + }, ], data: operations_data, in_place_edit: true, @@ -280,8 +286,8 @@ frappe.ui.form.on("Work Order", { var pending_qty = 0; frm.doc.operations.forEach(data => { - if(data.completed_qty != frm.doc.qty) { - pending_qty = frm.doc.qty - flt(data.completed_qty); + if(data.completed_qty + data.process_loss_qty != frm.doc.qty) { + pending_qty = frm.doc.qty - flt(data.completed_qty) - flt(data.process_loss_qty); if (pending_qty) { dialog.fields_dict.operations.df.data.push({ @@ -290,7 +296,8 @@ frappe.ui.form.on("Work Order", { 'workstation': data.workstation, 'batch_size': data.batch_size, 'qty': pending_qty, - 'pending_qty': pending_qty + 'pending_qty': pending_qty, + 'sequence_id': data.sequence_id }); } } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index aecace673c..a236f2a339 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -46,8 +46,8 @@ "required_items_section", "materials_and_operations_tab", "operations_section", - "operations", "transfer_material_against", + "operations", "time", "planned_start_date", "planned_end_date", @@ -330,7 +330,6 @@ "label": "Expected Delivery Date" }, { - "collapsible": 1, "fieldname": "operations_section", "fieldtype": "Section Break", "label": "Operations", @@ -591,7 +590,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-04-06 12:35:12.149827", + "modified": "2023-06-09 13:20:09.154362", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 3265b8f1d4..bfdcf615c1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -245,7 +245,9 @@ class WorkOrder(Document): status = "Not Started" if flt(self.material_transferred_for_manufacturing) > 0: status = "In Process" - if flt(self.produced_qty) >= flt(self.qty): + + total_qty = flt(self.produced_qty) + flt(self.process_loss_qty) + if flt(total_qty) >= flt(self.qty): status = "Completed" else: status = "Cancelled" @@ -761,13 +763,15 @@ class WorkOrder(Document): max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage / 100 * flt(self.qty)) for d in self.get("operations"): - if not d.completed_qty: + precision = d.precision("completed_qty") + qty = flt(d.completed_qty, precision) + flt(d.process_loss_qty, precision) + if not qty: d.status = "Pending" - elif flt(d.completed_qty) < flt(self.qty): + elif flt(qty) < flt(self.qty): d.status = "Work in Progress" - elif flt(d.completed_qty) == flt(self.qty): + elif flt(qty) == flt(self.qty): d.status = "Completed" - elif flt(d.completed_qty) <= max_allowed_qty_for_wo: + elif flt(qty) <= max_allowed_qty_for_wo: d.status = "Completed" else: frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'")) 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 31b920145e..de1f67f13f 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -2,12 +2,14 @@ "actions": [], "creation": "2014-10-16 14:35:41.950175", "doctype": "DocType", + "editable_grid": 1, "engine": "InnoDB", "field_order": [ "details", "operation", "status", "completed_qty", + "process_loss_qty", "column_break_4", "bom", "workstation_type", @@ -36,6 +38,7 @@ "fieldtype": "Section Break" }, { + "columns": 2, "fieldname": "operation", "fieldtype": "Link", "in_list_view": 1, @@ -46,6 +49,7 @@ "reqd": 1 }, { + "columns": 2, "fieldname": "bom", "fieldtype": "Link", "in_list_view": 1, @@ -62,7 +66,7 @@ "oldfieldtype": "Text" }, { - "columns": 1, + "columns": 2, "description": "Operation completed for how many finished goods?", "fieldname": "completed_qty", "fieldtype": "Float", @@ -80,6 +84,7 @@ "options": "Pending\nWork in Progress\nCompleted" }, { + "columns": 1, "fieldname": "workstation", "fieldtype": "Link", "in_list_view": 1, @@ -115,7 +120,7 @@ "fieldname": "time_in_mins", "fieldtype": "Float", "in_list_view": 1, - "label": "Operation Time", + "label": "Time", "oldfieldname": "time_in_mins", "oldfieldtype": "Currency", "reqd": 1 @@ -203,12 +208,21 @@ "fieldtype": "Link", "label": "Workstation Type", "options": "Workstation Type" + }, + { + "columns": 2, + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Process Loss Qty", + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-09 01:37:56.563068", + "modified": "2023-06-09 14:03:01.612909", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 2c8e7a7da4..00b447192b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -656,6 +656,21 @@ frappe.ui.form.on('Stock Entry', { }); } }, + + process_loss_qty(frm) { + if (frm.doc.process_loss_qty) { + frm.doc.process_loss_percentage = flt(frm.doc.process_loss_qty / frm.doc.fg_completed_qty * 100, precision("process_loss_qty", frm.doc)); + refresh_field("process_loss_percentage"); + } + }, + + process_loss_percentage(frm) { + debugger + if (frm.doc.process_loss_percentage) { + frm.doc.process_loss_qty = flt((frm.doc.fg_completed_qty * frm.doc.process_loss_percentage) / 100 , precision("process_loss_qty", frm.doc)); + refresh_field("process_loss_qty"); + } + } }); frappe.ui.form.on('Stock Entry Detail', { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index bc5533fd2d..9bf679b895 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -24,6 +24,7 @@ "company", "posting_date", "posting_time", + "column_break_eaoa", "set_posting_time", "inspection_required", "apply_putaway_rule", @@ -640,16 +641,16 @@ }, { "collapsible": 1, + "depends_on": "eval: doc.fg_completed_qty > 0 && in_list([\"Manufacture\", \"Repack\"], doc.purpose)", "fieldname": "section_break_7qsm", "fieldtype": "Section Break", "label": "Process Loss" }, { - "depends_on": "process_loss_percentage", + "depends_on": "eval: doc.fg_completed_qty > 0 && in_list([\"Manufacture\", \"Repack\"], doc.purpose)", "fieldname": "process_loss_qty", "fieldtype": "Float", - "label": "Process Loss Qty", - "read_only": 1 + "label": "Process Loss Qty" }, { "fieldname": "column_break_e92r", @@ -657,8 +658,6 @@ }, { "depends_on": "eval:doc.from_bom && doc.fg_completed_qty", - "fetch_from": "bom_no.process_loss_percentage", - "fetch_if_empty": 1, "fieldname": "process_loss_percentage", "fieldtype": "Percent", "label": "% Process Loss" @@ -667,6 +666,10 @@ "fieldname": "items_section", "fieldtype": "Section Break", "label": "Items" + }, + { + "fieldname": "column_break_eaoa", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", @@ -674,7 +677,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-04-06 12:42:56.673180", + "modified": "2023-06-09 15:46:28.418339", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f19df83791..816957c1c8 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -442,13 +442,16 @@ class StockEntry(StockController): if self.purpose == "Manufacture" and self.work_order: for d in self.items: if d.is_finished_item: + if self.process_loss_qty: + d.qty = self.fg_completed_qty - self.process_loss_qty + item_wise_qty.setdefault(d.item_code, []).append(d.qty) precision = frappe.get_precision("Stock Entry Detail", "qty") for item_code, qty_list in item_wise_qty.items(): total = flt(sum(qty_list), precision) - if (self.fg_completed_qty - total) > 0: + if (self.fg_completed_qty - total) > 0 and not self.process_loss_qty: self.process_loss_qty = flt(self.fg_completed_qty - total, precision) self.process_loss_percentage = flt(self.process_loss_qty * 100 / self.fg_completed_qty) @@ -1640,16 +1643,36 @@ class StockEntry(StockController): if self.purpose not in ("Manufacture", "Repack"): return - self.process_loss_qty = 0.0 - if not self.process_loss_percentage: + precision = self.precision("process_loss_qty") + if self.work_order: + data = frappe.get_all( + "Work Order Operation", + filters={"parent": self.work_order}, + fields=["max(process_loss_qty) as process_loss_qty"], + ) + + if data and data[0].process_loss_qty is not None: + process_loss_qty = data[0].process_loss_qty + if flt(self.process_loss_qty, precision) != flt(process_loss_qty, precision): + self.process_loss_qty = flt(process_loss_qty, precision) + + frappe.msgprint( + _("The Process Loss Qty has reset as per job cards Process Loss Qty"), alert=True + ) + + if not self.process_loss_percentage and not self.process_loss_qty: self.process_loss_percentage = frappe.get_cached_value( "BOM", self.bom_no, "process_loss_percentage" ) - if self.process_loss_percentage: + if self.process_loss_percentage and not self.process_loss_qty: self.process_loss_qty = flt( (flt(self.fg_completed_qty) * flt(self.process_loss_percentage)) / 100 ) + else: + self.process_loss_percentage = flt( + (flt(self.process_loss_qty) / flt(self.fg_completed_qty)) * 100 + ) def set_work_order_details(self): if not getattr(self, "pro_doc", None): From dcbd7d5f1f66938cc4146eefe3a52f07be681a0d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 10 Jun 2023 20:55:30 +0530 Subject: [PATCH 27/63] fix: incorrect TCS amount while customer has advance payment (#35397) * fix: incorrect TCS amount while customer has advance payment * test: only unallocated advance should for threshold breach validation --- .../tax_withholding_category.py | 66 +++++++++++-------- .../test_tax_withholding_category.py | 54 +++++++++++++++ 2 files changed, 93 insertions(+), 27 deletions(-) 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 1f2d980373..d8c037089d 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -3,8 +3,10 @@ import frappe -from frappe import _ +from frappe import _, qb from frappe.model.document import Document +from frappe.query_builder import Criterion +from frappe.query_builder.functions import Abs, Sum from frappe.utils import cint, getdate @@ -346,26 +348,33 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): def get_advance_vouchers( parties, company=None, from_date=None, to_date=None, party_type="Supplier" ): - # for advance vouchers, debit and credit is reversed - dr_or_cr = "debit" if party_type == "Supplier" else "credit" + """ + Use Payment Ledger to fetch unallocated Advance Payments + """ - filters = { - dr_or_cr: [">", 0], - "is_opening": "No", - "is_cancelled": 0, - "party_type": party_type, - "party": ["in", parties], - } + ple = qb.DocType("Payment Ledger Entry") - if party_type == "Customer": - filters.update({"against_voucher": ["is", "not set"]}) + conditions = [] + + conditions.append(ple.amount.lt(0)) + conditions.append(ple.delinked == 0) + conditions.append(ple.party_type == party_type) + conditions.append(ple.party.isin(parties)) + conditions.append(ple.voucher_no == ple.against_voucher_no) if company: - filters["company"] = company - if from_date and to_date: - filters["posting_date"] = ["between", (from_date, to_date)] + conditions.append(ple.company == company) - return frappe.get_all("GL Entry", filters=filters, distinct=1, pluck="voucher_no") or [""] + if from_date and to_date: + conditions.append(ple.posting_date[from_date:to_date]) + + advances = ( + qb.from_(ple).select(ple.voucher_no).distinct().where(Criterion.all(conditions)).run(as_list=1) + ) + if advances: + advances = [x[0] for x in advances] + + return advances def get_taxes_deducted_on_advances_allocated(inv, tax_details): @@ -499,6 +508,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): tcs_amount = 0 + ple = qb.DocType("Payment Ledger Entry") # sum of debit entries made from sales invoices invoiced_amt = ( @@ -516,18 +526,20 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): ) # sum of credit entries made from PE / JV with unset 'against voucher' + + conditions = [] + conditions.append(ple.amount.lt(0)) + conditions.append(ple.delinked == 0) + conditions.append(ple.party.isin(parties)) + conditions.append(ple.voucher_no == ple.against_voucher_no) + conditions.append(ple.company == inv.company) + + advances = ( + qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1) + ) + advance_amt = ( - frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "party": ["in", parties], - "company": inv.company, - "voucher_no": ["in", adv_vouchers], - }, - "sum(credit)", - ) - or 0.0 + qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0 ) # sum of credit entries made from sales invoice diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index bc4f6709fc..4580b13613 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -152,6 +152,60 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in reversed(invoices): d.cancel() + def test_tcs_on_unallocated_advance_payments(self): + frappe.db.set_value( + "Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS" + ) + + vouchers = [] + + # create advance payment + pe = create_payment_entry( + payment_type="Receive", party_type="Customer", party="Test TCS Customer", paid_amount=20000 + ) + pe.paid_from = "Debtors - _TC" + pe.paid_to = "Cash - _TC" + pe.submit() + vouchers.append(pe) + + # create invoice + si1 = create_sales_invoice(customer="Test TCS Customer", rate=5000) + si1.submit() + vouchers.append(si1) + + # reconcile + pr = frappe.get_doc("Payment Reconciliation") + pr.company = "_Test Company" + pr.party_type = "Customer" + pr.party = "Test TCS Customer" + pr.receivable_payable_account = "Debtors - _TC" + pr.get_unreconciled_entries() + invoices = [x.as_dict() for x in pr.get("invoices")] + payments = [x.as_dict() for x in pr.get("payments")] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # make another invoice + # sum of unallocated amount from payment entry and this sales invoice will breach cumulative threashold + # TDS should be calculated + si2 = create_sales_invoice(customer="Test TCS Customer", rate=15000) + si2.submit() + vouchers.append(si2) + + si3 = create_sales_invoice(customer="Test TCS Customer", rate=10000) + si3.submit() + vouchers.append(si3) + + # assert tax collection on total invoice amount created until now + tcs_charged = sum([d.base_tax_amount for d in si2.taxes if d.account_head == "TCS - _TC"]) + tcs_charged += sum([d.base_tax_amount for d in si3.taxes if d.account_head == "TCS - _TC"]) + self.assertEqual(tcs_charged, 1500) + + # cancel invoice and payments to avoid clashing + for d in reversed(vouchers): + d.reload() + d.cancel() + def test_tds_calculation_on_net_total(self): frappe.db.set_value( "Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS" From c1b42b858d6d938e83e33bdda1e6e31346051c67 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 11 Jun 2023 09:04:41 -0500 Subject: [PATCH 28/63] fix: set Phone and Email option in doctypes (#35549) --- erpnext/accounts/doctype/dunning/dunning.json | 5 +- .../doctype/pos_invoice/pos_invoice.json | 4 +- .../purchase_invoice/purchase_invoice.json | 6 +- .../doctype/sales_invoice/sales_invoice.json | 3 +- .../purchase_order/purchase_order.json | 4 +- .../supplier_quotation.json | 3 +- .../maintenance_schedule.json | 6 +- .../maintenance_visit/maintenance_visit.json | 5 +- .../installation_note/installation_note.json | 1013 ++++------------ .../selling/doctype/quotation/quotation.json | 3 +- .../doctype/sales_order/sales_order.json | 4 +- .../doctype/delivery_note/delivery_note.json | 5 +- .../purchase_receipt/purchase_receipt.json | 3 +- .../subcontracting_order.json | 1019 +++++++++-------- .../subcontracting_receipt.json | 3 +- .../warranty_claim/warranty_claim.json | 10 +- 16 files changed, 790 insertions(+), 1306 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json index d55bfd1ac4..2a32b99f42 100644 --- a/erpnext/accounts/doctype/dunning/dunning.json +++ b/erpnext/accounts/doctype/dunning/dunning.json @@ -245,6 +245,7 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -315,10 +316,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-08-03 18:55:43.683053", + "modified": "2023-06-03 16:24:01.677026", "modified_by": "Administrator", "module": "Accounts", "name": "Dunning", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -365,6 +367,7 @@ ], "sort_field": "modified", "sort_order": "ASC", + "states": [], "title_field": "customer_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index eedaaaf338..f6047079ff 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -442,6 +442,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -1554,11 +1555,10 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-09-30 03:49:50.455199", + "modified": "2023-06-03 16:23:41.083409", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", - "name_case": "Title Case", "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 60f9d62bf2..0c18f5edb5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -443,12 +443,14 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", + "options": "Email", "print_hide": 1, "read_only": 1 }, @@ -1364,12 +1366,12 @@ "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set From Warehouse", "no_copy": 1, "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "ignore_user_permissions": 1, "width": "50px" }, { @@ -1573,7 +1575,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-04-29 12:57:50.832598", + "modified": "2023-06-03 16:21:54.637245", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 6a65b30ceb..7b68dd41d9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -520,6 +520,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -2154,7 +2155,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2023-04-28 14:15:59.901154", + "modified": "2023-06-03 16:22:16.219333", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 645abf25a8..b242108a9a 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -322,6 +322,7 @@ "fieldtype": "Small Text", "hidden": 1, "label": "Customer Mobile No", + "options": "Phone", "print_hide": 1 }, { @@ -368,6 +369,7 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Contact Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -1271,7 +1273,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-05-24 11:16:41.195340", + "modified": "2023-06-03 16:19:45.710444", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 11ff91af94..7b635b36ba 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -230,6 +230,7 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -844,7 +845,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-04-14 16:43:41.714832", + "modified": "2023-06-03 16:20:15.880114", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 4f89a679c8..08026d031f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -152,6 +152,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -160,6 +161,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Contact Email", + "options": "Email", "print_hide": 1, "read_only": 1 }, @@ -236,10 +238,11 @@ "link_fieldname": "maintenance_schedule" } ], - "modified": "2021-05-27 16:05:10.746465", + "modified": "2023-06-03 16:15:43.958072", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -260,5 +263,6 @@ "search_fields": "status,customer,customer_name", "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "customer" } \ No newline at end of file diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index 4a6aa0a34b..b0d5cb8996 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -101,6 +101,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -108,6 +109,7 @@ "fieldtype": "Data", "hidden": 1, "label": "Contact Email", + "options": "Email", "read_only": 1 }, { @@ -293,7 +295,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-12-17 03:10:27.608112", + "modified": "2023-06-03 16:19:07.902723", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", @@ -319,6 +321,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "customer", "title_field": "customer_name" } \ No newline at end of file diff --git a/erpnext/selling/doctype/installation_note/installation_note.json b/erpnext/selling/doctype/installation_note/installation_note.json index 765bc5c02c..18c7d08e18 100644 --- a/erpnext/selling/doctype/installation_note/installation_note.json +++ b/erpnext/selling/doctype/installation_note/installation_note.json @@ -1,812 +1,267 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-04-30 13:13:06", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "actions": [], + "autoname": "naming_series:", + "creation": "2013-04-30 13:13:06", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "installation_note", + "column_break0", + "naming_series", + "customer", + "customer_address", + "contact_person", + "customer_name", + "address_display", + "contact_display", + "contact_mobile", + "contact_email", + "territory", + "customer_group", + "column_break1", + "inst_date", + "inst_time", + "status", + "company", + "amended_from", + "remarks", + "item_details", + "items" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "installation_note", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Installation Note", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "installation_note", + "fieldtype": "Section Break", + "label": "Installation Note", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", "width": "50%" - }, + }, { - "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": "Series", - "length": 0, - "no_copy": 1, - "oldfieldname": "naming_series", - "oldfieldtype": "Select", - "options": "MAT-INS-.YYYY.-", - "permlevel": 0, - "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": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "oldfieldname": "naming_series", + "oldfieldtype": "Select", + "options": "MAT-INS-.YYYY.-", + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "customer_address", - "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": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_name", - "oldfieldtype": "Data", - "permlevel": 0, - "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 - }, + "bold": 1, + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Name", + "oldfieldname": "customer_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "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": "Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "contact_display", + "fieldtype": "Small Text", + "hidden": 1, + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_mobile", - "fieldtype": "Small Text", - "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": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Mobile No", + "options": "Phone", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_email", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "contact_email", + "fieldtype": "Data", + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "description": "", - "fieldname": "territory", - "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": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "description": "", - "fieldname": "customer_group", - "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": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "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 - }, + "depends_on": "customer", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "inst_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": "Installation Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "inst_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "inst_date", + "fieldtype": "Date", + "label": "Installation Date", + "oldfieldname": "inst_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "inst_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": "Installation Time", - "length": 0, - "no_copy": 0, - "oldfieldname": "inst_time", - "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "inst_time", + "fieldtype": "Time", + "label": "Installation Time", + "oldfieldname": "inst_time", + "oldfieldtype": "Time" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "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": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "Draft\nSubmitted\nCancelled", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "Draft\nSubmitted\nCancelled", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "company", - "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": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Select", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Select", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Installation Note", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Installation Note", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "remarks", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Remarks", - "length": 0, - "no_copy": 0, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "remarks", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Remarks", + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "Simple", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "Simple" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "fieldtype": "Table", - "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": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "installed_item_details", - "oldfieldtype": "Table", - "options": "Installation Note Item", - "permlevel": 0, - "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": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "installed_item_details", + "oldfieldtype": "Table", + "options": "Installation Note Item", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-wrench", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:28.000728", - "modified_by": "Administrator", - "module": "Selling", - "name": "Installation Note", - "owner": "Administrator", + ], + "icon": "fa fa-wrench", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2023-06-03 16:31:08.386961", + "modified_by": "Administrator", + "module": "Selling", + "name": "Installation Note", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Sales User" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "customer", - "title_field": "customer_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "timeline_field": "customer", + "title_field": "customer_name" } \ No newline at end of file diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 2ffa6a5c12..8c816cf6e4 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -291,6 +291,7 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -1072,7 +1073,7 @@ "idx": 82, "is_submittable": 1, "links": [], - "modified": "2023-04-14 16:50:44.550098", + "modified": "2023-06-03 16:21:04.980033", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index f7143d7594..f65969e993 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -398,6 +398,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -1475,6 +1476,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Phone", + "options": "Phone", "read_only": 1 }, { @@ -1643,7 +1645,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-04-22 09:55:37.008190", + "modified": "2023-06-03 16:16:23.411247", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 2adf9c310f..6ee8f205e0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -374,6 +374,7 @@ "fieldtype": "Small Text", "hidden": 1, "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -1398,7 +1399,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-04-21 11:15:23.931084", + "modified": "2023-06-03 16:13:25.011487", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", @@ -1468,4 +1469,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index dc61ec4d24..b41e971c8a 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -326,6 +326,7 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -1239,7 +1240,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-05-07 20:18:25.458185", + "modified": "2023-06-03 16:23:20.781368", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json index f98f559d5c..28c52c9272 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json @@ -1,511 +1,512 @@ { - "actions": [], - "allow_auto_repeat": 1, - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2022-04-01 22:39:17.662819", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "title", - "naming_series", - "purchase_order", - "supplier", - "supplier_name", - "supplier_warehouse", - "column_break_7", - "company", - "transaction_date", - "schedule_date", - "amended_from", - "accounting_dimensions_section", - "cost_center", - "dimension_col_break", - "project", - "address_and_contact_section", - "supplier_address", - "address_display", - "contact_person", - "contact_display", - "contact_mobile", - "contact_email", - "column_break_19", - "shipping_address", - "shipping_address_display", - "billing_address", - "billing_address_display", - "section_break_24", - "column_break_25", - "set_warehouse", - "items", - "section_break_32", - "total_qty", - "column_break_29", - "total", - "service_items_section", - "service_items", - "raw_materials_supplied_section", - "set_reserve_warehouse", - "supplied_items", - "additional_costs_section", - "distribute_additional_costs_based_on", - "additional_costs", - "total_additional_costs", - "order_status_section", - "status", - "column_break_39", - "per_received", - "printing_settings_section", - "select_print_heading", - "column_break_43", - "letter_head" - ], - "fields": [ - { - "allow_on_submit": 1, - "default": "{supplier_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "SC-ORD-.YYYY.-", - "print_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "purchase_order", - "fieldtype": "Link", - "label": "Subcontracting Purchase Order", - "options": "Purchase Order", - "reqd": 1 - }, - { - "bold": 1, - "fieldname": "supplier", - "fieldtype": "Link", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Supplier", - "options": "Supplier", - "print_hide": 1, - "reqd": 1, - "search_index": 1 - }, - { - "bold": 1, - "fetch_from": "supplier.supplier_name", - "fieldname": "supplier_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Supplier Name", - "read_only": 1, - "reqd": 1 - }, - { - "depends_on": "supplier", - "fieldname": "supplier_warehouse", - "fieldtype": "Link", - "label": "Supplier Warehouse", - "options": "Warehouse", - "reqd": 1 - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "print_width": "50%", - "width": "50%" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "options": "Company", - "print_hide": 1, - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "default": "Today", - "fetch_from": "purchase_order.transaction_date", - "fetch_if_empty": 1, - "fieldname": "transaction_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Date", - "reqd": 1, - "search_index": 1 - }, - { - "allow_on_submit": 1, - "fetch_from": "purchase_order.schedule_date", - "fetch_if_empty": 1, - "fieldname": "schedule_date", - "fieldtype": "Date", - "label": "Required By", - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "options": "Subcontracting Order", - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "address_and_contact_section", - "fieldtype": "Section Break", - "label": "Address and Contact" - }, - { - "fetch_from": "supplier.supplier_primary_address", - "fetch_if_empty": 1, - "fieldname": "supplier_address", - "fieldtype": "Link", - "label": "Supplier Address", - "options": "Address", - "print_hide": 1 - }, - { - "fieldname": "address_display", - "fieldtype": "Small Text", - "label": "Supplier Address Details", - "read_only": 1 - }, - { - "fetch_from": "supplier.supplier_primary_contact", - "fetch_if_empty": 1, - "fieldname": "contact_person", - "fieldtype": "Link", - "label": "Supplier Contact", - "options": "Contact", - "print_hide": 1 - }, - { - "fieldname": "contact_display", - "fieldtype": "Small Text", - "in_global_search": 1, - "label": "Contact Name", - "read_only": 1 - }, - { - "fieldname": "contact_mobile", - "fieldtype": "Small Text", - "label": "Contact Mobile No", - "read_only": 1 - }, - { - "fieldname": "contact_email", - "fieldtype": "Small Text", - "label": "Contact Email", - "options": "Email", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_19", - "fieldtype": "Column Break" - }, - { - "fieldname": "shipping_address", - "fieldtype": "Link", - "label": "Company Shipping Address", - "options": "Address", - "print_hide": 1 - }, - { - "fieldname": "shipping_address_display", - "fieldtype": "Small Text", - "label": "Shipping Address Details", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "billing_address", - "fieldtype": "Link", - "label": "Company Billing Address", - "options": "Address" - }, - { - "fieldname": "billing_address_display", - "fieldtype": "Small Text", - "label": "Billing Address Details", - "read_only": 1 - }, - { - "fieldname": "section_break_24", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_25", - "fieldtype": "Column Break" - }, - { - "depends_on": "purchase_order", - "description": "Sets 'Warehouse' in each row of the Items table.", - "fieldname": "set_warehouse", - "fieldtype": "Link", - "label": "Set Target Warehouse", - "options": "Warehouse", - "print_hide": 1 - }, - { - "allow_bulk_edit": 1, - "depends_on": "purchase_order", - "fieldname": "items", - "fieldtype": "Table", - "label": "Items", - "options": "Subcontracting Order Item", - "reqd": 1 - }, - { - "fieldname": "section_break_32", - "fieldtype": "Section Break" - }, - { - "depends_on": "purchase_order", - "fieldname": "total_qty", - "fieldtype": "Float", - "label": "Total Quantity", - "read_only": 1 - }, - { - "fieldname": "column_break_29", - "fieldtype": "Column Break" - }, - { - "depends_on": "purchase_order", - "fieldname": "total", - "fieldtype": "Currency", - "label": "Total", - "options": "currency", - "read_only": 1 - }, - { - "collapsible": 1, - "depends_on": "purchase_order", - "fieldname": "service_items_section", - "fieldtype": "Section Break", - "label": "Service Items" - }, - { - "fieldname": "service_items", - "fieldtype": "Table", - "label": "Service Items", - "options": "Subcontracting Order Service Item", - "read_only": 1, - "reqd": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "supplied_items", - "depends_on": "supplied_items", - "fieldname": "raw_materials_supplied_section", - "fieldtype": "Section Break", - "label": "Raw Materials Supplied" - }, - { - "depends_on": "supplied_items", - "description": "Sets 'Reserve Warehouse' in each row of the Supplied Items table.", - "fieldname": "set_reserve_warehouse", - "fieldtype": "Link", - "label": "Set Reserve Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "supplied_items", - "fieldtype": "Table", - "label": "Supplied Items", - "no_copy": 1, - "options": "Subcontracting Order Supplied Item", - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "total_additional_costs", - "depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)", - "fieldname": "additional_costs_section", - "fieldtype": "Section Break", - "label": "Additional Costs" - }, - { - "fieldname": "additional_costs", - "fieldtype": "Table", - "label": "Additional Costs", - "options": "Landed Cost Taxes and Charges" - }, - { - "fieldname": "total_additional_costs", - "fieldtype": "Currency", - "label": "Total Additional Costs", - "print_hide_if_no_value": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "order_status_section", - "fieldtype": "Section Break", - "label": "Order Status" - }, - { - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "in_standard_filter": 1, - "label": "Status", - "no_copy": 1, - "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled", - "print_hide": 1, - "read_only": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "column_break_39", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "per_received", - "fieldtype": "Percent", - "in_list_view": 1, - "label": "% Received", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "printing_settings_section", - "fieldtype": "Section Break", - "label": "Printing Settings", - "print_hide": 1, - "print_width": "50%", - "width": "50%" - }, - { - "allow_on_submit": 1, - "fieldname": "select_print_heading", - "fieldtype": "Link", - "label": "Print Heading", - "no_copy": 1, - "options": "Print Heading", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "column_break_43", - "fieldtype": "Column Break" - }, - { - "allow_on_submit": 1, - "fieldname": "letter_head", - "fieldtype": "Link", - "label": "Letter Head", - "options": "Letter Head", - "print_hide": 1 - }, - { - "default": "Qty", - "fieldname": "distribute_additional_costs_based_on", - "fieldtype": "Select", - "label": "Distribute Additional Costs Based On ", - "options": "Qty\nAmount" - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" - } - ], - "icon": "fa fa-file-text", - "is_submittable": 1, - "links": [], - "modified": "2022-08-15 14:08:49.204218", - "modified_by": "Administrator", - "module": "Subcontracting", - "name": "Subcontracting Order", - "naming_rule": "By \"Naming Series\" field", - "owner": "Administrator", - "permissions": [ - { - "read": 1, - "report": 1, - "role": "Stock User" - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Manager", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase User", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "role": "Purchase Manager", - "write": 1 - } - ], - "search_fields": "status, transaction_date, supplier", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "timeline_field": "supplier", - "title_field": "supplier_name", - "track_changes": 1 + "actions": [], + "allow_auto_repeat": 1, + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2022-04-01 22:39:17.662819", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "title", + "naming_series", + "purchase_order", + "supplier", + "supplier_name", + "supplier_warehouse", + "column_break_7", + "company", + "transaction_date", + "schedule_date", + "amended_from", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", + "address_and_contact_section", + "supplier_address", + "address_display", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "column_break_19", + "shipping_address", + "shipping_address_display", + "billing_address", + "billing_address_display", + "section_break_24", + "column_break_25", + "set_warehouse", + "items", + "section_break_32", + "total_qty", + "column_break_29", + "total", + "service_items_section", + "service_items", + "raw_materials_supplied_section", + "set_reserve_warehouse", + "supplied_items", + "additional_costs_section", + "distribute_additional_costs_based_on", + "additional_costs", + "total_additional_costs", + "order_status_section", + "status", + "column_break_39", + "per_received", + "printing_settings_section", + "select_print_heading", + "column_break_43", + "letter_head" + ], + "fields": [ + { + "allow_on_submit": 1, + "default": "{supplier_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "SC-ORD-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Subcontracting Purchase Order", + "options": "Purchase Order", + "reqd": 1 + }, + { + "bold": 1, + "fieldname": "supplier", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Supplier", + "options": "Supplier", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, + { + "bold": 1, + "fetch_from": "supplier.supplier_name", + "fieldname": "supplier_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Supplier Name", + "read_only": 1, + "reqd": 1 + }, + { + "depends_on": "supplier", + "fieldname": "supplier_warehouse", + "fieldtype": "Link", + "label": "Supplier Warehouse", + "options": "Warehouse", + "reqd": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "default": "Today", + "fetch_from": "purchase_order.transaction_date", + "fetch_if_empty": 1, + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1, + "search_index": 1 + }, + { + "allow_on_submit": 1, + "fetch_from": "purchase_order.schedule_date", + "fetch_if_empty": 1, + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Required By", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Subcontracting Order", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "address_and_contact_section", + "fieldtype": "Section Break", + "label": "Address and Contact" + }, + { + "fetch_from": "supplier.supplier_primary_address", + "fetch_if_empty": 1, + "fieldname": "supplier_address", + "fieldtype": "Link", + "label": "Supplier Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Supplier Address Details", + "read_only": 1 + }, + { + "fetch_from": "supplier.supplier_primary_contact", + "fetch_if_empty": 1, + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Supplier Contact", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact Name", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Contact Mobile No", + "options": "Phone", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Small Text", + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_address", + "fieldtype": "Link", + "label": "Company Shipping Address", + "options": "Address", + "print_hide": 1 + }, + { + "fieldname": "shipping_address_display", + "fieldtype": "Small Text", + "label": "Shipping Address Details", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "billing_address", + "fieldtype": "Link", + "label": "Company Billing Address", + "options": "Address" + }, + { + "fieldname": "billing_address_display", + "fieldtype": "Small Text", + "label": "Billing Address Details", + "read_only": 1 + }, + { + "fieldname": "section_break_24", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "depends_on": "purchase_order", + "description": "Sets 'Warehouse' in each row of the Items table.", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Set Target Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, + { + "allow_bulk_edit": 1, + "depends_on": "purchase_order", + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Subcontracting Order Item", + "reqd": 1 + }, + { + "fieldname": "section_break_32", + "fieldtype": "Section Break" + }, + { + "depends_on": "purchase_order", + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "depends_on": "purchase_order", + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "purchase_order", + "fieldname": "service_items_section", + "fieldtype": "Section Break", + "label": "Service Items" + }, + { + "fieldname": "service_items", + "fieldtype": "Table", + "label": "Service Items", + "options": "Subcontracting Order Service Item", + "read_only": 1, + "reqd": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "supplied_items", + "depends_on": "supplied_items", + "fieldname": "raw_materials_supplied_section", + "fieldtype": "Section Break", + "label": "Raw Materials Supplied" + }, + { + "depends_on": "supplied_items", + "description": "Sets 'Reserve Warehouse' in each row of the Supplied Items table.", + "fieldname": "set_reserve_warehouse", + "fieldtype": "Link", + "label": "Set Reserve Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "supplied_items", + "fieldtype": "Table", + "label": "Supplied Items", + "no_copy": 1, + "options": "Subcontracting Order Supplied Item", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "total_additional_costs", + "depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)", + "fieldname": "additional_costs_section", + "fieldtype": "Section Break", + "label": "Additional Costs" + }, + { + "fieldname": "additional_costs", + "fieldtype": "Table", + "label": "Additional Costs", + "options": "Landed Cost Taxes and Charges" + }, + { + "fieldname": "total_additional_costs", + "fieldtype": "Currency", + "label": "Total Additional Costs", + "print_hide_if_no_value": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "order_status_section", + "fieldtype": "Section Break", + "label": "Order Status" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Draft\nOpen\nPartially Received\nCompleted\nMaterial Transferred\nPartial Material Transferred\nCancelled", + "print_hide": 1, + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "per_received", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Received", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "printing_settings_section", + "fieldtype": "Section Break", + "label": "Printing Settings", + "print_hide": 1, + "print_width": "50%", + "width": "50%" + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_43", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "default": "Qty", + "fieldname": "distribute_additional_costs_based_on", + "fieldtype": "Select", + "label": "Distribute Additional Costs Based On ", + "options": "Qty\nAmount" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + } + ], + "icon": "fa fa-file-text", + "is_submittable": 1, + "links": [], + "modified": "2023-06-03 16:18:17.782538", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "read": 1, + "report": 1, + "role": "Stock User" + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "Purchase Manager", + "write": 1 + } + ], + "search_fields": "status, transaction_date, supplier", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "timeline_field": "supplier", + "title_field": "supplier_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 3385eac052..9dee3aae46 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -205,6 +205,7 @@ "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -629,7 +630,7 @@ "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-16 14:18:57.001239", + "modified": "2023-06-03 16:18:39.088518", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.json b/erpnext/support/doctype/warranty_claim/warranty_claim.json index 45485ca2c2..01d9b01390 100644 --- a/erpnext/support/doctype/warranty_claim/warranty_claim.json +++ b/erpnext/support/doctype/warranty_claim/warranty_claim.json @@ -1,9 +1,11 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-01-10 16:34:30", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "naming_series", "status", @@ -249,6 +251,7 @@ "fieldname": "contact_mobile", "fieldtype": "Data", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -362,10 +365,12 @@ ], "icon": "fa fa-bug", "idx": 1, - "modified": "2021-11-09 17:26:09.703215", + "links": [], + "modified": "2023-06-03 16:17:07.694449", "modified_by": "Administrator", "module": "Support", "name": "Warranty Claim", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -384,6 +389,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "customer", "title_field": "customer_name" -} +} \ No newline at end of file From 9a12545ac3fcf50d7dabf86fcf156433802708d3 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 10 Jun 2023 12:33:06 +0530 Subject: [PATCH 29/63] fix(ux): add filter `disabled=0` for batch no --- .../doctype/serial_and_batch_bundle/serial_and_batch_bundle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index d50bdba3c9..2e23bc3887 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -194,6 +194,7 @@ frappe.ui.form.on('Serial and Batch Bundle', { return { filters: { item: frm.doc.item_code, + disabled: 0, } }; }); From 0b009da1225585d235243e7b1015a760f32d04c0 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 11 Jun 2023 20:14:59 +0530 Subject: [PATCH 30/63] fix(ux): only list related DocTypes --- .../serial_and_batch_bundle.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js index 2e23bc3887..cda444510a 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js @@ -161,6 +161,23 @@ frappe.ui.form.on('Serial and Batch Bundle', { 'istable': 0, 'issingle': 0, 'is_submittable': 1, + 'name': ['in', [ + "Asset Capitalization", + "Asset Repair", + "Delivery Note", + "Installation Note", + "Job Card", + "Maintenance Schedule", + "POS Invoice", + "Pick List", + "Purchase Invoice", + "Purchase Receipt", + "Quotation", + "Sales Invoice", + "Stock Entry", + "Stock Reconciliation", + "Subcontracting Receipt", + ]], } }; }); From c6acb0d2006455a8181e1e1844a2b172288cea4d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 11 Jun 2023 22:04:08 +0530 Subject: [PATCH 31/63] fix: DocType not found --- .../serial_and_batch_bundle.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index cc55bd6ea8..7aca791fe1 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -563,9 +563,16 @@ class SerialandBatchBundle(Document): @property def child_table(self): + parent_child_map = { + "Asset Capitalization": "Asset Capitalization Stock Item", + "Asset Repair": "Asset Repair Consumed Item", + "Quotation": "Packed Item", + "Stock Entry": "Stock Entry Detail", + } + table = f"{self.voucher_type} Item" - if self.voucher_type == "Stock Entry": - table = f"{self.voucher_type} Detail" + if self.voucher_type in parent_child_map: + table = parent_child_map[self.voucher_type] return table From c9923e49969f7aa4e05dda8e2c403f5542644762 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 11 Jun 2023 22:08:46 +0530 Subject: [PATCH 32/63] fix: 'NoneType' object has no attribute 'precision' --- .../serial_and_batch_bundle.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 7aca791fe1..9a4206a03b 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -122,7 +122,11 @@ class SerialandBatchBundle(Document): frappe.throw(_(message), exception, title=_("Error")) def set_incoming_rate(self, row=None, save=False): - if self.type_of_transaction not in ["Inward", "Outward"]: + if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [ + "Installation Note", + "Maintenance Schedule", + "Pick List", + ]: return if self.type_of_transaction == "Outward": @@ -220,7 +224,7 @@ class SerialandBatchBundle(Document): def set_incoming_rate_for_inward_transaction(self, row=None, save=False): valuation_field = "valuation_rate" - if self.voucher_type in ["Sales Invoice", "Delivery Note"]: + if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]: valuation_field = "incoming_rate" if self.voucher_type == "POS Invoice": @@ -229,8 +233,10 @@ class SerialandBatchBundle(Document): rate = row.get(valuation_field) if row else 0.0 child_table = self.child_table - if self.voucher_type == "Subcontracting Receipt" and self.voucher_detail_no: - if frappe.db.exists("Subcontracting Receipt Supplied Item", self.voucher_detail_no): + if self.voucher_type == "Subcontracting Receipt": + if not self.voucher_detail_no: + return + elif frappe.db.exists("Subcontracting Receipt Supplied Item", self.voucher_detail_no): valuation_field = "rate" child_table = "Subcontracting Receipt Supplied Item" else: From 2f24546b21cea8834ceb92d7cc72314fa2750124 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jun 2023 15:20:28 +0530 Subject: [PATCH 33/63] fix: Make difference entry button not working (#35622) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 34a753f267..74fd559612 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -952,6 +952,7 @@ class JournalEntry(AccountsController): blank_row.debit_in_account_currency = abs(diff) blank_row.debit = abs(diff) + self.set_total_debit_credit() self.validate_total_debit_and_credit() @frappe.whitelist() From 42f4f80e0cc4fc6a52f4bce99234b8f1b8ddc395 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 12 Jun 2023 17:35:13 +0530 Subject: [PATCH 34/63] fix: Payment against credit notes will be considered as payment against parent invoice in Accounts Receivable/Payable report (#35642) * fix: payment against credit note should be linked to parent invoice * test: AR/AP report for payment against cr note scenario * fix: cr_note shows up as outstanding invoice Payment made against cr_note causes it be reported as outstanding invoice --- .../payment_reconciliation.py | 40 +++++++----- .../accounts_receivable.py | 12 +++- .../test_accounts_receivable.py | 65 ++++++++++++++++++- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cc2b9420cc..081fe70354 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -6,7 +6,6 @@ import frappe from frappe import _, msgprint, qb from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn -from frappe.query_builder.functions import IfNull from frappe.utils import flt, get_link_to_form, getdate, nowdate, today import erpnext @@ -127,12 +126,29 @@ class PaymentReconciliation(Document): return list(journal_entries) + def get_return_invoices(self): + voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" + doc = qb.DocType(voucher_type) + self.return_invoices = ( + qb.from_(doc) + .select( + ConstantColumn(voucher_type).as_("voucher_type"), + doc.name.as_("voucher_no"), + doc.return_against, + ) + .where( + (doc.docstatus == 1) + & (doc[frappe.scrub(self.party_type)] == self.party) + & (doc.is_return == 1) + ) + .run(as_dict=True) + ) + def get_dr_or_cr_notes(self): self.build_qb_filter_conditions(get_return_invoices=True) ple = qb.DocType("Payment Ledger Entry") - voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" if erpnext.get_party_account_type(self.party_type) == "Receivable": self.common_filter_conditions.append(ple.account_type == "Receivable") @@ -140,19 +156,10 @@ class PaymentReconciliation(Document): self.common_filter_conditions.append(ple.account_type == "Payable") self.common_filter_conditions.append(ple.account == self.receivable_payable_account) - # get return invoices - doc = qb.DocType(voucher_type) - return_invoices = ( - qb.from_(doc) - .select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no")) - .where( - (doc.docstatus == 1) - & (doc[frappe.scrub(self.party_type)] == self.party) - & (doc.is_return == 1) - & (IfNull(doc.return_against, "") == "") - ) - .run(as_dict=True) - ) + self.get_return_invoices() + return_invoices = [ + x for x in self.return_invoices if x.return_against == None or x.return_against == "" + ] outstanding_dr_or_cr = [] if return_invoices: @@ -204,6 +211,9 @@ class PaymentReconciliation(Document): accounting_dimensions=self.accounting_dimension_filter_conditions, ) + cr_dr_notes = [x.voucher_no for x in self.return_invoices] + non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes] + if self.invoice_limit: non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit] diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 11de9a098d..30f7fb38c5 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -181,6 +181,16 @@ class ReceivablePayableReport(object): return key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) + + # If payment is made against credit note + # and credit note is made against a Sales Invoice + # then consider the payment against original sales invoice. + if ple.against_voucher_type in ("Sales Invoice", "Purchase Invoice"): + if ple.against_voucher_no in self.return_entries: + return_against = self.return_entries.get(ple.against_voucher_no) + if return_against: + key = (ple.against_voucher_type, return_against, ple.party) + row = self.voucher_balance.get(key) if not row: @@ -610,7 +620,7 @@ class ReceivablePayableReport(object): def get_return_entries(self): doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" - filters = {"is_return": 1, "docstatus": 1} + filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company} party_field = scrub(self.filters.party_type) if self.filters.get(party_field): filters.update({party_field: self.filters.get(party_field)}) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index afd02a006e..6f1889b34e 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -210,6 +210,67 @@ class TestAccountsReceivable(FrappeTestCase): ], ) + def test_payment_against_credit_note(self): + """ + Payment against credit/debit note should be considered against the parent invoice + """ + company = "_Test Company 2" + customer = "_Test Customer 2" + + si1 = make_sales_invoice() + + pe = get_payment_entry("Sales Invoice", si1.name, bank_account="Cash - _TC2") + pe.paid_from = "Debtors - _TC2" + pe.insert() + pe.submit() + + cr_note = make_credit_note(si1.name) + + si2 = make_sales_invoice() + + # manually link cr_note with si2 using journal entry + je = frappe.new_doc("Journal Entry") + je.company = company + je.voucher_type = "Credit Note" + je.posting_date = today() + + debit_account = "Debtors - _TC2" + debit_entry = { + "account": debit_account, + "party_type": "Customer", + "party": customer, + "debit": 100, + "debit_in_account_currency": 100, + "reference_type": cr_note.doctype, + "reference_name": cr_note.name, + "cost_center": "Main - _TC2", + } + credit_entry = { + "account": debit_account, + "party_type": "Customer", + "party": customer, + "credit": 100, + "credit_in_account_currency": 100, + "reference_type": si2.doctype, + "reference_name": si2.name, + "cost_center": "Main - _TC2", + } + + je.append("accounts", debit_entry) + je.append("accounts", credit_entry) + je = je.save().submit() + + filters = { + "company": company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + report = execute(filters) + self.assertEqual(report[1], []) + def make_sales_invoice(no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") @@ -256,7 +317,7 @@ def make_payment(docname): def make_credit_note(docname): - create_sales_invoice( + credit_note = create_sales_invoice( company="_Test Company 2", customer="_Test Customer 2", currency="EUR", @@ -269,3 +330,5 @@ def make_credit_note(docname): is_return=1, return_against=docname, ) + + return credit_note From 0382eecff4a8005e6d013a8daf3fee1ffdeaf408 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 10 Jun 2023 13:27:38 +0530 Subject: [PATCH 35/63] fix: test case --- .../doctype/job_card/test_job_card.py | 114 ++++++++++++++++++ .../doctype/routing/test_routing.py | 1 + .../doctype/work_order/test_work_order.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 6 +- 4 files changed, 120 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index a7f06486ab..e7fbcda7ab 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -5,6 +5,7 @@ from typing import Literal import frappe +from frappe.test_runner import make_test_records from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import random_string from frappe.utils.data import add_to_date, now, today @@ -469,6 +470,119 @@ class TestJobCard(FrappeTestCase): self.assertEqual(ste.from_bom, 1.0) self.assertEqual(ste.bom_no, work_order.bom_no) + def test_job_card_proccess_qty_and_completed_qty(self): + from erpnext.manufacturing.doctype.routing.test_routing import ( + create_routing, + setup_bom, + setup_operations, + ) + from erpnext.manufacturing.doctype.work_order.work_order import ( + make_stock_entry as make_stock_entry_for_wo, + ) + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + operations = [ + {"operation": "Test Operation A1", "workstation": "Test Workstation A", "time_in_mins": 30}, + {"operation": "Test Operation B1", "workstation": "Test Workstation A", "time_in_mins": 20}, + ] + + make_test_records("UOM") + + warehouse = create_warehouse("Test Warehouse 123 for Job Card") + + setup_operations(operations) + + item_code = "Test Job Card Process Qty Item" + for item in [item_code, item_code + "RM 1", item_code + "RM 2"]: + if not frappe.db.exists("Item", item): + make_item( + item, + { + "item_name": item, + "stock_uom": "Nos", + "is_stock_item": 1, + }, + ) + + routing_doc = create_routing(routing_name="Testing Route", operations=operations) + bom_doc = setup_bom( + item_code=item_code, + routing=routing_doc.name, + raw_materials=[item_code + "RM 1", item_code + "RM 2"], + source_warehouse=warehouse, + ) + + for row in bom_doc.items: + make_stock_entry( + item_code=row.item_code, + target=row.source_warehouse, + qty=10, + basic_rate=100, + ) + + wo_doc = make_wo_order_test_record( + production_item=item_code, + bom_no=bom_doc.name, + skip_transfer=1, + wip_warehouse=warehouse, + source_warehouse=warehouse, + ) + + for row in routing_doc.operations: + self.assertEqual(row.sequence_id, row.idx) + + first_job_card = frappe.get_all( + "Job Card", + filters={"work_order": wo_doc.name, "sequence_id": 1}, + fields=["name"], + order_by="sequence_id", + limit=1, + )[0].name + + jc = frappe.get_doc("Job Card", first_job_card) + jc.time_logs[0].completed_qty = 8 + jc.save() + jc.submit() + + self.assertEqual(jc.process_loss_qty, 2) + self.assertEqual(jc.for_quantity, 10) + + second_job_card = frappe.get_all( + "Job Card", + filters={"work_order": wo_doc.name, "sequence_id": 2}, + fields=["name"], + order_by="sequence_id", + limit=1, + )[0].name + + jc2 = frappe.get_doc("Job Card", second_job_card) + jc2.time_logs[0].completed_qty = 10 + + self.assertRaises(frappe.ValidationError, jc2.save) + + jc2.load_from_db() + jc2.time_logs[0].completed_qty = 8 + jc2.save() + jc2.submit() + + self.assertEqual(jc2.for_quantity, 10) + self.assertEqual(jc2.process_loss_qty, 2) + + s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 10)) + s.submit() + + self.assertEqual(s.process_loss_qty, 2) + + wo_doc.reload() + for row in wo_doc.operations: + self.assertEqual(row.completed_qty, 8) + self.assertEqual(row.process_loss_qty, 2) + + self.assertEqual(wo_doc.produced_qty, 8) + self.assertEqual(wo_doc.process_loss_qty, 2) + self.assertEqual(wo_doc.status, "Completed") + def create_bom_with_multiple_operations(): "Create a BOM with multiple operations and Material Transfer against Job Card" diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 48f1851cb1..a37ff28031 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -141,6 +141,7 @@ def setup_bom(**args): routing=args.routing, with_operations=1, currency=args.currency, + source_warehouse=args.source_warehouse, ) else: bom_doc = frappe.get_doc("BOM", name) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3c7c787df8..9f8390ca68 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -903,7 +903,7 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(se.process_loss_qty, 1) wo.load_from_db() - self.assertEqual(wo.status, "In Process") + self.assertEqual(wo.status, "Completed") @timeout(seconds=60) def test_job_card_scrap_item(self): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 816957c1c8..517fea5bd1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -581,7 +581,9 @@ class StockEntry(StockController): for d in prod_order.get("operations"): total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty) - completed_qty = d.completed_qty + (allowance_percentage / 100 * d.completed_qty) + completed_qty = ( + d.completed_qty + d.process_loss_qty + (allowance_percentage / 100 * d.completed_qty) + ) if total_completed_qty > flt(completed_qty): job_card = frappe.db.get_value("Job Card", {"operation_id": d.name}, "name") if not job_card: @@ -1669,7 +1671,7 @@ class StockEntry(StockController): self.process_loss_qty = flt( (flt(self.fg_completed_qty) * flt(self.process_loss_percentage)) / 100 ) - else: + elif self.process_loss_qty and not self.process_loss_percentage: self.process_loss_percentage = flt( (flt(self.process_loss_qty) / flt(self.fg_completed_qty)) * 100 ) From db159dd11f66862f35123041d2195fe6490a243f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 12 Jun 2023 18:28:16 +0530 Subject: [PATCH 36/63] fix: Stock Reconciliation document update while reposting --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index dc481e8281..a668ab89dd 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -944,7 +944,7 @@ class update_entries_after(object): for item in sr.items: # Skip for Serial and Batch Items - if item.serial_no or item.batch_no: + if item.name != sle.voucher_detail_no or item.serial_no or item.batch_no: continue previous_sle = get_previous_sle( From 4f3d531f35de59985b534cda50fb049ba4ba6c6a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 18:43:26 +0530 Subject: [PATCH 37/63] fix: don't set default payment amount in case of invoice return (backport #35645) (#35647) fix: don't set default payment amount in case of invoice return (#35645) (cherry picked from commit 79483cc90eb71fa82d645b2cf54d9731502cf6c7) Co-authored-by: Anand Baburajan --- erpnext/public/js/controllers/taxes_and_totals.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index fd961c4aaa..6f4e602abb 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -805,11 +805,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { ); } - this.frm.doc.payments.find(pay => { - if (pay.default) { - pay.amount = total_amount_to_pay; - } - }); + if(!this.frm.doc.is_return){ + this.frm.doc.payments.find(payment => { + if (payment.default) { + payment.amount = total_amount_to_pay; + } + }); + } this.frm.refresh_fields(); } From 1e8ee9354a0eb35653fe5a1a1bc6b392c0071f23 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Jun 2023 19:20:52 +0530 Subject: [PATCH 38/63] fix(DX): Check Frappe and ERPNext major versions (#35651) --- erpnext/__init__.py | 2 +- erpnext/hooks.py | 5 ++++- erpnext/setup/install.py | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index c9c9c9c6df..3e418c48cf 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "14.0.0-dev" +__version__ = "15.0.0-dev" def get_default_company(user=None): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 77dbc8f9b3..c821fcf4e6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -39,7 +39,10 @@ setup_wizard_requires = "assets/erpnext/js/setup_wizard.js" setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages" setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test" -before_install = "erpnext.setup.install.check_setup_wizard_not_completed" +before_install = [ + "erpnext.setup.install.check_setup_wizard_not_completed", + "erpnext.setup.install.check_frappe_version", +] after_install = "erpnext.setup.install.after_install" boot_session = "erpnext.startup.boot.boot_session" diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 1d5428a40e..74c1ee22c9 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -2,12 +2,14 @@ # License: GNU General Public License v3. See license.txt +import click import frappe from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.utils import cint +import erpnext from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules from erpnext.setup.doctype.incoterm.incoterm import create_incoterms @@ -41,6 +43,25 @@ You can reinstall this site (after saving your data) using: bench --site [sitena frappe.throw(message) # nosemgrep +def check_frappe_version(): + def major_version(v: str) -> str: + return v.split(".")[0] + + frappe_version = major_version(frappe.__version__) + erpnext_version = major_version(erpnext.__version__) + + if frappe_version == erpnext_version: + return + + click.secho( + f"You're attempting to install ERPNext version {erpnext_version} with Frappe version {frappe_version}. " + "This is not supported and will result in broken install. Switch to correct branch before installing.", + fg="red", + ) + + raise SystemExit(1) + + def set_single_defaults(): for dt in ( "Accounts Settings", From 62011410b2cd912f607b7b2b675e143510dbc484 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 12 Jun 2023 19:22:55 +0530 Subject: [PATCH 39/63] fix: test case PyPDF2 (#35652) fix: test case --- erpnext/regional/report/irs_1099/irs_1099.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py index 66ade1f89f..c5d8112894 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.py +++ b/erpnext/regional/report/irs_1099/irs_1099.py @@ -10,7 +10,7 @@ from frappe.utils.data import fmt_money from frappe.utils.jinja import render_template from frappe.utils.pdf import get_pdf from frappe.utils.print_format import read_multi_pdf -from PyPDF2 import PdfWriter +from pypdf import PdfWriter from erpnext.accounts.utils import get_fiscal_year From f957a84830f951800893c4139a17eae0ffb68f7f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Jun 2023 21:46:06 +0530 Subject: [PATCH 40/63] build!: update deps and drop `setup.py` (#35653) --- pyproject.toml | 4 ++-- setup.py | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml index 0718e5b4a1..c119ada46e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,8 +9,8 @@ readme = "README.md" dynamic = ["version"] dependencies = [ # Core dependencies - "pycountry~=20.7.3", - "Unidecode~=1.2.0", + "pycountry~=22.3.5", + "Unidecode~=1.3.6", "barcodenumber~=0.5.0", # integration dependencies diff --git a/setup.py b/setup.py deleted file mode 100644 index 29fa1c7f18..0000000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -# TODO: Remove this file when v15.0.0 is released -from setuptools import setup - -name = "erpnext" - -setup() From 96a0132501ef2c5055b310c500cd9959edfcbfa8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 9 Jun 2023 11:54:45 +0530 Subject: [PATCH 41/63] fix: allow user to set rounding loss allowance for accounts balance --- .../exchange_rate_revaluation.js | 18 +++++++++- .../exchange_rate_revaluation.json | 10 +++++- .../exchange_rate_revaluation.py | 35 ++++++++++++++++--- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index f72ecc9e50..733a7616b2 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -35,6 +35,21 @@ frappe.ui.form.on('Exchange Rate Revaluation', { } }, + validate_rounding_loss: function(frm) { + allowance = frm.doc.rounding_loss_allowance; + if (!(allowance > 0 && allowance < 1)) { + frappe.throw(__("Rounding Loss Allowance should be between 0 and 1")); + } + }, + + rounding_loss_allowance: function(frm) { + frm.events.validate_rounding_loss(frm); + }, + + validate: function(frm) { + frm.events.validate_rounding_loss(frm); + }, + get_entries: function(frm, account) { frappe.call({ method: "get_accounts_data", @@ -126,7 +141,8 @@ var get_account_details = function(frm, cdt, cdn) { company: frm.doc.company, posting_date: frm.doc.posting_date, party_type: row.party_type, - party: row.party + party: row.party, + rounding_loss_allowance: frm.doc.rounding_loss_allowance }, callback: function(r){ $.extend(row, r.message); diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json index 0d198ca120..2310d1272c 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json @@ -8,6 +8,7 @@ "engine": "InnoDB", "field_order": [ "posting_date", + "rounding_loss_allowance", "column_break_2", "company", "section_break_4", @@ -96,11 +97,18 @@ { "fieldname": "column_break_10", "fieldtype": "Column Break" + }, + { + "default": "0.05", + "description": "Only values between 0 and 1 are allowed. \nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account", + "fieldname": "rounding_loss_allowance", + "fieldtype": "Float", + "label": "Rounding Loss Allowance" } ], "is_submittable": 1, "links": [], - "modified": "2022-12-29 19:38:24.416529", + "modified": "2023-06-12 21:02:09.818208", "modified_by": "Administrator", "module": "Accounts", "name": "Exchange Rate Revaluation", diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index b528ee58e2..043fbdd5d6 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -18,8 +18,13 @@ from erpnext.setup.utils import get_exchange_rate class ExchangeRateRevaluation(Document): def validate(self): + self.validate_rounding_loss_allowance() self.set_total_gain_loss() + def validate_rounding_loss_allowance(self): + if not (self.rounding_loss_allowance > 0 and self.rounding_loss_allowance < 1): + frappe.throw(_("Rounding Loss Allowance should be between 0 and 1")) + def set_total_gain_loss(self): total_gain_loss = 0 @@ -92,7 +97,12 @@ class ExchangeRateRevaluation(Document): def get_accounts_data(self): self.validate_mandatory() account_details = self.get_account_balance_from_gle( - company=self.company, posting_date=self.posting_date, account=None, party_type=None, party=None + company=self.company, + posting_date=self.posting_date, + account=None, + party_type=None, + party=None, + rounding_loss_allowance=self.rounding_loss_allowance, ) accounts_with_new_balance = self.calculate_new_account_balance( self.company, self.posting_date, account_details @@ -104,7 +114,9 @@ class ExchangeRateRevaluation(Document): return accounts_with_new_balance @staticmethod - def get_account_balance_from_gle(company, posting_date, account, party_type, party): + def get_account_balance_from_gle( + company, posting_date, account, party_type, party, rounding_loss_allowance + ): account_details = [] if company and posting_date: @@ -172,10 +184,18 @@ class ExchangeRateRevaluation(Document): ) # round off balance based on currency precision + # and consider debit-credit difference allowance currency_precision = get_currency_precision() + rounding_loss_allowance = rounding_loss_allowance or 0.05 for acc in account_details: acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision) + if abs(acc.balance_in_account_currency) <= rounding_loss_allowance: + acc.balance_in_account_currency = 0 + acc.balance = flt(acc.balance, currency_precision) + if abs(acc.balance) <= rounding_loss_allowance: + acc.balance = 0 + acc.zero_balance = ( True if (acc.balance == 0 or acc.balance_in_account_currency == 0) else False ) @@ -531,7 +551,9 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party): @frappe.whitelist() -def get_account_details(company, posting_date, account, party_type=None, party=None): +def get_account_details( + company, posting_date, account, party_type=None, party=None, rounding_loss_allowance=None +): if not (company and posting_date): frappe.throw(_("Company and Posting Date is mandatory")) @@ -549,7 +571,12 @@ def get_account_details(company, posting_date, account, party_type=None, party=N "account_currency": account_currency, } account_balance = ExchangeRateRevaluation.get_account_balance_from_gle( - company=company, posting_date=posting_date, account=account, party_type=party_type, party=party + company=company, + posting_date=posting_date, + account=account, + party_type=party_type, + party=party, + rounding_loss_allowance=rounding_loss_allowance, ) if account_balance and ( From bada5796fac7eb5e47a7640ee75709b356dd65c7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 13 Jun 2023 09:32:24 +0530 Subject: [PATCH 42/63] fix: attribute error on payment reconciliation tool --- .../payment_reconciliation/payment_reconciliation.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 081fe70354..2c8faecf4b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -211,7 +211,13 @@ class PaymentReconciliation(Document): accounting_dimensions=self.accounting_dimension_filter_conditions, ) - cr_dr_notes = [x.voucher_no for x in self.return_invoices] + cr_dr_notes = ( + [x.voucher_no for x in self.return_invoices] + if self.party_type in ["Customer", "Supplier"] + else [] + ) + # Filter out cr/dr notes from outstanding invoices list + # Happens when non-standalone cr/dr notes are linked with another invoice through journal entry non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes] if self.invoice_limit: From a3ea98534874207718727282558739865a11c803 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 13 Jun 2023 17:30:38 +0530 Subject: [PATCH 43/63] refactor: Use `db.set_single_value` (#35668) I just applied semgrep autofix. Untested completed, review before merging. ```yaml - id: frappe-set-value-semantics patterns: - pattern-either: - pattern: frappe.db.set_value($DOCTYPE, None, $...AFTER) - pattern: frappe.db.set_value($DOCTYPE, $DOCTYPE, $...AFTER) fix: frappe.db.set_single_value($DOCTYPE, $...AFTER) message: | If $DOCTYPE is a single doctype then using `frappe.db.set_value` is discouraged for setting values in DB. Use db.set_single_value for single doctype instead. languages: [python] severity: ERROR ``` --- .semgrepignore | 0 .../doctype/fiscal_year/fiscal_year.py | 2 +- .../journal_entry/test_journal_entry.py | 4 +- .../doctype/pos_invoice/test_pos_invoice.py | 4 +- .../purchase_invoice/test_purchase_invoice.py | 10 ++--- .../sales_invoice/test_sales_invoice.py | 32 +++++++------- .../doctype/tax_rule/test_tax_rule.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 2 +- .../test_asset_maintenance.py | 2 +- .../purchase_order/test_purchase_order.py | 8 ++-- .../buying/doctype/supplier/test_supplier.py | 4 +- .../tests/test_subcontracting_controller.py | 4 +- .../doctype/opportunity/test_opportunity.py | 4 +- .../shopping_cart/test_shopping_cart.py | 2 +- .../plaid_settings/test_plaid_settings.py | 2 +- .../doctype/work_order/test_work_order.py | 44 ++++++++----------- ...e_backflush_subcontract_rm_based_on_bom.py | 4 +- .../patches/v12_0/rename_tolerance_fields.py | 2 +- ...eferred_accounting_in_accounts_settings.py | 4 +- .../v12_0/set_default_homepage_type.py | 2 +- .../patches/v12_0/set_priority_for_support.py | 2 +- .../modify_invalid_gain_loss_gl_entries.py | 4 +- .../v14_0/discount_accounting_separation.py | 2 +- erpnext/patches/v14_0/migrate_crm_settings.py | 3 +- .../selling/doctype/customer/test_customer.py | 4 +- .../doctype/sales_order/test_sales_order.py | 27 +++++------- .../test_currency_exchange.py | 14 +++--- erpnext/setup/install.py | 2 +- .../delivery_note/test_delivery_note.py | 6 +-- erpnext/stock/doctype/item/item.py | 6 +-- .../test_landed_cost_voucher.py | 2 +- .../material_request/test_material_request.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 4 +- .../test_quality_inspection.py | 6 +-- .../doctype/stock_entry/test_stock_entry.py | 18 ++++---- .../test_stock_ledger_entry.py | 8 ++-- .../test_stock_reconciliation.py | 2 +- .../stock_settings/test_stock_settings.py | 2 +- erpnext/stock/stock_balance.py | 6 +-- erpnext/support/doctype/issue/test_issue.py | 2 +- .../test_service_level_agreement.py | 2 +- .../issue_analytics/test_issue_analytics.py | 2 +- erpnext/tests/test_exotel.py | 2 +- 43 files changed, 124 insertions(+), 142 deletions(-) create mode 100644 .semgrepignore diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index 3207e4195e..9d1b99b29b 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -12,7 +12,7 @@ from frappe.utils import add_days, add_years, cstr, getdate class FiscalYear(Document): @frappe.whitelist() def set_as_default(self): - frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name) + frappe.db.set_single_value("Global Defaults", "current_fiscal_year", self.name) global_defaults = frappe.get_doc("Global Defaults") global_defaults.check_permission("write") global_defaults.on_update() diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index f7297d19e0..73b1911543 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -105,8 +105,8 @@ class TestJournalEntry(unittest.TestCase): elif test_voucher.doctype in ["Sales Order", "Purchase Order"]: # if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 + frappe.db.set_single_value( + "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 ) submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name) self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 9685d99f35..f842a16b74 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -31,7 +31,7 @@ class TestPOSInvoice(unittest.TestCase): frappe.set_user("Administrator") if frappe.db.get_single_value("Selling Settings", "validate_selling_price"): - frappe.db.set_value("Selling Settings", None, "validate_selling_price", 0) + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 0) def test_timestamp_change(self): w = create_pos_invoice(do_not_save=1) @@ -722,7 +722,7 @@ class TestPOSInvoice(unittest.TestCase): ) if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): - frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1) + frappe.db.set_single_value("Selling Settings", "validate_selling_price", 1) item = "Test Selling Price Validation" make_item(item, {"is_stock_item": 1}) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 5b83534caf..42eb018078 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -42,7 +42,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): @classmethod def setUpClass(self): unlink_payment_on_cancel_of_invoice() - frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) @classmethod def tearDownClass(self): @@ -1232,9 +1232,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice" ) - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1 - ) + frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account") frappe.db.set_value( @@ -1369,8 +1367,8 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pay.reload() pay.cancel() - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled + frappe.db.set_single_value( + "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled ) frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 51e0d91615..784bdf6612 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1063,7 +1063,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(pos.write_off_amount, 10) def test_pos_with_no_gl_entry_for_change_amount(self): - frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 0) + frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 0) make_pos_profile( company="_Test Company with perpetual inventory", @@ -1113,7 +1113,7 @@ class TestSalesInvoice(unittest.TestCase): self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True) - frappe.db.set_value("Accounts Settings", None, "post_change_gl_entries", 1) + frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 1) def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False): if validate_without_change_gle: @@ -2452,7 +2452,7 @@ class TestSalesInvoice(unittest.TestCase): "Check mapping (expense account) of inter company SI to PI in absence of default warehouse." # setup old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company 1") frappe.local.enable_perpetual_inventory["_Test Company 1"] = 1 @@ -2506,7 +2506,7 @@ class TestSalesInvoice(unittest.TestCase): # tear down frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock) def test_sle_for_target_warehouse(self): se = make_stock_entry( @@ -2898,7 +2898,7 @@ class TestSalesInvoice(unittest.TestCase): party_link = create_party_link("Supplier", supplier, customer) # enable common party accounting - frappe.db.set_value("Accounts Settings", None, "enable_common_party_accounting", 1) + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1) # create a sales invoice si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC") @@ -2925,7 +2925,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(jv[0], si.grand_total) party_link.delete() - frappe.db.set_value("Accounts Settings", None, "enable_common_party_accounting", 0) + frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0) def test_payment_statuses(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry @@ -3045,7 +3045,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertRaises(frappe.ValidationError, si.save) def test_sales_invoice_submission_post_account_freezing_date(self): - frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1)) + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", add_days(getdate(), 1)) si = create_sales_invoice(do_not_save=True) si.posting_date = add_days(getdate(), 1) si.save() @@ -3054,7 +3054,7 @@ class TestSalesInvoice(unittest.TestCase): si.posting_date = getdate() si.submit() - frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None) + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) def test_over_billing_case_against_delivery_note(self): """ @@ -3066,7 +3066,7 @@ class TestSalesInvoice(unittest.TestCase): over_billing_allowance = frappe.db.get_single_value( "Accounts Settings", "over_billing_allowance" ) - frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) + frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 0) dn = create_delivery_note() dn.submit() @@ -3082,7 +3082,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue("cannot overbill" in str(err.exception).lower()) - frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance) + frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", over_billing_allowance) def test_multi_currency_deferred_revenue_via_journal_entry(self): deferred_account = create_account( @@ -3121,7 +3121,7 @@ class TestSalesInvoice(unittest.TestCase): si.save() si.submit() - frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", getdate("2019-01-31")) + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", getdate("2019-01-31")) pda1 = frappe.get_doc( dict( @@ -3166,7 +3166,7 @@ class TestSalesInvoice(unittest.TestCase): acc_settings.submit_journal_entries = 0 acc_settings.save() - frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None) + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) def test_standalone_serial_no_return(self): si = create_sales_invoice( @@ -3216,9 +3216,7 @@ class TestSalesInvoice(unittest.TestCase): "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice" ) - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1 - ) + frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) jv = make_journal_entry("_Test Receivable USD - _TC", "_Test Bank - _TC", -7000, save=False) @@ -3261,8 +3259,8 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, nowdate()) - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled + frappe.db.set_single_value( + "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled ) def test_batch_expiry_for_sales_invoice_return(self): diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index 848e05424b..335b483563 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -15,7 +15,7 @@ test_records = frappe.get_test_records("Tax Rule") class TestTaxRule(unittest.TestCase): @classmethod def setUpClass(cls): - frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0) + frappe.db.set_single_value("Shopping Cart Settings", "enabled", 0) @classmethod def tearDownClass(cls): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 0dfcee4325..2a74f20e1b 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1804,7 +1804,7 @@ def set_depreciation_settings_in_company(company=None): company.save() # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) + frappe.db.set_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically", 1) def enable_cwip_accounting(asset_category, enable=1): diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index e40a5519eb..23088c9ccf 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -182,4 +182,4 @@ def set_depreciation_settings_in_company(): company.save() # Enable booking asset depreciation entry automatically - frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) + frappe.db.set_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically", 1) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 920486a78e..3edaffae2a 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -92,7 +92,7 @@ class TestPurchaseOrder(FrappeTestCase): frappe.db.set_value("Item", "_Test Item", "over_delivery_receipt_allowance", 0) frappe.db.set_value("Item", "_Test Item", "over_billing_allowance", 0) - frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) + frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 0) def test_update_remove_child_linked_to_mr(self): """Test impact on linked PO and MR on deleting/updating row.""" @@ -581,7 +581,7 @@ class TestPurchaseOrder(FrappeTestCase): ) def test_group_same_items(self): - frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) frappe.get_doc( { "doctype": "Purchase Order", @@ -836,8 +836,8 @@ class TestPurchaseOrder(FrappeTestCase): ) from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt - frappe.db.set_value("Selling Settings", None, "maintain_same_sales_rate", 1) - frappe.db.set_value("Buying Settings", None, "maintain_same_rate", 1) + frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1) + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) prepare_data_for_internal_transfer() supplier = "_Test Internal Supplier 2" diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index b9fc344647..7a205ac20c 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -156,7 +156,7 @@ class TestSupplier(FrappeTestCase): def test_serach_fields_for_supplier(self): from erpnext.controllers.queries import supplier_query - frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series") + frappe.db.set_single_value("Buying Settings", "supp_master_name", "Naming Series") supplier_name = create_supplier(supplier_name="Test Supplier 1").name @@ -189,7 +189,7 @@ class TestSupplier(FrappeTestCase): self.assertEqual(data[0].supplier_type, "Company") self.assertTrue("supplier_type" in data[0]) - frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name") + frappe.db.set_single_value("Buying Settings", "supp_master_name", "Supplier Name") def create_supplier(**args): diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 8a325e447b..eeb35c4d96 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -1074,8 +1074,8 @@ def make_bom_for_subcontracted_items(): def set_backflush_based_on(based_on): - frappe.db.set_value( - "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", based_on + frappe.db.set_single_value( + "Buying Settings", "backflush_raw_materials_of_subcontract_based_on", based_on ) diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 1ff3267e71..247e20ddf2 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -53,9 +53,7 @@ class TestOpportunity(unittest.TestCase): self.assertEqual(opportunity_doc.total, 2200) def test_carry_forward_of_email_and_comments(self): - frappe.db.set_value( - "CRM Settings", "CRM Settings", "carry_forward_communication_and_comments", 1 - ) + frappe.db.set_single_value("CRM Settings", "carry_forward_communication_and_comments", 1) lead_doc = make_lead() lead_doc.add_comment("Comment", text="Test Comment 1") lead_doc.add_comment("Comment", text="Test Comment 2") diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py index f44f8fe298..951039db4f 100644 --- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py +++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py @@ -205,7 +205,7 @@ class TestShoppingCart(unittest.TestCase): self.assertEqual(quote_doctstatus, 0) - frappe.db.set_value("E Commerce Settings", None, "save_quotations_as_draft", 0) + frappe.db.set_single_value("E Commerce Settings", "save_quotations_as_draft", 0) frappe.local.shopping_cart_settings = None update_cart("_Test Item", 1) quote_name = request_for_quotation() # Request for Quote diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 6d34a204cd..86e1b31eba 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -32,7 +32,7 @@ class TestPlaidSettings(unittest.TestCase): frappe.delete_doc(doctype, d.name, force=True) def test_plaid_disabled(self): - frappe.db.set_value("Plaid Settings", None, "enabled", 0) + frappe.db.set_single_value("Plaid Settings", "enabled", 0) self.assertTrue(get_plaid_configuration() == "disabled") def test_add_account_type(self): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 9f8390ca68..690fe47949 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -503,10 +503,8 @@ class TestWorkOrder(FrappeTestCase): stock_entry.cancel() def test_capcity_planning(self): - frappe.db.set_value( - "Manufacturing Settings", - None, - {"disable_capacity_planning": 0, "capacity_planning_for_days": 1}, + frappe.db.set_single_value( + "Manufacturing Settings", {"disable_capacity_planning": 0, "capacity_planning_for_days": 1} ) data = frappe.get_cached_value( @@ -529,7 +527,7 @@ class TestWorkOrder(FrappeTestCase): self.assertRaises(CapacityError, work_order1.submit) - frappe.db.set_value("Manufacturing Settings", None, {"capacity_planning_for_days": 30}) + frappe.db.set_single_value("Manufacturing Settings", {"capacity_planning_for_days": 30}) work_order1.reload() work_order1.submit() @@ -539,7 +537,7 @@ class TestWorkOrder(FrappeTestCase): work_order.cancel() def test_work_order_with_non_transfer_item(self): - frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM") + frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0} for item, allow_transfer in items.items(): @@ -619,7 +617,7 @@ class TestWorkOrder(FrappeTestCase): fg_item = "Test Batch Size Item For BOM 3" rm1 = "Test Batch Size Item RM 1 For BOM 3" - frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0) + frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 0) for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]: item_args = {"include_item_in_manufacturing": 1, "is_stock_item": 1} @@ -655,7 +653,7 @@ class TestWorkOrder(FrappeTestCase): work_order = make_wo_order_test_record( item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1 ) - frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 1) + frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 1) ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) for row in ste1.get("items"): if row.is_finished_item: @@ -699,10 +697,10 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(sorted(remaining_batches), sorted(batches)) - frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0) + frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 0) def test_partial_material_consumption(self): - frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1) + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 1) wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) ste_cancel_list = [] @@ -736,13 +734,12 @@ class TestWorkOrder(FrappeTestCase): for ste_doc in ste_cancel_list: ste_doc.cancel() - frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 0) def test_extra_material_transfer(self): - frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) - frappe.db.set_value( + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 0) + frappe.db.set_single_value( "Manufacturing Settings", - None, "backflush_raw_materials_based_on", "Material Transferred for Manufacture", ) @@ -787,7 +784,7 @@ class TestWorkOrder(FrappeTestCase): for ste_doc in ste_cancel_list: ste_doc.cancel() - frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM") + frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") def test_make_stock_entry_for_customer_provided_item(self): finished_item = "Test Item for Make Stock Entry 1" @@ -1087,9 +1084,8 @@ class TestWorkOrder(FrappeTestCase): def test_partial_manufacture_entries(self): cancel_stock_entry = [] - frappe.db.set_value( + frappe.db.set_single_value( "Manufacturing Settings", - None, "backflush_raw_materials_based_on", "Material Transferred for Manufacture", ) @@ -1139,7 +1135,7 @@ class TestWorkOrder(FrappeTestCase): doc = frappe.get_doc("Stock Entry", ste) doc.cancel() - frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM") + frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") @change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1}) def test_auto_batch_creation(self): @@ -1283,9 +1279,8 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(work_order.required_items[1].transferred_qty, 2) def test_backflushed_batch_raw_materials_based_on_transferred(self): - frappe.db.set_value( + frappe.db.set_single_value( "Manufacturing Settings", - None, "backflush_raw_materials_based_on", "Material Transferred for Manufacture", ) @@ -1356,9 +1351,8 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(abs(d.qty), 2) def test_backflushed_serial_no_raw_materials_based_on_transferred(self): - frappe.db.set_value( + frappe.db.set_single_value( "Manufacturing Settings", - None, "backflush_raw_materials_based_on", "Material Transferred for Manufacture", ) @@ -1400,9 +1394,8 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(manufacture_ste_doc2.items[0].qty, 2) def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self): - frappe.db.set_value( + frappe.db.set_single_value( "Manufacturing Settings", - None, "backflush_raw_materials_based_on", "Material Transferred for Manufacture", ) @@ -1486,9 +1479,8 @@ class TestWorkOrder(FrappeTestCase): self.assertFalse(serial_nos) def test_non_consumed_material_return_against_work_order(self): - frappe.db.set_value( + frappe.db.set_single_value( "Manufacturing Settings", - None, "backflush_raw_materials_based_on", "Material Transferred for Manufacture", ) diff --git a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py index 51ba706dcf..037dda56c8 100644 --- a/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py +++ b/erpnext/patches/v11_0/update_backflush_subcontract_rm_based_on_bom.py @@ -7,8 +7,8 @@ import frappe def execute(): frappe.reload_doc("buying", "doctype", "buying_settings") - frappe.db.set_value( - "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM" + frappe.db.set_single_value( + "Buying Settings", "backflush_raw_materials_of_subcontract_based_on", "BOM" ) frappe.reload_doc("stock", "doctype", "stock_entry_detail") diff --git a/erpnext/patches/v12_0/rename_tolerance_fields.py b/erpnext/patches/v12_0/rename_tolerance_fields.py index ef1ba655a9..c53604c506 100644 --- a/erpnext/patches/v12_0/rename_tolerance_fields.py +++ b/erpnext/patches/v12_0/rename_tolerance_fields.py @@ -11,6 +11,6 @@ def execute(): rename_field("Item", "tolerance", "over_delivery_receipt_allowance") qty_allowance = frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance") - frappe.db.set_value("Accounts Settings", None, "over_delivery_receipt_allowance", qty_allowance) + frappe.db.set_single_value("Accounts Settings", "over_delivery_receipt_allowance", qty_allowance) frappe.db.sql("update tabItem set over_billing_allowance=over_delivery_receipt_allowance") diff --git a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py index 37af989549..84dd1c7116 100644 --- a/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py +++ b/erpnext/patches/v12_0/set_automatically_process_deferred_accounting_in_accounts_settings.py @@ -4,6 +4,6 @@ import frappe def execute(): frappe.reload_doc("accounts", "doctype", "accounts_settings") - frappe.db.set_value( - "Accounts Settings", None, "automatically_process_deferred_accounting_entry", 1 + frappe.db.set_single_value( + "Accounts Settings", "automatically_process_deferred_accounting_entry", 1 ) diff --git a/erpnext/patches/v12_0/set_default_homepage_type.py b/erpnext/patches/v12_0/set_default_homepage_type.py index d70b28efd8..d91fe33a3f 100644 --- a/erpnext/patches/v12_0/set_default_homepage_type.py +++ b/erpnext/patches/v12_0/set_default_homepage_type.py @@ -2,4 +2,4 @@ import frappe def execute(): - frappe.db.set_value("Homepage", "Homepage", "hero_section_based_on", "Default") + frappe.db.set_single_value("Homepage", "hero_section_based_on", "Default") diff --git a/erpnext/patches/v12_0/set_priority_for_support.py b/erpnext/patches/v12_0/set_priority_for_support.py index a8a07e76ea..a16eb8a36b 100644 --- a/erpnext/patches/v12_0/set_priority_for_support.py +++ b/erpnext/patches/v12_0/set_priority_for_support.py @@ -46,7 +46,7 @@ def set_priorities_service_level(): frappe.reload_doc("support", "doctype", "service_level") frappe.reload_doc("support", "doctype", "support_settings") - frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) + frappe.db.set_single_value("Support Settings", "track_service_level_agreement", 1) for service_level in service_level_priorities: if service_level: diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index 6c64ef6559..0f77afda78 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -47,7 +47,7 @@ def execute(): acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto") if acc_frozen_upto: - frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None) + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None) for invoice in purchase_invoices + sales_invoices: try: @@ -65,4 +65,4 @@ def execute(): print(f"Failed to correct gl entries of {invoice.name}") if acc_frozen_upto: - frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", acc_frozen_upto) + frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", acc_frozen_upto) diff --git a/erpnext/patches/v14_0/discount_accounting_separation.py b/erpnext/patches/v14_0/discount_accounting_separation.py index 0d1349a320..4216ecc337 100644 --- a/erpnext/patches/v14_0/discount_accounting_separation.py +++ b/erpnext/patches/v14_0/discount_accounting_separation.py @@ -8,4 +8,4 @@ def execute(): discount_account = data and int(data[0][0]) or 0 if discount_account: for doctype in ["Buying Settings", "Selling Settings"]: - frappe.db.set_value(doctype, doctype, "enable_discount_accounting", 1, update_modified=False) + frappe.db.set_single_value(doctype, "enable_discount_accounting", 1, update_modified=False) diff --git a/erpnext/patches/v14_0/migrate_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py index 696a1009df..24772553d9 100644 --- a/erpnext/patches/v14_0/migrate_crm_settings.py +++ b/erpnext/patches/v14_0/migrate_crm_settings.py @@ -11,8 +11,7 @@ def execute(): frappe.reload_doc("crm", "doctype", "crm_settings") if settings: - frappe.db.set_value( - "CRM Settings", + frappe.db.set_single_value( "CRM Settings", { "campaign_naming_by": settings.campaign_naming_by, diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index a621c737ed..6e737e4b55 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -345,7 +345,7 @@ class TestCustomer(FrappeTestCase): def test_serach_fields_for_customer(self): from erpnext.controllers.queries import customer_query - frappe.db.set_value("Selling Settings", None, "cust_master_name", "Naming Series") + frappe.db.set_single_value("Selling Settings", "cust_master_name", "Naming Series") make_property_setter( "Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype" @@ -371,7 +371,7 @@ class TestCustomer(FrappeTestCase): self.assertEqual(data[0].territory, "_Test Territory") self.assertTrue("territory" in data[0]) - frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name") + frappe.db.set_single_value("Selling Settings", "cust_master_name", "Customer Name") def get_customer_dict(customer_name): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 6459deffaa..45100d7a64 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -43,11 +43,8 @@ class TestSalesOrder(FrappeTestCase): @classmethod def tearDownClass(cls) -> None: # reset config to previous state - frappe.db.set_value( - "Accounts Settings", - "Accounts Settings", - "unlink_advance_payment_on_cancelation_of_order", - cls.unlink_setting, + frappe.db.set_single_value( + "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", cls.unlink_setting ) super().tearDownClass() @@ -705,7 +702,7 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(so.taxes[0].total, 110) old_stock_settings_value = frappe.db.get_single_value("Stock Settings", "default_warehouse") - frappe.db.set_value("Stock Settings", None, "default_warehouse", "_Test Warehouse - _TC") + frappe.db.set_single_value("Stock Settings", "default_warehouse", "_Test Warehouse - _TC") items = json.dumps( [ @@ -741,7 +738,7 @@ class TestSalesOrder(FrappeTestCase): so.delete() new_item_with_tax.delete() frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete() - frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value) + frappe.db.set_single_value("Stock Settings", "default_warehouse", old_stock_settings_value) def test_warehouse_user(self): test_user = create_user("test_so_warehouse_user@example.com", "Sales User", "Stock User") @@ -820,7 +817,7 @@ class TestSalesOrder(FrappeTestCase): def test_auto_insert_price(self): make_item("_Test Item for Auto Price List", {"is_stock_item": 0}) make_item("_Test Item for Auto Price List with Discount Percentage", {"is_stock_item": 0}) - frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) item_price = frappe.db.get_value( "Item Price", {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"} @@ -861,7 +858,7 @@ class TestSalesOrder(FrappeTestCase): ) # do not update price list - frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0) + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) item_price = frappe.db.get_value( "Item Price", {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"} @@ -882,7 +879,7 @@ class TestSalesOrder(FrappeTestCase): None, ) - frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) + frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) def test_drop_shipping(self): from erpnext.buying.doctype.purchase_order.purchase_order import update_status @@ -1257,8 +1254,8 @@ class TestSalesOrder(FrappeTestCase): def test_advance_payment_entry_unlink_against_sales_order(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 + frappe.db.set_single_value( + "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 ) so = make_sales_order() @@ -1312,8 +1309,8 @@ class TestSalesOrder(FrappeTestCase): so = make_sales_order() # disable unlinking of payment entry - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 + frappe.db.set_single_value( + "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 ) # create a payment entry against sales order @@ -2080,7 +2077,7 @@ def make_sales_order(**args): def create_dn_against_so(so, delivered_qty=0): - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) dn = make_delivery_note(so) dn.get("items")[0].qty = delivered_qty or 5 diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index e3d281a564..3b48c2b312 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -87,13 +87,13 @@ class TestCurrencyExchange(unittest.TestCase): cache.delete(key) def tearDown(self): - frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) + frappe.db.set_single_value("Accounts Settings", "allow_stale", 1) self.clear_cache() def test_exchange_rate(self, mock_get): save_new_records(test_records) - frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) + frappe.db.set_single_value("Accounts Settings", "allow_stale", 1) # Start with allow_stale is True exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") @@ -124,7 +124,7 @@ class TestCurrencyExchange(unittest.TestCase): settings.save() # Update exchange - frappe.db.set_value("Accounts Settings", None, "allow_stale", 1) + frappe.db.set_single_value("Accounts Settings", "allow_stale", 1) # Start with allow_stale is True exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") @@ -152,8 +152,8 @@ class TestCurrencyExchange(unittest.TestCase): def test_exchange_rate_strict(self, mock_get): # strict currency settings - frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) - frappe.db.set_value("Accounts Settings", None, "stale_days", 1) + frappe.db.set_single_value("Accounts Settings", "allow_stale", 0) + frappe.db.set_single_value("Accounts Settings", "stale_days", 1) exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") self.assertEqual(exchange_rate, 60.0) @@ -175,8 +175,8 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") self.assertEqual(exchange_rate, 65.1) - frappe.db.set_value("Accounts Settings", None, "allow_stale", 0) - frappe.db.set_value("Accounts Settings", None, "stale_days", 1) + frappe.db.set_single_value("Accounts Settings", "allow_stale", 0) + frappe.db.set_single_value("Accounts Settings", "stale_days", 1) self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_buying") diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 74c1ee22c9..013d945c22 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -211,7 +211,7 @@ def add_standard_navbar_items(): def add_app_name(): - frappe.db.set_value("System Settings", None, "app_name", "ERPNext") + frappe.db.set_single_value("System Settings", "app_name", "ERPNext") def setup_log_settings(): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 15a72a862e..8baae8a19c 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -43,7 +43,7 @@ from erpnext.stock.stock_ledger import get_previous_sle class TestDeliveryNote(FrappeTestCase): def test_over_billing_against_dn(self): - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) dn = create_delivery_note(do_not_submit=True) self.assertRaises(frappe.ValidationError, make_sales_invoice, dn.name) @@ -709,7 +709,7 @@ class TestDeliveryNote(FrappeTestCase): # Testing if Customer's Purchase Order No was rightly copied self.assertEqual(so.po_no, si.po_no) - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) dn1 = make_delivery_note(so.name) dn1.get("items")[0].qty = 2 @@ -741,7 +741,7 @@ class TestDeliveryNote(FrappeTestCase): make_sales_invoice as make_sales_invoice_from_so, ) - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) so = make_sales_order() diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f91a991173..93d799a395 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -585,7 +585,7 @@ class Item(Document): existing_allow_negative_stock = frappe.db.get_value( "Stock Settings", None, "allow_negative_stock" ) - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) repost_stock_for_warehouses = frappe.get_all( "Stock Ledger Entry", @@ -601,8 +601,8 @@ class Item(Document): for warehouse in repost_stock_for_warehouses: repost_stock(new_name, warehouse) - frappe.db.set_value( - "Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock + frappe.db.set_single_value( + "Stock Settings", "allow_negative_stock", existing_allow_negative_stock ) def update_bom_item_desc(self): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 03ff12cae0..257f263bd2 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -25,7 +25,7 @@ from erpnext.stock.serial_batch_bundle import SerialNoValuation class TestLandedCostVoucher(FrappeTestCase): def test_landed_cost_voucher(self): - frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) pr = make_purchase_receipt( company="_Test Company with perpetual inventory", diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 03f58c664d..e5aff38c52 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -400,7 +400,7 @@ class TestMaterialRequest(FrappeTestCase): mr.insert() mr.submit() - frappe.db.set_value("Stock Settings", None, "mr_qty_allowance", 20) + frappe.db.set_single_value("Stock Settings", "mr_qty_allowance", 20) # map a stock entry diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c0ea806196..92235b0845 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -27,7 +27,7 @@ from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction class TestPurchaseReceipt(FrappeTestCase): def setUp(self): - frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) def test_purchase_receipt_received_qty(self): """ @@ -1925,7 +1925,7 @@ def make_purchase_receipt(**args): if not frappe.db.exists("Location", "Test Location"): frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() - frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + frappe.db.set_single_value("Buying Settings", "allow_multiple_items", 1) pr = frappe.new_doc("Purchase Receipt") args = frappe._dict(args) pr.posting_date = args.posting_date or today() diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 9d2e139622..f5f8c3afd1 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -167,13 +167,13 @@ class TestQualityInspection(FrappeTestCase): reference_type="Stock Entry", reference_name=se.name, readings=readings, status="Rejected" ) - frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop") + frappe.db.set_single_value("Stock Settings", "action_if_quality_inspection_is_rejected", "Stop") se.reload() self.assertRaises( QualityInspectionRejectedError, se.submit ) # when blocked in Stock settings, block rejected QI - frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Warn") + frappe.db.set_single_value("Stock Settings", "action_if_quality_inspection_is_rejected", "Warn") se.reload() se.submit() # when allowed in Stock settings, allow rejected QI @@ -182,7 +182,7 @@ class TestQualityInspection(FrappeTestCase): qa.cancel() se.reload() se.cancel() - frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop") + frappe.db.set_single_value("Stock Settings", "action_if_quality_inspection_is_rejected", "Stop") def test_qi_status(self): make_stock_entry( diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 64d81f6937..cc8a108bc9 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -55,7 +55,7 @@ class TestStockEntry(FrappeTestCase): frappe.set_user("Administrator") def test_fifo(self): - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) item_code = "_Test Item 2" warehouse = "_Test Warehouse - _TC" @@ -142,7 +142,7 @@ class TestStockEntry(FrappeTestCase): or 0 ) - frappe.db.set_value("Stock Settings", None, "auto_indent", 1) + frappe.db.set_single_value("Stock Settings", "auto_indent", 1) # update re-level qty so that it is more than projected_qty if projected_qty >= variant.reorder_levels[0].warehouse_reorder_level: @@ -154,7 +154,7 @@ class TestStockEntry(FrappeTestCase): mr_list = reorder_item() - frappe.db.set_value("Stock Settings", None, "auto_indent", 0) + frappe.db.set_single_value("Stock Settings", "auto_indent", 0) items = [] for mr in mr_list: @@ -792,24 +792,24 @@ class TestStockEntry(FrappeTestCase): remove_user_permission("Company", "_Test Company 1", "test2@example.com") def test_freeze_stocks(self): - frappe.db.set_value("Stock Settings", None, "stock_auth_role", "") + frappe.db.set_single_value("Stock Settings", "stock_auth_role", "") # test freeze_stocks_upto - frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", add_days(nowdate(), 5)) + frappe.db.set_single_value("Stock Settings", "stock_frozen_upto", add_days(nowdate(), 5)) se = frappe.copy_doc(test_records[0]).insert() self.assertRaises(StockFreezeError, se.submit) - frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", "") + frappe.db.set_single_value("Stock Settings", "stock_frozen_upto", "") # test freeze_stocks_upto_days - frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", -1) + frappe.db.set_single_value("Stock Settings", "stock_frozen_upto_days", -1) se = frappe.copy_doc(test_records[0]) se.set_posting_time = 1 se.posting_date = nowdate() se.set_stock_entry_type() se.insert() self.assertRaises(StockFreezeError, se.submit) - frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0) + frappe.db.set_single_value("Stock Settings", "stock_frozen_upto_days", 0) def test_work_order(self): from erpnext.manufacturing.doctype.work_order.work_order import ( @@ -1211,7 +1211,7 @@ class TestStockEntry(FrappeTestCase): ) def test_conversion_factor_change(self): - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) repack_entry = frappe.copy_doc(test_records[3]) repack_entry.posting_date = nowdate() repack_entry.posting_time = nowtime() diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index a398855159..f7c6ffece8 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -416,8 +416,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): def test_back_dated_entry_not_allowed(self): # Back dated stock transactions are only allowed to stock managers - frappe.db.set_value( - "Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", "Stock Manager" + frappe.db.set_single_value( + "Stock Settings", "role_allowed_to_create_edit_back_dated_transactions", "Stock Manager" ) # Set User with Stock User role but not Stock Manager @@ -453,8 +453,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin): stock_entry_on_today.cancel() finally: - frappe.db.set_value( - "Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None + frappe.db.set_single_value( + "Stock Settings", "role_allowed_to_create_edit_back_dated_transactions", None ) frappe.set_user("Administrator") user.remove_roles("Stock Manager") diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index a04e2da581..4817c8d8dc 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -33,7 +33,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): def setUpClass(cls): create_batch_or_serial_no_items() super().setUpClass() - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) def tearDown(self): frappe.local.future_sle = {} diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py index 974e16339b..cda739e582 100644 --- a/erpnext/stock/doctype/stock_settings/test_stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py @@ -10,7 +10,7 @@ from frappe.tests.utils import FrappeTestCase class TestStockSettings(FrappeTestCase): def setUp(self): super().setUp() - frappe.db.set_value("Stock Settings", None, "clean_description_html", 0) + frappe.db.set_single_value("Stock Settings", "clean_description_html", 0) def test_settings(self): item = frappe.get_doc( diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 488675518a..a4fe2ee52f 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -18,7 +18,7 @@ def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, existing_allow_negative_stock = frappe.db.get_value( "Stock Settings", None, "allow_negative_stock" ) - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) item_warehouses = frappe.db.sql( """ @@ -37,8 +37,8 @@ def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, frappe.db.rollback() if allow_negative_stock: - frappe.db.set_value( - "Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock + frappe.db.set_single_value( + "Stock Settings", "allow_negative_stock", existing_allow_negative_stock ) frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py index a44012444c..b30b699813 100644 --- a/erpnext/support/doctype/issue/test_issue.py +++ b/erpnext/support/doctype/issue/test_issue.py @@ -20,7 +20,7 @@ class TestSetUp(unittest.TestCase): frappe.db.sql("delete from `tabSLA Fulfilled On Status`") frappe.db.sql("delete from `tabPause SLA On Status`") frappe.db.sql("delete from `tabService Day`") - frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) + frappe.db.set_single_value("Support Settings", "track_service_level_agreement", 1) create_service_level_agreements_for_issues() diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index 472f6bc24e..1f8f4a2436 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -16,7 +16,7 @@ from erpnext.support.doctype.service_level_agreement.service_level_agreement imp class TestServiceLevelAgreement(unittest.TestCase): def setUp(self): self.create_company() - frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) + frappe.db.set_single_value("Support Settings", "track_service_level_agreement", 1) lead = frappe.qb.DocType("Lead") frappe.qb.from_(lead).delete().where(lead.company == self.company).run() diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py index 169392e5e9..e30b31beef 100644 --- a/erpnext/support/report/issue_analytics/test_issue_analytics.py +++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py @@ -17,7 +17,7 @@ class TestIssueAnalytics(unittest.TestCase): @classmethod def setUpClass(self): frappe.db.sql("delete from `tabIssue` where company='_Test Company'") - frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) + frappe.db.set_single_value("Support Settings", "track_service_level_agreement", 1) current_month_date = getdate() last_month_date = add_months(current_month_date, -1) diff --git a/erpnext/tests/test_exotel.py b/erpnext/tests/test_exotel.py index f5cca72002..9b91414571 100644 --- a/erpnext/tests/test_exotel.py +++ b/erpnext/tests/test_exotel.py @@ -12,7 +12,7 @@ class TestExotel(FrappeAPITestCase): cls.test_employee_name = make_employee( user="test_employee_exotel@company.com", cell_number="9999999999" ) - frappe.db.set_value("Exotel Settings", "Exotel Settings", "enabled", 1) + frappe.db.set_single_value("Exotel Settings", "enabled", 1) phones = [{"phone": "+91 9999999991", "is_primary_phone": 0, "is_primary_mobile_no": 1}] create_contact(name="Test Contact", salutation="Mr", phones=phones) frappe.db.commit() From 20de27d480b6c55fce0335cac15866d83b44acc1 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Tue, 13 Jun 2023 09:36:53 -0400 Subject: [PATCH 44/63] fix(accounts): validate payment entry references with latest data. (#31166) * test: payment entry over allocation. * fix: validate allocated_amount against latest outstanding amount. * fix: payment entry get outstanding documents for advance payments * fix: only fetch latest outstanding_amount. * fix: throw if reference is allocated * test: throw error if a reference has been partially allocated after inital creation. * chore: test name * fix: remove unused part of test * chore: linter * chore: more user friendly error messages * fix: only validate outstanding amount if partly paid and don't filter by cost center * chore: minor refactor for doc.cost_center Co-authored-by: Deepesh Garg --------- Co-authored-by: Anand Baburajan Co-authored-by: Deepesh Garg --- .../doctype/payment_entry/payment_entry.py | 68 +++++++++++++++---- .../payment_entry/test_payment_entry.py | 24 +++++++ 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 3df48e22ad..b6d3e5a30e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -148,19 +148,57 @@ class PaymentEntry(AccountsController): ) def validate_allocated_amount(self): - for d in self.get("references"): + if self.payment_type == "Internal Transfer": + return + + latest_references = get_outstanding_reference_documents( + { + "posting_date": self.posting_date, + "company": self.company, + "party_type": self.party_type, + "payment_type": self.payment_type, + "party": self.party, + "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + } + ) + + # Group latest_references by (voucher_type, voucher_no) + latest_lookup = {} + for d in latest_references: + d = frappe._dict(d) + latest_lookup.update({(d.voucher_type, d.voucher_no): d}) + + for d in self.get("references").copy(): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) + + # The reference has already been fully paid + if not latest: + frappe.throw( + _("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name) + ) + # The reference has already been partly paid + elif ( + latest.outstanding_amount < latest.invoice_amount + and d.outstanding_amount != latest.outstanding_amount + ): + frappe.throw( + _( + "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount." + ).format(d.reference_doctype, d.reference_name) + ) + + d.outstanding_amount = latest.outstanding_amount + + fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") + if (flt(d.allocated_amount)) > 0: if flt(d.allocated_amount) > flt(d.outstanding_amount): - frappe.throw( - _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx) - ) + frappe.throw(fail_message.format(d.idx)) # Check for negative outstanding invoices as well if flt(d.allocated_amount) < 0: if flt(d.allocated_amount) < flt(d.outstanding_amount): - frappe.throw( - _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx) - ) + frappe.throw(fail_message.format(d.idx)) def delink_advance_entry_references(self): for reference in self.references: @@ -373,7 +411,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." + "{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry." ).format( _(k), frappe.bold(", ".join(d.reference_name for d in v)), @@ -1449,7 +1487,7 @@ def get_orders_to_be_billed( if voucher_type: doc = frappe.get_doc({"doctype": voucher_type}) condition = "" - if doc and hasattr(doc, "cost_center"): + if doc and hasattr(doc, "cost_center") and doc.cost_center: condition = " and cost_center='%s'" % cost_center orders = [] @@ -1495,9 +1533,15 @@ def get_orders_to_be_billed( order_list = [] for d in orders: - if not ( - flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than")) - and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than")) + if ( + filters + and filters.get("outstanding_amt_greater_than") + and filters.get("outstanding_amt_less_than") + and not ( + flt(filters.get("outstanding_amt_greater_than")) + <= flt(d.outstanding_amount) + <= flt(filters.get("outstanding_amt_less_than")) + ) ): continue diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 68f333dc29..278b12f659 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1013,6 +1013,30 @@ class TestPaymentEntry(FrappeTestCase): employee = make_employee("test_payment_entry@salary.com", company="_Test Company") create_payment_entry(party_type="Employee", party=employee, save=True) + def test_duplicate_payment_entry_allocate_amount(self): + si = create_sales_invoice() + + pe_draft = get_payment_entry("Sales Invoice", si.name) + pe_draft.insert() + + pe = get_payment_entry("Sales Invoice", si.name) + pe.submit() + + self.assertRaises(frappe.ValidationError, pe_draft.submit) + + def test_duplicate_payment_entry_partial_allocate_amount(self): + si = create_sales_invoice() + + pe_draft = get_payment_entry("Sales Invoice", si.name) + pe_draft.insert() + + pe = get_payment_entry("Sales Invoice", si.name) + pe.received_amount = si.total / 2 + pe.references[0].allocated_amount = si.total / 2 + pe.submit() + + self.assertRaises(frappe.ValidationError, pe_draft.submit) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 9f669d4c2f18613b0a8e7b58074ce0aa3438037c Mon Sep 17 00:00:00 2001 From: Hossein Yousefian <86075967+ihosseinu@users.noreply.github.com> Date: Tue, 13 Jun 2023 17:20:07 +0330 Subject: [PATCH 45/63] Stock aging report fix when called in dashboard chart (#35671) fix: get_range_age conditions fixed see https://github.com/frappe/erpnext/issues/35669 --- erpnext/stock/report/stock_ageing/stock_ageing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index d3f1f31af4..d0929a082c 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -96,14 +96,14 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D range1 = range2 = range3 = above_range3 = 0.0 for item in fifo_queue: - age = date_diff(to_date, item[1]) + age = flt(date_diff(to_date, item[1])) qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0 - if age <= filters.range1: + if age <= flt(filters.range1): range1 = flt(range1 + qty, precision) - elif age <= filters.range2: + elif age <= flt(filters.range2): range2 = flt(range2 + qty, precision) - elif age <= filters.range3: + elif age <= flt(filters.range3): range3 = flt(range3 + qty, precision) else: above_range3 = flt(above_range3 + qty, precision) From 491a50a02766d833eec0b2cad8650ef495206a8e Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 13 Jun 2023 19:42:56 +0530 Subject: [PATCH 46/63] fix: make showing taxes as table in print configurable (#35672) --- .../doctype/accounts_settings/accounts_settings.json | 9 ++++++++- erpnext/controllers/accounts_controller.py | 3 +++ erpnext/controllers/print_settings.py | 8 +++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index c59d90dfab..09482d7d30 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -34,6 +34,7 @@ "book_tax_discount_loss", "print_settings", "show_inclusive_tax_in_print", + "show_taxes_as_table_in_print", "column_break_12", "show_payment_schedule_in_print", "currency_exchange_section", @@ -376,6 +377,12 @@ "fieldname": "auto_reconcile_payments", "fieldtype": "Check", "label": "Auto Reconcile Payments" + }, + { + "default": "0", + "fieldname": "show_taxes_as_table_in_print", + "fieldtype": "Check", + "label": "Show Taxes as Table in Print" } ], "icon": "icon-cog", @@ -383,7 +390,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-06-01 15:42:44.912316", + "modified": "2023-06-13 18:47:46.430291", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2e290e30ca..c83e28d78f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -917,6 +917,9 @@ class AccountsController(TransactionBase): return is_inclusive + def should_show_taxes_as_table_in_print(self): + return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print")) + def validate_advance_entries(self): order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py index d2c80961a3..c951154a9e 100644 --- a/erpnext/controllers/print_settings.py +++ b/erpnext/controllers/print_settings.py @@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings): doc.print_templates.update( { "total": "templates/print_formats/includes/total.html", - "taxes": "templates/print_formats/includes/taxes.html", } ) + if not doc.should_show_taxes_as_table_in_print(): + doc.print_templates.update( + { + "taxes": "templates/print_formats/includes/taxes.html", + } + ) + def format_columns(display_columns, compact_fields): compact_fields = compact_fields + ["image", "item_code", "item_name"] From 937c0feefe302d0a63fca2bee84da1580f7b6e26 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Jun 2023 20:06:36 +0530 Subject: [PATCH 47/63] fix: Lower deduction certificate not getting applied (#35667) --- .../tax_withholding_category.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) 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 d8c037089d..c2b7ff0f35 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -7,7 +7,7 @@ from frappe import _, qb from frappe.model.document import Document from frappe.query_builder import Criterion from frappe.query_builder.functions import Abs, Sum -from frappe.utils import cint, getdate +from frappe.utils import cint, flt, getdate class TaxWithholdingCategory(Document): @@ -581,7 +581,12 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total): tds_amount = 0 limit_consumed = frappe.db.get_value( "Purchase Invoice", - {"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1}, + { + "supplier": ("in", parties), + "apply_tds": 1, + "docstatus": 1, + "posting_date": ("between", (ldc.valid_from, ldc.valid_upto)), + }, "sum(tax_withholding_net_total)", ) @@ -596,10 +601,10 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total): def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): - if current_amount < (certificate_limit - deducted_amount): + if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0: return current_amount * rate / 100 else: - ltds_amount = certificate_limit - deducted_amount + ltds_amount = certificate_limit - flt(deducted_amount) tds_amount = current_amount - ltds_amount return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100 @@ -610,9 +615,9 @@ def is_valid_certificate( ): valid = False - if ( - getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto) - ) and certificate_limit > deducted_amount: + available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount) + + if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0: valid = True return valid From 984f89d274289535e05c53ae8dc47ef4454a65e3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Jun 2023 21:35:52 +0530 Subject: [PATCH 48/63] fix: Validation for delivery date in Sales Order (#35597) * fix: Validation for delivery date in Sales Order * chore: update utils * chore: revert * chore: Add default delivery date --- erpnext/selling/doctype/quotation/quotation.py | 2 ++ erpnext/selling/doctype/quotation/test_quotation.py | 4 +--- erpnext/selling/doctype/sales_order/sales_order.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 61969fe8a9..8ff681b048 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -299,6 +299,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): ) target.flags.ignore_permissions = ignore_permissions + target.delivery_date = nowdate() target.run_method("set_missing_values") target.run_method("calculate_taxes_and_totals") @@ -306,6 +307,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): balance_qty = obj.qty - ordered_items.get(obj.item_code, 0.0) target.qty = balance_qty if balance_qty > 0 else 0 target.stock_qty = flt(target.qty) * flt(obj.conversion_factor) + target.delivery_date = nowdate() if obj.against_blanket_order: target.against_blanket_order = obj.against_blanket_order diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 67f6518657..5623a12cdd 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -60,9 +60,9 @@ class TestQuotation(FrappeTestCase): sales_order = make_sales_order(quotation.name) sales_order.currency = "USD" sales_order.conversion_rate = 20.0 - sales_order.delivery_date = "2019-01-01" sales_order.naming_series = "_T-Quotation-" sales_order.transaction_date = nowdate() + sales_order.delivery_date = nowdate() sales_order.insert() self.assertEqual(sales_order.currency, "USD") @@ -644,8 +644,6 @@ def make_quotation(**args): }, ) - qo.delivery_date = add_days(qo.transaction_date, 10) - if not args.do_not_save: qo.insert() if not args.do_not_submit: diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 58b9df8dab..624dadbc4d 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -171,7 +171,8 @@ class SalesOrder(SellingController): frappe.msgprint( _("Expected Delivery Date should be after Sales Order Date"), indicator="orange", - title=_("Warning"), + title=_("Invalid Delivery Date"), + raise_exception=True, ) else: frappe.throw(_("Please enter Delivery Date")) From fe054508f12678236986ea3cf867b12c7f4bcde2 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 14 Jun 2023 15:04:36 +0530 Subject: [PATCH 49/63] fix: 'NoneType' object has no attribute 'precision' for Job Card --- .../serial_and_batch_bundle.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 9a4206a03b..57bb71ef1e 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -124,6 +124,7 @@ class SerialandBatchBundle(Document): def set_incoming_rate(self, row=None, save=False): if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [ "Installation Note", + "Job Card", "Maintenance Schedule", "Pick List", ]: @@ -569,6 +570,9 @@ class SerialandBatchBundle(Document): @property def child_table(self): + if self.voucher_type == "Job Card": + return + parent_child_map = { "Asset Capitalization": "Asset Capitalization Stock Item", "Asset Repair": "Asset Repair Consumed Item", @@ -576,11 +580,11 @@ class SerialandBatchBundle(Document): "Stock Entry": "Stock Entry Detail", } - table = f"{self.voucher_type} Item" - if self.voucher_type in parent_child_map: - table = parent_child_map[self.voucher_type] - - return table + return ( + parent_child_map[self.voucher_type] + if self.voucher_type in parent_child_map + else f"{self.voucher_type} Item" + ) def delink_refernce_from_voucher(self): or_filters = {"serial_and_batch_bundle": self.name} From 5c805db57318a9d74cdf991115546aae0e310400 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 12 Jun 2023 13:06:13 +0530 Subject: [PATCH 50/63] fix(ux): add `is_cancelled=0` filter for SBB --- erpnext/public/js/controllers/transaction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a47d131866..953a8936b0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -130,6 +130,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe 'item_code': item_row.item_code, 'voucher_type': doc.doctype, 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, } } }); From 7549a5c371c318395a6375696e48155245d4c659 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Wed, 14 Jun 2023 14:58:01 +0530 Subject: [PATCH 51/63] fix(ux): add filters for SBB --- .../asset_capitalization.js | 12 ++++++++++++ .../assets/doctype/asset_repair/asset_repair.js | 12 ++++++++++++ .../manufacturing/doctype/job_card/job_card.js | 11 +++++++++++ .../installation_note/installation_note.js | 11 +++++++++++ erpnext/selling/doctype/quotation/quotation.js | 12 ++++++++++++ erpnext/stock/doctype/pick_list/pick_list.js | 17 +++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.js | 12 ++++++++++++ .../stock_reconciliation.js | 12 ++++++++++++ .../subcontracting_receipt.js | 12 ++++++++++++ 9 files changed, 111 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js index 5a3768585a..96f4438ef7 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js @@ -65,6 +65,18 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s }; }); + me.frm.set_query("serial_and_batch_bundle", "stock_items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); + me.frm.set_query("item_code", "stock_items", function() { return erpnext.queries.item({"is_stock_item": 1}); }); diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index f9ed2cc344..b2ab82cbfb 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -28,6 +28,18 @@ frappe.ui.form.on('Asset Repair', { } }; }; + + frm.set_query("serial_and_batch_bundle", "stock_items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); }, refresh: function(frm) { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 5305db318b..6c9338f249 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -12,6 +12,17 @@ frappe.ui.form.on('Job Card', { }; }); + frm.set_query("serial_and_batch_bundle", () => { + return { + filters: { + 'item_code': frm.doc.production_item, + 'voucher_type': frm.doc.doctype, + 'voucher_no': ["in", [frm.doc.name, ""]], + 'is_cancelled': 0, + } + } + }); + frm.set_indicator_formatter('sub_operation', function(doc) { if (doc.status == "Pending") { diff --git a/erpnext/selling/doctype/installation_note/installation_note.js b/erpnext/selling/doctype/installation_note/installation_note.js index 27a3b35ccf..d63060e6e4 100644 --- a/erpnext/selling/doctype/installation_note/installation_note.js +++ b/erpnext/selling/doctype/installation_note/installation_note.js @@ -7,6 +7,17 @@ frappe.ui.form.on('Installation Note', { frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer', erpnext.queries.customer); + frm.set_query("serial_and_batch_bundle", "items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); }, onload: function(frm) { if(!frm.doc.status) { diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 2d5c3fa961..280485a833 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -34,6 +34,18 @@ frappe.ui.form.on('Quotation', { } }; }); + + frm.set_query("serial_and_batch_bundle", "packed_items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); }, refresh: function(frm) { diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 54e263130e..acbb62d0a0 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -12,6 +12,7 @@ frappe.ui.form.on('Pick List', { 'Delivery Note': 'Delivery Note', 'Stock Entry': 'Stock Entry', }; + frm.set_query('parent_warehouse', () => { return { filters: { @@ -20,6 +21,7 @@ frappe.ui.form.on('Pick List', { } }; }); + frm.set_query('work_order', () => { return { query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders', @@ -28,6 +30,7 @@ frappe.ui.form.on('Pick List', { } }; }); + frm.set_query('material_request', () => { return { filters: { @@ -35,9 +38,11 @@ frappe.ui.form.on('Pick List', { } }; }); + frm.set_query('item_code', 'locations', () => { return erpnext.queries.item({ "is_stock_item": 1 }); }); + frm.set_query('batch_no', 'locations', (frm, cdt, cdn) => { const row = locals[cdt][cdn]; return { @@ -48,6 +53,18 @@ frappe.ui.form.on('Pick List', { }, }; }); + + frm.set_query("serial_and_batch_bundle", "locations", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); }, set_item_locations:(frm, save) => { if (!(frm.doc.locations && frm.doc.locations.length)) { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 2c8e7a7da4..f1aae0edeb 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -103,6 +103,18 @@ frappe.ui.form.on('Stock Entry', { } }); + frm.set_query("serial_and_batch_bundle", "items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); + frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index d584858cd9..56cc21cb2c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -30,6 +30,18 @@ frappe.ui.form.on("Stock Reconciliation", { }; }); + frm.set_query("serial_and_batch_bundle", "items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); + if (frm.doc.company) { erpnext.queries.setup_queries(frm, "Warehouse", function() { return erpnext.queries.warehouse(frm.doc); diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 78572a66bc..bd1512b1e3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -77,6 +77,18 @@ frappe.ui.form.on('Subcontracting Receipt', { } }); + frm.set_query("serial_and_batch_bundle", "supplied_items", (doc, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.rm_item_code, + 'voucher_type': doc.doctype, + 'voucher_no': ["in", [doc.name, ""]], + 'is_cancelled': 0, + } + } + }); + let batch_no_field = frm.get_docfield('items', 'batch_no'); if (batch_no_field) { batch_no_field.get_route_options_for_new_doc = function(row) { From afaa85fbde07c1041166625ee5da493502998ff6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:54:27 +0530 Subject: [PATCH 52/63] fix(telephony): Check if setup_phone method exists We are just overriding Data control. This fails if other field type like "Small Text" has option set as "Phone" --- erpnext/public/js/telephony.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js index 1c3e314797..f4b0b18137 100644 --- a/erpnext/public/js/telephony.js +++ b/erpnext/public/js/telephony.js @@ -8,7 +8,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlDat Object.values(this.frm.fields_dict).forEach(function(field) { if (field.df.read_only === 1 && field.df.options === 'Phone' && field.disp_area.style[0] != 'display' && !field.has_icon) { - field.setup_phone(); + field.setup_phone && field.setup_phone(); field.has_icon = true; } }); From cd538e138a755f6df013390afb3d8268dcf97157 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 14 Jun 2023 20:28:28 +0530 Subject: [PATCH 53/63] fix: reference error while using exchange rate revaluation --- .../exchange_rate_revaluation/exchange_rate_revaluation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index 733a7616b2..f51b90d8f6 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -36,7 +36,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', { }, validate_rounding_loss: function(frm) { - allowance = frm.doc.rounding_loss_allowance; + let allowance = frm.doc.rounding_loss_allowance; if (!(allowance > 0 && allowance < 1)) { frappe.throw(__("Rounding Loss Allowance should be between 0 and 1")); } From f968f0f257be84490f57fb3a91c9027465b9e51a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 14 Jun 2023 23:22:22 +0530 Subject: [PATCH 54/63] fix: added validation for incorrect type --- erpnext/stock/deprecated_serial_batch.py | 3 +++ erpnext/stock/serial_batch_bundle.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 023773142d..2f1270e958 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -125,6 +125,9 @@ class DeprecatedBatchNoValuation: if batch_no not in self.non_batchwise_valuation_batches: continue + if not self.non_batchwise_balance_qty: + continue + self.batch_avg_rate[batch_no] = ( self.non_batchwise_balance_value / self.non_batchwise_balance_qty ) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index a75c3b0ffb..2c18f99acd 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -5,7 +5,7 @@ import frappe from frappe import _, bold from frappe.model.naming import make_autoname from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import cint, flt, now, nowtime, today +from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today from erpnext.stock.deprecated_serial_batch import ( DeprecatedBatchNoValuation, @@ -79,9 +79,24 @@ class SerialBatchBundle: self.set_serial_and_batch_bundle(sn_doc) def validate_actual_qty(self, sn_doc): + link = get_link_to_form("Serial and Batch Bundle", sn_doc.name) + + condition = { + "Inward": self.sle.actual_qty > 0, + "Outward": self.sle.actual_qty < 0, + }.get(sn_doc.type_of_transaction) + + if not condition: + correct_type = "Inward" + if sn_doc.type_of_transaction == "Inward": + correct_type = "Outward" + + msg = f"The type of transaction of Serial and Batch Bundle {link} is {bold(sn_doc.type_of_transaction)} but as per the Actual Qty {self.sle.actual_qty} for the item {bold(self.sle.item_code)} in the {self.sle.voucher_type} {self.sle.voucher_no} the type of transaction should be {bold(correct_type)}" + frappe.throw(_(msg), title=_("Incorrect Type of Transaction")) + precision = sn_doc.precision("total_qty") if flt(sn_doc.total_qty, precision) != flt(self.sle.actual_qty, precision): - msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {sn_doc.name} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}" + msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {link} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}" frappe.throw(_(msg)) def validate_item(self): From f8273f7db66ad1ba073474ddfdd7968ba6a08e9f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 15 Jun 2023 11:37:59 +0530 Subject: [PATCH 55/63] fix: typeerror on exchange rate revaluation --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 043fbdd5d6..5d239c91f7 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -186,7 +186,7 @@ class ExchangeRateRevaluation(Document): # round off balance based on currency precision # and consider debit-credit difference allowance currency_precision = get_currency_precision() - rounding_loss_allowance = rounding_loss_allowance or 0.05 + rounding_loss_allowance = float(rounding_loss_allowance) or 0.05 for acc in account_details: acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision) if abs(acc.balance_in_account_currency) <= rounding_loss_allowance: @@ -552,7 +552,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party): @frappe.whitelist() def get_account_details( - company, posting_date, account, party_type=None, party=None, rounding_loss_allowance=None + company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None ): if not (company and posting_date): frappe.throw(_("Company and Posting Date is mandatory")) From 6a1b0a2fab64b4192063a529bfef33e97aeea47e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 15 Jun 2023 11:39:22 +0530 Subject: [PATCH 56/63] fix: update `Stock Reconciliation` diff qty while reposting --- erpnext/stock/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index a668ab89dd..b3ed220680 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -962,6 +962,7 @@ class update_entries_after(object): item.current_amount = flt(item.current_qty) * flt(item.current_valuation_rate) item.amount = flt(item.qty) * flt(item.valuation_rate) + item.quantity_difference = item.qty - item.current_qty item.amount_difference = item.amount - item.current_amount else: sr.difference_amount = sum([item.amount_difference for item in sr.items]) From 1c2fe085b5c2f1d41e8fc069fe5b9d2c95facec8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jun 2023 12:54:43 +0530 Subject: [PATCH 57/63] fix: test case and removed outward field --- erpnext/public/js/controllers/transaction.js | 7 ++-- erpnext/public/js/utils.js | 32 +++++++++++++++++++ .../js/utils/serial_no_batch_selector.js | 18 ++++------- .../page/point_of_sale/pos_item_details.js | 1 - erpnext/selling/sales_common.js | 1 - .../stock/doctype/stock_entry/stock_entry.js | 2 +- .../stock_reconciliation.js | 4 +++ .../stock_reconciliation.py | 27 ++++++++++------ .../stock_reconciliation_item.json | 5 +-- 9 files changed, 66 insertions(+), 31 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 953a8936b0..933556774b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2340,14 +2340,11 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) { - item_row.outward = frm.doc.is_return ? 0 : 1; + item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward"; } else { - item_row.outward = frm.doc.is_return ? 1 : 0; + item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward"; } - item_row.type_of_transaction = (item_row.outward === 1 - ? "Outward":"Inward"); - new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { if (r) { let update_values = { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 58aa8d7da2..a859a671b0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -350,6 +350,38 @@ $.extend(erpnext.utils, { } }, + + pick_serial_and_batch_bundle(frm, cdt, cdn, type_of_transaction, warehouse_field) { + let item_row = frappe.get_doc(cdt, cdn); + item_row.type_of_transaction = type_of_transaction; + + frappe.db.get_value("Item", item_row.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + item_row.has_batch_no = r.message.has_batch_no; + item_row.has_serial_no = r.message.has_serial_no; + + frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() { + new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { + if (r) { + let update_values = { + "serial_and_batch_bundle": r.name, + "qty": Math.abs(r.total_qty) + } + + if (!warehouse_field) { + warehouse_field = "warehouse"; + } + + if (r.warehouse) { + update_values[warehouse_field] = r.warehouse; + } + + frappe.model.set_value(item_row.doctype, item_row.name, update_values); + } + }); + }); + }); + } }); erpnext.utils.select_alternate_items = function(opts) { diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index f9eec2a411..27a7968033 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -26,7 +26,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { title: this.item?.title || primary_label, fields: this.get_dialog_fields(), primary_action_label: primary_label, - primary_action: () => this.update_ledgers(), + primary_action: () => this.update_bundle_entries(), secondary_action_label: __('Edit Full Form'), secondary_action: () => this.edit_full_form(), }); @@ -36,7 +36,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_serial_no_filters() { - let warehouse = this.item?.outward ? + let warehouse = this.item?.type_of_transaction === "Outward" ? (this.item.warehouse || this.item.s_warehouse) : ""; return { @@ -121,7 +121,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { }); } - if (this.item?.outward) { + if (this.item?.type_of_transaction === "Outward") { fields = [...this.get_filter_fields(), ...fields]; } else { fields = [...fields, ...this.get_attach_field()]; @@ -267,7 +267,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { label: __('Batch No'), in_list_view: 1, get_query: () => { - if (!this.item.outward) { + if (this.item.type_of_transaction !== "Outward") { return { filters: { 'item': this.item.item_code, @@ -356,7 +356,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { this.dialog.fields_dict.entries.grid.refresh(); } - update_ledgers() { + update_bundle_entries() { let entries = this.dialog.get_values().entries; let warehouse = this.dialog.get_value('warehouse'); @@ -390,7 +390,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { _new.warehouse = this.get_warehouse(); _new.has_serial_no = this.item.has_serial_no; _new.has_batch_no = this.item.has_batch_no; - _new.type_of_transaction = this.get_type_of_transaction(); + _new.type_of_transaction = this.item.type_of_transaction; _new.company = this.frm.doc.company; _new.voucher_type = this.frm.doc.doctype; bundle_id = _new.name; @@ -401,15 +401,11 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } get_warehouse() { - return (this.item?.outward ? + return (this.item?.type_of_transaction === "Outward" ? (this.item.warehouse || this.item.s_warehouse) : (this.item.warehouse || this.item.t_warehouse)); } - get_type_of_transaction() { - return (this.item?.outward ? 'Outward' : 'Inward'); - } - render_data() { if (!this.frm.is_new() && this.bundle) { frappe.call({ 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 e6b2b3b5d5..b6e567c7cc 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -379,7 +379,6 @@ erpnext.PointOfSale.ItemDetails = class { frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", () => { let frm = this.events.get_frm(); let item_row = this.item_row; - item_row.outward = 1; item_row.type_of_transaction = "Outward"; new erpnext.SerialBatchPackageSelector(frm, item_row, (r) => { diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 98ad8a7cdb..87c0fae42a 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -317,7 +317,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran item.has_serial_no = r.message.has_serial_no; item.has_batch_no = r.message.has_batch_no; item.type_of_transaction = item.qty > 0 ? "Outward":"Inward"; - item.outward = item.qty > 0 ? 1 : 0; item.title = item.has_serial_no ? __("Select Serial No") : __("Select Batch No"); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index f7fb633d7e..3d497ac2eb 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1114,7 +1114,7 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { item.has_serial_no = r.message.has_serial_no; item.has_batch_no = r.message.has_batch_no; - item.outward = item.s_warehouse ? 1 : 0; + item.type_of_transaction = item.s_warehouse ? "Outward" : "Inward"; frappe.require(path, function() { new erpnext.SerialBatchPackageSelector( diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 56cc21cb2c..6afbf01e1e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -286,6 +286,10 @@ frappe.ui.form.on("Stock Reconciliation Item", { } }, + add_serial_batch_bundle(frm, cdt, cdn) { + erpnext.utils.pick_serial_and_batch_bundle(frm, cdt, cdn, "Inward"); + } + }); erpnext.stock.StockReconciliation = class StockReconciliation extends erpnext.stock.StockController { diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 4004c0012f..6ea27edc45 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -193,7 +193,13 @@ class StockReconciliation(StockController): def _changed(item): if item.current_serial_and_batch_bundle: - self.calculate_difference_amount(item, frappe._dict({})) + bundle_data = frappe.get_all( + "Serial and Batch Bundle", + filters={"name": item.current_serial_and_batch_bundle}, + fields=["total_qty as qty", "avg_rate as rate"], + )[0] + + self.calculate_difference_amount(item, bundle_data) return True item_dict = get_stock_balance_for( @@ -446,16 +452,17 @@ class StockReconciliation(StockController): sl_entries.append(args) - args = self.get_sle_for_items(row) - args.update( - { - "actual_qty": row.qty, - "incoming_rate": row.valuation_rate, - "serial_and_batch_bundle": row.serial_and_batch_bundle, - } - ) + if row.qty != 0: + args = self.get_sle_for_items(row) + args.update( + { + "actual_qty": row.qty, + "incoming_rate": row.valuation_rate, + "serial_and_batch_bundle": row.serial_and_batch_bundle, + } + ) - sl_entries.append(args) + sl_entries.append(args) def update_valuation_rate_for_serial_no(self): for d in self.items: diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 8738f4ae2b..62d6e4c8a2 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -103,7 +103,8 @@ { "fieldname": "serial_no", "fieldtype": "Long Text", - "label": "Serial No" + "label": "Serial No", + "read_only": 1 }, { "fieldname": "column_break_11", @@ -213,7 +214,7 @@ ], "istable": 1, "links": [], - "modified": "2023-05-27 17:35:31.026852", + "modified": "2023-06-15 11:45:55.808942", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", From 520268002fca6ac05766829eb7e27f97bbc29b5d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 15 Jun 2023 13:10:19 +0530 Subject: [PATCH 58/63] refactor!: remove hierarchy charts --- erpnext/public/build.json | 8 +- erpnext/public/js/hierarchy-chart.bundle.js | 3 - .../hierarchy_chart_desktop.js | 608 ------------------ .../hierarchy_chart/hierarchy_chart_mobile.js | 550 ---------------- erpnext/public/js/templates/node_card.html | 33 - erpnext/public/scss/erpnext.bundle.scss | 1 - erpnext/public/scss/hierarchy_chart.scss | 313 --------- erpnext/utilities/hierarchy_chart.py | 36 -- 8 files changed, 1 insertion(+), 1551 deletions(-) delete mode 100644 erpnext/public/js/hierarchy-chart.bundle.js delete mode 100644 erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js delete mode 100644 erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js delete mode 100644 erpnext/public/js/templates/node_card.html delete mode 100644 erpnext/public/scss/hierarchy_chart.scss delete mode 100644 erpnext/utilities/hierarchy_chart.py diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 3d38aca418..1bed541831 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -2,8 +2,7 @@ "css/erpnext.css": [ "public/less/erpnext.less", "public/scss/call_popup.scss", - "public/scss/point-of-sale.scss", - "public/scss/hierarchy_chart.scss" + "public/scss/point-of-sale.scss" ], "js/erpnext-web.min.js": [ "public/js/website_utils.js", @@ -37,7 +36,6 @@ "public/js/utils/dimension_tree_filter.js", "public/js/telephony.js", "public/js/templates/call_link.html", - "public/js/templates/node_card.html", "public/js/bulk_transaction_processing.js" ], "js/item-dashboard.min.js": [ @@ -62,10 +60,6 @@ "public/js/bank_reconciliation_tool/number_card.js", "public/js/bank_reconciliation_tool/dialog_manager.js" ], - "js/hierarchy-chart.min.js": [ - "public/js/hierarchy_chart/hierarchy_chart_desktop.js", - "public/js/hierarchy_chart/hierarchy_chart_mobile.js" - ], "js/e-commerce.min.js": [ "e_commerce/product_ui/views.js", "e_commerce/product_ui/grid.js", diff --git a/erpnext/public/js/hierarchy-chart.bundle.js b/erpnext/public/js/hierarchy-chart.bundle.js deleted file mode 100644 index 02703139dd..0000000000 --- a/erpnext/public/js/hierarchy-chart.bundle.js +++ /dev/null @@ -1,3 +0,0 @@ -import "./hierarchy_chart/hierarchy_chart_desktop.js"; -import "./hierarchy_chart/hierarchy_chart_mobile.js"; -import "./templates/node_card.html"; diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js deleted file mode 100644 index a585aa614f..0000000000 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ /dev/null @@ -1,608 +0,0 @@ -import html2canvas from 'html2canvas'; -erpnext.HierarchyChart = class { - /* Options: - - doctype - - wrapper: wrapper for the hierarchy view - - method: - - to get the data for each node - - this method should return id, name, title, image, and connections for each node - */ - constructor(doctype, wrapper, method) { - this.page = wrapper.page; - this.method = method; - this.doctype = doctype; - - this.setup_page_style(); - this.page.main.addClass('frappe-card'); - - this.nodes = {}; - this.setup_node_class(); - } - - setup_page_style() { - this.page.main.css({ - 'min-height': '300px', - 'max-height': '600px', - 'overflow': 'auto', - 'position': 'relative' - }); - } - - setup_node_class() { - let me = this; - this.Node = class { - constructor({ - id, parent, parent_id, image, name, title, expandable, connections, is_root // eslint-disable-line - }) { - // to setup values passed via constructor - $.extend(this, arguments[0]); - - this.expanded = 0; - - me.nodes[this.id] = this; - me.make_node_element(this); - - if (!me.all_nodes_expanded) { - me.setup_node_click_action(this); - } - - me.setup_edit_node_action(this); - } - }; - } - - make_node_element(node) { - let node_card = frappe.render_template('node_card', { - id: node.id, - name: node.name, - title: node.title, - image: node.image, - parent: node.parent_id, - connections: node.connections, - is_mobile: false - }); - - node.parent.append(node_card); - node.$link = $(`[id="${node.id}"]`); - } - - show() { - this.setup_actions(); - if ($(`[data-fieldname="company"]`).length) return; - let me = this; - - let company = this.page.add_field({ - fieldtype: 'Link', - options: 'Company', - fieldname: 'company', - placeholder: __('Select Company'), - default: frappe.defaults.get_default('company'), - only_select: true, - reqd: 1, - change: () => { - me.company = undefined; - $('#hierarchy-chart-wrapper').remove(); - - if (company.get_value()) { - me.company = company.get_value(); - - // svg for connectors - me.make_svg_markers(); - me.setup_hierarchy(); - me.render_root_nodes(); - me.all_nodes_expanded = false; - } else { - frappe.throw(__('Please select a company first.')); - } - } - }); - - company.refresh(); - $(`[data-fieldname="company"]`).trigger('change'); - $(`[data-fieldname="company"] .link-field`).css('z-index', 2); - } - - setup_actions() { - let me = this; - this.page.clear_inner_toolbar(); - this.page.add_inner_button(__('Export'), function() { - me.export_chart(); - }); - - this.page.add_inner_button(__('Expand All'), function() { - me.load_children(me.root_node, true); - me.all_nodes_expanded = true; - - me.page.remove_inner_button(__('Expand All')); - me.page.add_inner_button(__('Collapse All'), function() { - me.setup_hierarchy(); - me.render_root_nodes(); - me.all_nodes_expanded = false; - - me.page.remove_inner_button(__('Collapse All')); - me.setup_actions(); - }); - }); - } - - export_chart() { - frappe.dom.freeze(__('Exporting...')); - this.page.main.css({ - 'min-height': '', - 'max-height': '', - 'overflow': 'visible', - 'position': 'fixed', - 'left': '0', - 'top': '0' - }); - - $('.node-card').addClass('exported'); - - html2canvas(document.querySelector('#hierarchy-chart-wrapper'), { - scrollY: -window.scrollY, - scrollX: 0 - }).then(function(canvas) { - // Export the canvas to its data URI representation - let dataURL = canvas.toDataURL('image/png'); - - // download the image - let a = document.createElement('a'); - a.href = dataURL; - a.download = 'hierarchy_chart'; - a.click(); - }).finally(() => { - frappe.dom.unfreeze(); - }); - - this.setup_page_style(); - $('.node-card').removeClass('exported'); - } - - setup_hierarchy() { - if (this.$hierarchy) - this.$hierarchy.remove(); - - $(`#connectors`).empty(); - - // setup hierarchy - this.$hierarchy = $( - `
      -
    • -
        -
      • -
      `); - - this.page.main - .find('#hierarchy-chart') - .empty() - .append(this.$hierarchy); - - this.nodes = {}; - } - - make_svg_markers() { - $('#hierarchy-chart-wrapper').remove(); - - this.page.main.append(` -
      - - - - - - - - - - - - - - - - - - - -
      -
      -
      `); - } - - render_root_nodes(expanded_view=false) { - let me = this; - - return frappe.call({ - method: me.method, - args: { - company: me.company - } - }).then(r => { - if (r.message.length) { - let expand_node = undefined; - let node = undefined; - - $.each(r.message, (_i, data) => { - if ($(`[id="${data.id}"]`).length) - return; - - node = new me.Node({ - id: data.id, - parent: $('
    • ').appendTo(me.$hierarchy.find('.node-children')), - parent_id: undefined, - image: data.image, - name: data.name, - title: data.title, - expandable: true, - connections: data.connections, - is_root: true - }); - - if (!expand_node && data.connections) - expand_node = node; - }); - - me.root_node = expand_node; - if (!expanded_view) { - me.expand_node(expand_node); - } - } - }); - } - - expand_node(node) { - const is_sibling = this.selected_node && this.selected_node.parent_id === node.parent_id; - this.set_selected_node(node); - this.show_active_path(node); - this.collapse_previous_level_nodes(node); - - // since the previous node collapses, all connections to that node need to be rebuilt - // if a sibling node is clicked, connections don't need to be rebuilt - if (!is_sibling) { - // rebuild outgoing connections - this.refresh_connectors(node.parent_id); - - // rebuild incoming connections - let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); - this.refresh_connectors(grandparent); - } - - if (node.expandable && !node.expanded) { - return this.load_children(node); - } - } - - collapse_node() { - if (this.selected_node.expandable) { - this.selected_node.$children.hide(); - $(`path[data-parent="${this.selected_node.id}"]`).hide(); - this.selected_node.expanded = false; - } - } - - show_active_path(node) { - // mark node parent on active path - $(`[id="${node.parent_id}"]`).addClass('active-path'); - } - - load_children(node, deep=false) { - if (!deep) { - frappe.run_serially([ - () => this.get_child_nodes(node.id), - (child_nodes) => this.render_child_nodes(node, child_nodes) - ]); - } else { - frappe.run_serially([ - () => frappe.dom.freeze(), - () => this.setup_hierarchy(), - () => this.render_root_nodes(true), - () => this.get_all_nodes(), - (data_list) => this.render_children_of_all_nodes(data_list), - () => frappe.dom.unfreeze() - ]); - } - } - - get_child_nodes(node_id) { - let me = this; - return new Promise(resolve => { - frappe.call({ - method: me.method, - args: { - parent: node_id, - company: me.company - } - }).then(r => resolve(r.message)); - }); - } - - render_child_nodes(node, child_nodes) { - const last_level = this.$hierarchy.find('.level:last').index(); - const current_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); - - if (last_level === current_level) { - this.$hierarchy.append(` -
    • - `); - } - - if (!node.$children) { - node.$children = $('
        ') - .hide() - .appendTo(this.$hierarchy.find('.level:last')); - - node.$children.empty(); - - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - if (!$(`[id="${data.id}"]`).length) { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); - } - }); - } - } - - node.$children.show(); - $(`path[data-parent="${node.id}"]`).show(); - node.expanded = true; - } - - get_all_nodes() { - let me = this; - return new Promise(resolve => { - frappe.call({ - method: 'erpnext.utilities.hierarchy_chart.get_all_nodes', - args: { - method: me.method, - company: me.company - }, - callback: (r) => { - resolve(r.message); - } - }); - }); - } - - render_children_of_all_nodes(data_list) { - let entry = undefined; - let node = undefined; - - while (data_list.length) { - // to avoid overlapping connectors - entry = data_list.shift(); - node = this.nodes[entry.parent]; - if (node) { - this.render_child_nodes_for_expanded_view(node, entry.data); - } else if (data_list.length) { - data_list.push(entry); - } - } - } - - render_child_nodes_for_expanded_view(node, child_nodes) { - node.$children = $('
          '); - - const last_level = this.$hierarchy.find('.level:last').index(); - const node_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); - - if (last_level === node_level) { - this.$hierarchy.append(` -
        • - `); - node.$children.appendTo(this.$hierarchy.find('.level:last')); - } else { - node.$children.appendTo(this.$hierarchy.find('.level:eq(' + (node_level + 1) + ')')); - } - - node.$children.hide().empty(); - - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); - }); - } - - node.$children.show(); - $(`path[data-parent="${node.id}"]`).show(); - node.expanded = true; - } - - add_node(node, data) { - return new this.Node({ - id: data.id, - parent: $('
        • ').appendTo(node.$children), - parent_id: node.id, - image: data.image, - name: data.name, - title: data.title, - expandable: data.expandable, - connections: data.connections, - children: undefined - }); - } - - add_connector(parent_id, child_id) { - // using pure javascript for better performance - const parent_node = document.getElementById(`${parent_id}`); - const child_node = document.getElementById(`${child_id}`); - - let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - - // we need to connect right side of the parent to the left side of the child node - const pos_parent_right = { - x: parent_node.offsetLeft + parent_node.offsetWidth, - y: parent_node.offsetTop + parent_node.offsetHeight / 2 - }; - const pos_child_left = { - x: child_node.offsetLeft - 5, - y: child_node.offsetTop + child_node.offsetHeight / 2 - }; - - const connector = this.get_connector(pos_parent_right, pos_child_left); - - path.setAttribute('d', connector); - this.set_path_attributes(path, parent_id, child_id); - - document.getElementById('connectors').appendChild(path); - } - - get_connector(pos_parent_right, pos_child_left) { - if (pos_parent_right.y === pos_child_left.y) { - // don't add arcs if it's a straight line - return "M" + - (pos_parent_right.x) + "," + (pos_parent_right.y) + " " + - "L"+ - (pos_child_left.x) + "," + (pos_child_left.y); - } else { - let arc_1 = ""; - let arc_2 = ""; - let offset = 0; - - if (pos_parent_right.y > pos_child_left.y) { - // if child is above parent on Y axis 1st arc is anticlocwise - // second arc is clockwise - arc_1 = "a10,10 1 0 0 10,-10 "; - arc_2 = "a10,10 0 0 1 10,-10 "; - offset = 10; - } else { - // if child is below parent on Y axis 1st arc is clockwise - // second arc is anticlockwise - arc_1 = "a10,10 0 0 1 10,10 "; - arc_2 = "a10,10 1 0 0 10,10 "; - offset = -10; - } - - return "M" + (pos_parent_right.x) + "," + (pos_parent_right.y) + " " + - "L" + - (pos_parent_right.x + 40) + "," + (pos_parent_right.y) + " " + - arc_1 + - "L" + - (pos_parent_right.x + 50) + "," + (pos_child_left.y + offset) + " " + - arc_2 + - "L"+ - (pos_child_left.x) + "," + (pos_child_left.y); - } - } - - set_path_attributes(path, parent_id, child_id) { - path.setAttribute("data-parent", parent_id); - path.setAttribute("data-child", child_id); - const parent = $(`[id="${parent_id}"]`); - - if (parent.hasClass('active')) { - path.setAttribute("class", "active-connector"); - path.setAttribute("marker-start", "url(#arrowstart-active)"); - path.setAttribute("marker-end", "url(#arrowhead-active)"); - } else { - path.setAttribute("class", "collapsed-connector"); - path.setAttribute("marker-start", "url(#arrowstart-collapsed)"); - path.setAttribute("marker-end", "url(#arrowhead-collapsed)"); - } - } - - set_selected_node(node) { - // remove active class from the current node - if (this.selected_node) - this.selected_node.$link.removeClass('active'); - - // add active class to the newly selected node - this.selected_node = node; - node.$link.addClass('active'); - } - - collapse_previous_level_nodes(node) { - let node_parent = $(`[id="${node.parent_id}"]`); - let previous_level_nodes = node_parent.parent().parent().children('li'); - let node_card = undefined; - - previous_level_nodes.each(function() { - node_card = $(this).find('.node-card'); - - if (!node_card.hasClass('active-path')) { - node_card.addClass('collapsed'); - } - }); - } - - refresh_connectors(node_parent) { - if (!node_parent) return; - - $(`path[data-parent="${node_parent}"]`).remove(); - - frappe.run_serially([ - () => this.get_child_nodes(node_parent), - (child_nodes) => { - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - this.add_connector(node_parent, data.id); - }); - } - } - ]); - } - - setup_node_click_action(node) { - let me = this; - let node_element = $(`[id="${node.id}"]`); - - node_element.click(function() { - const is_sibling = me.selected_node.parent_id === node.parent_id; - - if (is_sibling) { - me.collapse_node(); - } else if (node_element.is(':visible') - && (node_element.hasClass('collapsed') || node_element.hasClass('active-path'))) { - me.remove_levels_after_node(node); - me.remove_orphaned_connectors(); - } - - me.expand_node(node); - }); - } - - setup_edit_node_action(node) { - let node_element = $(`[id="${node.id}"]`); - let me = this; - - node_element.find('.btn-edit-node').click(function() { - frappe.set_route('Form', me.doctype, node.id); - }); - } - - remove_levels_after_node(node) { - let level = $(`[id="${node.id}"]`).parent().parent().parent().index(); - - level = $('.hierarchy > li:eq('+ level + ')'); - level.nextAll('li').remove(); - - let nodes = level.find('.node-card'); - let node_object = undefined; - - $.each(nodes, (_i, element) => { - node_object = this.nodes[element.id]; - node_object.expanded = 0; - node_object.$children = undefined; - }); - - nodes.removeClass('collapsed active-path'); - } - - remove_orphaned_connectors() { - let paths = $('#connectors > path'); - $.each(paths, (_i, path) => { - const parent = $(path).data('parent'); - const child = $(path).data('child'); - - if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) - return; - - $(path).remove(); - }); - } -}; diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js deleted file mode 100644 index 52236e7df9..0000000000 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js +++ /dev/null @@ -1,550 +0,0 @@ -erpnext.HierarchyChartMobile = class { - /* Options: - - doctype - - wrapper: wrapper for the hierarchy view - - method: - - to get the data for each node - - this method should return id, name, title, image, and connections for each node - */ - constructor(doctype, wrapper, method) { - this.page = wrapper.page; - this.method = method; - this.doctype = doctype; - - this.page.main.css({ - 'min-height': '300px', - 'max-height': '600px', - 'overflow': 'auto', - 'position': 'relative' - }); - this.page.main.addClass('frappe-card'); - - this.nodes = {}; - this.setup_node_class(); - } - - setup_node_class() { - let me = this; - this.Node = class { - constructor({ - id, parent, parent_id, image, name, title, expandable, connections, is_root // eslint-disable-line - }) { - // to setup values passed via constructor - $.extend(this, arguments[0]); - - this.expanded = 0; - - me.nodes[this.id] = this; - me.make_node_element(this); - me.setup_node_click_action(this); - me.setup_edit_node_action(this); - } - }; - } - - make_node_element(node) { - let node_card = frappe.render_template('node_card', { - id: node.id, - name: node.name, - title: node.title, - image: node.image, - parent: node.parent_id, - connections: node.connections, - is_mobile: true - }); - - node.parent.append(node_card); - node.$link = $(`[id="${node.id}"]`); - node.$link.addClass('mobile-node'); - } - - show() { - let me = this; - if ($(`[data-fieldname="company"]`).length) return; - - let company = this.page.add_field({ - fieldtype: 'Link', - options: 'Company', - fieldname: 'company', - placeholder: __('Select Company'), - default: frappe.defaults.get_default('company'), - only_select: true, - reqd: 1, - change: () => { - me.company = undefined; - - if (company.get_value() && me.company != company.get_value()) { - me.company = company.get_value(); - - // svg for connectors - me.make_svg_markers(); - - if (me.$sibling_group) - me.$sibling_group.remove(); - - // setup sibling group wrapper - me.$sibling_group = $(`
          `); - me.page.main.append(me.$sibling_group); - - me.setup_hierarchy(); - me.render_root_nodes(); - } - } - }); - - company.refresh(); - $(`[data-fieldname="company"]`).trigger('change'); - } - - make_svg_markers() { - $('#arrows').remove(); - - this.page.main.prepend(` - - - - - - - - - - - - - - - - - - - `); - } - - setup_hierarchy() { - $(`#connectors`).empty(); - if (this.$hierarchy) - this.$hierarchy.remove(); - - if (this.$sibling_group) - this.$sibling_group.empty(); - - this.$hierarchy = $( - `
            -
          • -
          `); - - this.page.main.append(this.$hierarchy); - } - - render_root_nodes() { - let me = this; - - frappe.call({ - method: me.method, - args: { - company: me.company - }, - }).then(r => { - if (r.message.length) { - let root_level = me.$hierarchy.find('.root-level'); - root_level.empty(); - - $.each(r.message, (_i, data) => { - return new me.Node({ - id: data.id, - parent: root_level, - parent_id: undefined, - image: data.image, - name: data.name, - title: data.title, - expandable: true, - connections: data.connections, - is_root: true - }); - }); - } - }); - } - - expand_node(node) { - const is_same_node = (this.selected_node && this.selected_node.id === node.id); - this.set_selected_node(node); - this.show_active_path(node); - - if (this.$sibling_group) { - const sibling_parent = this.$sibling_group.find('.node-group').attr('data-parent'); - if (node.parent_id !== undefined && node.parent_id != sibling_parent) - this.$sibling_group.empty(); - } - - if (!is_same_node) { - // since the previous/parent node collapses, all connections to that node need to be rebuilt - // rebuild outgoing connections of parent - this.refresh_connectors(node.parent_id, node.id); - - // rebuild incoming connections of parent - let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); - this.refresh_connectors(grandparent, node.parent_id); - } - - if (node.expandable && !node.expanded) { - return this.load_children(node); - } - } - - collapse_node() { - let node = this.selected_node; - if (node.expandable && node.$children) { - node.$children.hide(); - node.expanded = 0; - - // add a collapsed level to show the collapsed parent - // and a button beside it to move to that level - let node_parent = node.$link.parent(); - node_parent.prepend( - `
          ` - ); - - node_parent - .find('.collapsed-level') - .append(node.$link); - - frappe.run_serially([ - () => this.get_child_nodes(node.parent_id, node.id), - (child_nodes) => this.get_node_group(child_nodes, node.parent_id), - (node_group) => node_parent.find('.collapsed-level').append(node_group), - () => this.setup_node_group_action() - ]); - } - } - - show_active_path(node) { - // mark node parent on active path - $(`[id="${node.parent_id}"]`).addClass('active-path'); - } - - load_children(node) { - frappe.run_serially([ - () => this.get_child_nodes(node.id), - (child_nodes) => this.render_child_nodes(node, child_nodes) - ]); - } - - get_child_nodes(node_id, exclude_node=null) { - let me = this; - return new Promise(resolve => { - frappe.call({ - method: me.method, - args: { - parent: node_id, - company: me.company, - exclude_node: exclude_node - } - }).then(r => resolve(r.message)); - }); - } - - render_child_nodes(node, child_nodes) { - if (!node.$children) { - node.$children = $('
            ') - .hide() - .appendTo(node.$link.parent()); - - node.$children.empty(); - - if (child_nodes) { - $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - $(`[id="${data.id}"]`).addClass('active-child'); - - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); - }); - } - } - - node.$children.show(); - node.expanded = 1; - } - - add_node(node, data) { - var $li = $('
          • '); - - return new this.Node({ - id: data.id, - parent: $li.appendTo(node.$children), - parent_id: node.id, - image: data.image, - name: data.name, - title: data.title, - expandable: data.expandable, - connections: data.connections, - children: undefined - }); - } - - add_connector(parent_id, child_id) { - const parent_node = document.getElementById(`${parent_id}`); - const child_node = document.getElementById(`${child_id}`); - - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - - let connector = undefined; - - if ($(`[id="${parent_id}"]`).hasClass('active')) { - connector = this.get_connector_for_active_node(parent_node, child_node); - } else if ($(`[id="${parent_id}"]`).hasClass('active-path')) { - connector = this.get_connector_for_collapsed_node(parent_node, child_node); - } - - path.setAttribute('d', connector); - this.set_path_attributes(path, parent_id, child_id); - - document.getElementById('connectors').appendChild(path); - } - - get_connector_for_active_node(parent_node, child_node) { - // we need to connect the bottom left of the parent to the left side of the child node - let pos_parent_bottom = { - x: parent_node.offsetLeft + 20, - y: parent_node.offsetTop + parent_node.offsetHeight - }; - let pos_child_left = { - x: child_node.offsetLeft - 5, - y: child_node.offsetTop + child_node.offsetHeight / 2 - }; - - let connector = - "M" + - (pos_parent_bottom.x) + "," + (pos_parent_bottom.y) + " " + - "L" + - (pos_parent_bottom.x) + "," + (pos_child_left.y - 10) + " " + - "a10,10 1 0 0 10,10 " + - "L" + - (pos_child_left.x) + "," + (pos_child_left.y); - - return connector; - } - - get_connector_for_collapsed_node(parent_node, child_node) { - // we need to connect the bottom left of the parent to the top left of the child node - let pos_parent_bottom = { - x: parent_node.offsetLeft + 20, - y: parent_node.offsetTop + parent_node.offsetHeight - }; - let pos_child_top = { - x: child_node.offsetLeft + 20, - y: child_node.offsetTop - }; - - let connector = - "M" + - (pos_parent_bottom.x) + "," + (pos_parent_bottom.y) + " " + - "L" + - (pos_child_top.x) + "," + (pos_child_top.y); - - return connector; - } - - set_path_attributes(path, parent_id, child_id) { - path.setAttribute("data-parent", parent_id); - path.setAttribute("data-child", child_id); - const parent = $(`[id="${parent_id}"]`); - - if (parent.hasClass('active')) { - path.setAttribute("class", "active-connector"); - path.setAttribute("marker-start", "url(#arrowstart-active)"); - path.setAttribute("marker-end", "url(#arrowhead-active)"); - } else if (parent.hasClass('active-path')) { - path.setAttribute("class", "collapsed-connector"); - } - } - - set_selected_node(node) { - // remove .active class from the current node - if (this.selected_node) - this.selected_node.$link.removeClass('active'); - - // add active class to the newly selected node - this.selected_node = node; - node.$link.addClass('active'); - } - - setup_node_click_action(node) { - let me = this; - let node_element = $(`[id="${node.id}"]`); - - node_element.click(function() { - let el = undefined; - - if (node.is_root) { - el = $(this).detach(); - me.$hierarchy.empty(); - $(`#connectors`).empty(); - me.add_node_to_hierarchy(el, node); - } else if (node_element.is(':visible') && node_element.hasClass('active-path')) { - me.remove_levels_after_node(node); - me.remove_orphaned_connectors(); - } else { - el = $(this).detach(); - me.add_node_to_hierarchy(el, node); - me.collapse_node(); - } - - me.expand_node(node); - }); - } - - setup_edit_node_action(node) { - let node_element = $(`[id="${node.id}"]`); - let me = this; - - node_element.find('.btn-edit-node').click(function() { - frappe.set_route('Form', me.doctype, node.id); - }); - } - - setup_node_group_action() { - let me = this; - - $('.node-group').on('click', function() { - let parent = $(this).attr('data-parent'); - if (parent === 'undefined') { - me.setup_hierarchy(); - me.render_root_nodes(); - } else { - me.expand_sibling_group_node(parent); - } - }); - } - - add_node_to_hierarchy(node_element, node) { - this.$hierarchy.append(`
          • `); - node_element.removeClass('active-child active-path'); - this.$hierarchy.find('.level:last').append(node_element); - - let node_object = this.nodes[node.id]; - node_object.expanded = 0; - node_object.$children = undefined; - this.nodes[node.id] = node_object; - } - - get_node_group(nodes, parent, collapsed=true) { - let limit = 2; - const display_nodes = nodes.slice(0, limit); - const extra_nodes = nodes.slice(limit); - - let html = display_nodes.map(node => - this.get_avatar(node) - ).join(''); - - if (extra_nodes.length === 1) { - let node = extra_nodes[0]; - html += this.get_avatar(node); - } else if (extra_nodes.length > 1) { - html = ` - ${html} - -
            - +${extra_nodes.length} -
            -
            - `; - } - - if (html) { - const $node_group = - $(`
            -
            - ${html} -
            -
            `); - - if (collapsed) - $node_group.addClass('collapsed'); - - return $node_group; - } - - return null; - } - - get_avatar(node) { - return ` - - `; - } - - expand_sibling_group_node(parent) { - let node_object = this.nodes[parent]; - let node = node_object.$link; - - node.removeClass('active-child active-path'); - node_object.expanded = 0; - node_object.$children = undefined; - this.nodes[node.id] = node_object; - - // show parent's siblings and expand parent node - frappe.run_serially([ - () => this.get_child_nodes(node_object.parent_id, node_object.id), - (child_nodes) => this.get_node_group(child_nodes, node_object.parent_id, false), - (node_group) => { - if (node_group) - this.$sibling_group.empty().append(node_group); - }, - () => this.setup_node_group_action(), - () => this.reattach_and_expand_node(node, node_object) - ]); - } - - reattach_and_expand_node(node, node_object) { - var el = node.detach(); - - this.$hierarchy.empty().append(` -
          • - `); - this.$hierarchy.find('.level').append(el); - $(`#connectors`).empty(); - this.expand_node(node_object); - } - - remove_levels_after_node(node) { - let level = $(`[id="${node.id}"]`).parent().parent().index(); - - level = $('.hierarchy-mobile > li:eq('+ level + ')'); - level.nextAll('li').remove(); - - let node_object = this.nodes[node.id]; - let current_node = level.find(`[id="${node.id}"]`).detach(); - - current_node.removeClass('active-child active-path'); - - node_object.expanded = 0; - node_object.$children = undefined; - - level.empty().append(current_node); - } - - remove_orphaned_connectors() { - let paths = $('#connectors > path'); - $.each(paths, (_i, path) => { - const parent = $(path).data('parent'); - const child = $(path).data('child'); - - if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) - return; - - $(path).remove(); - }); - } - - refresh_connectors(node_parent, node_id) { - if (!node_parent) return; - - $(`path[data-parent="${node_parent}"]`).remove(); - this.add_connector(node_parent, node_id); - } -}; diff --git a/erpnext/public/js/templates/node_card.html b/erpnext/public/js/templates/node_card.html deleted file mode 100644 index 4cb6ee03c0..0000000000 --- a/erpnext/public/js/templates/node_card.html +++ /dev/null @@ -1,33 +0,0 @@ -
            -
            -
            - - - -
            -
            -
            - {{ name }} -
            - {{ frappe.utils.icon("edit", "xs") }} - {{ __("Edit") }} -
            -
            -
            -
            {{ title }}
            - - {% if is_mobile %} -
            - · {{ connections }} -
            - {% else %} - {% if connections == 1 %} -
            · {{ connections }} Connection
            - {% else %} -
            · {{ connections }} Connections
            - {% endif %} - {% endif %} -
            -
            -
            -
            diff --git a/erpnext/public/scss/erpnext.bundle.scss b/erpnext/public/scss/erpnext.bundle.scss index b68ddf52b2..d3313c7cee 100644 --- a/erpnext/public/scss/erpnext.bundle.scss +++ b/erpnext/public/scss/erpnext.bundle.scss @@ -1,4 +1,3 @@ @import "./erpnext"; @import "./call_popup"; @import "./point-of-sale"; -@import "./hierarchy_chart"; diff --git a/erpnext/public/scss/hierarchy_chart.scss b/erpnext/public/scss/hierarchy_chart.scss deleted file mode 100644 index 57d5e8414a..0000000000 --- a/erpnext/public/scss/hierarchy_chart.scss +++ /dev/null @@ -1,313 +0,0 @@ -.node-card { - background: white; - stroke: 1px solid var(--gray-200); - box-shadow: var(--shadow-base); - border-radius: 0.5rem; - padding: 0.75rem; - margin-left: 3rem; - width: 18rem; - overflow: hidden; - - .btn-edit-node { - display: none; - } - - .edit-chart-node { - display: none; - } - - .node-edit-icon { - display: none; - } -} - -.node-card.exported { - box-shadow: none -} - -.node-image { - width: 3.0rem; - height: 3.0rem; -} - -.node-name { - font-size: 1rem; - line-height: 1.72; -} - -.node-title { - font-size: 0.75rem; - line-height: 1.35; -} - -.node-info { - width: 12.7rem; -} - -.node-connections { - font-size: 0.75rem; - line-height: 1.35; -} - -.node-card.active { - background: var(--blue-50); - border: 1px solid var(--blue-500); - box-shadow: var(--shadow-md); - border-radius: 0.5rem; - padding: 0.75rem; - width: 18rem; - - .btn-edit-node { - display: flex; - background: var(--blue-100); - color: var(--blue-500); - padding: .25rem .5rem; - font-size: .75rem; - justify-content: center; - box-shadow: var(--shadow-sm); - margin-left: auto; - } - - .edit-chart-node { - display: block; - margin-right: 0.25rem; - } - - .node-edit-icon { - display: block; - } - - .node-edit-icon > .icon{ - stroke: var(--blue-500); - } - - .node-name { - align-items: center; - justify-content: space-between; - margin-bottom: 2px; - width: 12.2rem; - } -} - -.node-card.active-path { - background: var(--blue-100); - border: 1px solid var(--blue-300); - box-shadow: var(--shadow-sm); - border-radius: 0.5rem; - padding: 0.75rem; - width: 15rem; - height: 3.0rem; - - .btn-edit-node { - display: none !important; - } - - .edit-chart-node { - display: none; - } - - .node-edit-icon { - display: none; - } - - .node-info { - display: none; - } - - .node-title { - display: none; - } - - .node-connections { - display: none; - } - - .node-name { - font-size: 0.85rem; - line-height: 1.35; - } - - .node-image { - width: 1.5rem; - height: 1.5rem; - } - - .node-meta { - align-items: baseline; - } -} - -.node-card.collapsed { - background: white; - stroke: 1px solid var(--gray-200); - box-shadow: var(--shadow-sm); - border-radius: 0.5rem; - padding: 0.75rem; - width: 15rem; - height: 3.0rem; - - .btn-edit-node { - display: none !important; - } - - .edit-chart-node { - display: none; - } - - .node-edit-icon { - display: none; - } - - .node-info { - display: none; - } - - .node-title { - display: none; - } - - .node-connections { - display: none; - } - - .node-name { - font-size: 0.85rem; - line-height: 1.35; - } - - .node-image { - width: 1.5rem; - height: 1.5rem; - } - - .node-meta { - align-items: baseline; - } -} - -// horizontal hierarchy tree view -#hierarchy-chart-wrapper { - padding-top: 30px; - - #arrows { - margin-top: -80px; - } -} - -.hierarchy { - display: flex; -} - -.hierarchy li { - list-style-type: none; -} - -.child-node { - margin: 0px 0px 16px 0px; -} - -.hierarchy, .hierarchy-mobile { - .level { - margin-right: 8px; - align-items: flex-start; - flex-direction: column; - } -} - -#arrows { - position: absolute; - overflow: visible; -} - -.active-connector { - stroke: var(--blue-500); -} - -.collapsed-connector { - stroke: var(--blue-300); -} - -// mobile - -.hierarchy-mobile { - display: flex; - flex-direction: column; - align-items: center; - padding-top: 10px; - padding-left: 0px; -} - -.hierarchy-mobile li { - list-style-type: none; - display: flex; - flex-direction: column; - align-items: flex-end; -} - -.mobile-node { - margin-left: 0; -} - -.mobile-node.active-path { - width: 12.25rem; -} - -.active-child { - width: 15.5rem; -} - -.mobile-node .node-connections { - max-width: 80px; -} - -.hierarchy-mobile .node-children { - margin-top: 16px; -} - -.root-level .node-card { - margin: 0 0 16px; -} - -// node group - -.collapsed-level { - margin-bottom: 16px; - width: 18rem; -} - -.node-group { - background: white; - border: 1px solid var(--gray-300); - box-shadow: var(--shadow-sm); - border-radius: 0.5rem; - padding: 0.75rem; - width: 18rem; - height: 3rem; - overflow: hidden; - align-items: center; -} - -.node-group .avatar-group { - margin-left: 0px; -} - -.node-group .avatar-extra-count { - background-color: var(--blue-100); - color: var(--blue-500); -} - -.node-group .avatar-frame { - width: 1.5rem; - height: 1.5rem; -} - -.node-group.collapsed { - width: 5rem; - margin-left: 12px; -} - -.sibling-group { - display: flex; - flex-direction: column; - align-items: center; -} diff --git a/erpnext/utilities/hierarchy_chart.py b/erpnext/utilities/hierarchy_chart.py deleted file mode 100644 index 4bf4353cdf..0000000000 --- a/erpnext/utilities/hierarchy_chart.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - - -import frappe -from frappe import _ - - -@frappe.whitelist() -def get_all_nodes(method, company): - """Recursively gets all data from nodes""" - method = frappe.get_attr(method) - - if method not in frappe.whitelisted: - frappe.throw(_("Not Permitted"), frappe.PermissionError) - - root_nodes = method(company=company) - result = [] - nodes_to_expand = [] - - for root in root_nodes: - data = method(root.id, company) - result.append(dict(parent=root.id, parent_name=root.name, data=data)) - nodes_to_expand.extend( - [{"id": d.get("id"), "name": d.get("name")} for d in data if d.get("expandable")] - ) - - while nodes_to_expand: - parent = nodes_to_expand.pop(0) - data = method(parent.get("id"), company) - result.append(dict(parent=parent.get("id"), parent_name=parent.get("name"), data=data)) - for d in data: - if d.get("expandable"): - nodes_to_expand.append({"id": d.get("id"), "name": d.get("name")}) - - return result From 8c374f57ed309a973b83cf8d1463888012a3ecc2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 15 Jun 2023 13:56:05 +0530 Subject: [PATCH 59/63] chore: remove html2canvas from dependencies - was used for hierarchy charts (org charts) --- .eslintrc | 1 - package.json | 1 - yarn.lock | 19 ------------------- 3 files changed, 21 deletions(-) diff --git a/.eslintrc b/.eslintrc index 276d6ff372..12fefa0968 100644 --- a/.eslintrc +++ b/.eslintrc @@ -154,7 +154,6 @@ "before": true, "beforeEach": true, "onScan": true, - "html2canvas": true, "extend_cscript": true, "localforage": true } diff --git a/package.json b/package.json index 6c11e9dddc..4e686f7ca7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ }, "devDependencies": {}, "dependencies": { - "html2canvas": "^1.1.4", "onscan.js": "^1.5.2" } } diff --git a/yarn.lock b/yarn.lock index 8e5d1bd1c1..fa1b1d673b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,25 +2,6 @@ # yarn lockfile v1 -base64-arraybuffer@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" - integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== - -css-line-break@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" - integrity sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA== - dependencies: - base64-arraybuffer "^0.2.0" - -html2canvas@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.1.4.tgz#53ae91cd26e9e9e623c56533cccb2e3f57c8124c" - integrity sha512-uHgQDwrXsRmFdnlOVFvHin9R7mdjjZvoBoXxicPR+NnucngkaLa5zIDW9fzMkiip0jSffyTyWedE8iVogYOeWg== - dependencies: - css-line-break "1.1.1" - onscan.js@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341" From 6e198188ff90ad7291a48f2fbfaac069eaee8381 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jun 2023 14:45:19 +0530 Subject: [PATCH 60/63] fix: incorrect gl entries for standalone debit note with update stock --- erpnext/controllers/buying_controller.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index ad6a49a029..cf26baefb5 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -26,6 +26,8 @@ class BuyingController(SubcontractingController): self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"] def validate(self): + self.set_rate_for_standalone_debit_note() + super(BuyingController, self).validate() if getattr(self, "supplier", None) and not self.supplier_name: self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name") @@ -100,6 +102,28 @@ class BuyingController(SubcontractingController): do_not_submit=True, ) + def set_rate_for_standalone_debit_note(self): + if self.get("is_return") and self.get("update_stock") and not self.return_against: + for row in self.items: + row.rate = get_incoming_rate( + { + "item_code": row.item_code, + "warehouse": row.warehouse, + "posting_date": self.get("posting_date"), + "posting_time": self.get("posting_time"), + "qty": row.qty, + "serial_and_batch_bundle": row.get("serial_and_batch_bundle"), + "company": self.company, + "voucher_type": self.doctype, + "voucher_no": self.name, + }, + raise_error_if_no_rate=False, + ) + + row.discount_percentage = 0.0 + row.discount_amount = 0.0 + row.margin_rate_or_amount = 0.0 + def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) From d176d86e2c9a96ec0f7d884b89a170b7dc8e020e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 15 Jun 2023 16:01:08 +0530 Subject: [PATCH 61/63] fix: `Process Loss Report` --- .../process_loss_report.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index ce8f4f35a3..c3dd9cf9b1 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -33,10 +33,9 @@ def get_data(filters: Filters) -> Data: wo.name, wo.status, wo.production_item, - wo.qty, wo.produced_qty, wo.process_loss_qty, - (wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"), + wo.qty.as_("qty_to_manufacture"), Sum(se.total_incoming_value).as_("total_fg_value"), Sum(se.total_outgoing_value).as_("total_rm_value"), ) @@ -44,6 +43,7 @@ def get_data(filters: Filters) -> Data: (wo.process_loss_qty > 0) & (wo.company == filters.company) & (se.docstatus == 1) + & (se.purpose == "Manufacture") & (se.posting_date.between(filters.from_date, filters.to_date)) ) .groupby(se.work_order) @@ -79,20 +79,30 @@ def get_columns() -> Columns: "width": "100", }, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"}, + { + "label": _("Qty To Manufacture"), + "fieldname": "qty_to_manufacture", + "fieldtype": "Float", + "width": "150", + }, { "label": _("Manufactured Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": "150", }, - {"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"}, { - "label": _("Actual Manufactured Qty"), - "fieldname": "actual_produced_qty", + "label": _("Process Loss Qty"), + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "width": "150", + }, + { + "label": _("Process Loss Value"), + "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150", }, - {"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"}, {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"}, { "label": _("Raw Material Value"), @@ -105,5 +115,5 @@ def get_columns() -> Columns: def update_data_with_total_pl_value(data: Data) -> None: for row in data: - value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"] + value_per_unit_fg = row["total_fg_value"] / row["qty_to_manufacture"] row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg From bb39a2cac7f714fe30e77ced870e15ec69620801 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 15 Jun 2023 17:12:59 +0530 Subject: [PATCH 62/63] fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (#35714) fix: on asset scrap, don't add gl entry for acc. depr. if no acc. depr. --- erpnext/assets/doctype/asset/depreciation.py | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index c64b917e18..bfef57e494 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -513,18 +513,22 @@ def get_gl_entries_on_asset_disposal( }, item=asset, ), - asset.get_gl_dict( - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - "posting_date": date, - }, - item=asset, - ), ] + if accumulated_depr_amount: + gl_entries.append( + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": date, + }, + item=asset, + ), + ) + profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: get_profit_gl_entries( From f9f662679fc7ebcbfb96198b931c449cecab6ec2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jun 2023 15:01:15 +0530 Subject: [PATCH 63/63] test: added test case --- .../purchase_invoice/test_purchase_invoice.py | 22 +++++++++++++------ erpnext/controllers/buying_controller.py | 2 ++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 42eb018078..45bddfc096 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -642,13 +642,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): gle_filters={"account": "Stock In Hand - TCP1"}, ) - # assert loss booked in COGS - self.assertGLEs( - return_pi, - [{"credit": 0, "debit": 200}], - gle_filters={"account": "Cost of Goods Sold - TCP1"}, - ) - def test_return_with_lcv(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( @@ -1671,6 +1664,21 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) + def test_gl_entries_for_standalone_debit_note(self): + make_purchase_invoice(qty=5, rate=500, update_stock=True) + + returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True) + + # override the rate with valuation rate + sle = frappe.get_all( + "Stock Ledger Entry", + fields=["stock_value_difference", "actual_qty"], + filters={"voucher_no": returned_inv.name}, + )[0] + + rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) + self.assertAlmostEqual(returned_inv.items[0].rate, rate) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index cf26baefb5..a3a1461c9a 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -105,6 +105,8 @@ class BuyingController(SubcontractingController): def set_rate_for_standalone_debit_note(self): if self.get("is_return") and self.get("update_stock") and not self.return_against: for row in self.items: + + # override the rate with valuation rate row.rate = get_incoming_rate( { "item_code": row.item_code,