From 5822b6d76f468a857cd2de597899d7dbed89d712 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 10 Mar 2021 21:21:47 +0530 Subject: [PATCH 01/92] fix(payroll): allow duplicate additional salaries --- .../payroll/doctype/additional_salary/additional_salary.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index f5af677fce..d9f3add0d8 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -9,17 +9,10 @@ from frappe import _, bold from frappe.utils import getdate, date_diff, comma_and, formatdate class AdditionalSalary(Document): - def on_submit(self): if self.ref_doctype == "Employee Advance" and self.ref_docname: frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) - def before_insert(self): - if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component, - "amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}): - - frappe.throw(_("Additional Salary Component Exists.")) - def validate(self): self.validate_dates() self.validate_salary_structure() From 514122bf6f24b60c51b180fc5feb86a1f96c3e0a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 11 Mar 2021 17:22:11 +0530 Subject: [PATCH 02/92] refactor(payroll): simplified logic for additional salary --- .../additional_salary/additional_salary.py | 53 +++---- .../doctype/salary_slip/salary_slip.py | 133 ++++++++++-------- 2 files changed, 91 insertions(+), 95 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index f5af677fce..029e11ff9b 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -89,10 +89,11 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days -@frappe.whitelist() -def get_additional_salary_component(employee, start_date, end_date, component_type): - additional_salaries = frappe.db.sql(""" - select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date +def get_additional_salaries(employee, start_date, end_date, component_type): + additional_salary_list = frappe.db.sql(""" + select name, salary_component as component, type, amount, + overwrite_salary_structure_amount as overwrite, + deduct_full_tax_on_selected_payroll_date from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 @@ -102,7 +103,7 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty from_date <= %(to_date)s and to_date >= %(to_date)s ) and type = %(component_type)s - order by salary_component, overwrite_salary_structure_amount DESC + order by salary_component, overwrite ASC """, { 'employee': employee, 'from_date': start_date, @@ -110,38 +111,18 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty 'component_type': "Earning" if component_type == "earnings" else "Deduction" }, as_dict=1) - existing_salary_components= [] - salary_components_details = {} - additional_salary_details = [] + additional_salaries = [] + components_to_overwrite = [] - overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1] + for d in additional_salary_list: + if d.overwrite: + if d.component in components_to_overwrite: + frappe.throw(_("Multiple Additional Salaries with overwrite " + "property exist for Salary Component {0} between {1} and {2}.").format( + frappe.bold(d.component), start_date, end_date), title=_("Error")) - component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type'] - for d in additional_salaries: + components_to_overwrite.append(d.component) - if d.salary_component not in existing_salary_components: - component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields) - struct_row = frappe._dict({'salary_component': d.salary_component}) - if component: - struct_row.update(component[0]) + additional_salaries.append(d) - struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date - struct_row['is_additional_component'] = 1 - - salary_components_details[d.salary_component] = struct_row - - - if overwrites_components.count(d.salary_component) > 1: - frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error")) - else: - additional_salary_details.append({ - 'name': d.name, - 'component': d.salary_component, - 'amount': d.amount, - 'type': d.type, - 'overwrite': d.overwrite_salary_structure_amount, - }) - - existing_salary_components.append(d.salary_component) - - return salary_components_details, additional_salary_details + return additional_salaries diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 595d6974fd..a04a635807 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -13,7 +13,7 @@ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_da from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase from frappe.utils.background_jobs import enqueue -from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salary_component +from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits @@ -524,7 +524,7 @@ class SalarySlip(TransactionBase): except NameError as err: frappe.throw(_("{0}
This error can be due to missing or deleted field.").format(err), - title=_("Name error")) + title=_("Name error")) except SyntaxError as err: frappe.throw(_("Syntax error in formula or condition: {0}").format(err)) except Exception as e: @@ -558,15 +558,16 @@ class SalarySlip(TransactionBase): self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") def add_additional_salary_components(self, component_type): - salary_components_details, additional_salary_details = get_additional_salary_component(self.employee, + additional_salaries = get_additional_salaries(self.employee, self.start_date, self.end_date, component_type) - if salary_components_details and additional_salary_details: - for additional_salary in additional_salary_details: - additional_salary =frappe._dict(additional_salary) - amount = additional_salary.amount - overwrite = additional_salary.overwrite - self.update_component_row(frappe._dict(salary_components_details[additional_salary.component]), amount, - component_type, overwrite=overwrite, additional_salary=additional_salary.name) + + for additional_salary in additional_salaries: + self.update_component_row( + get_salary_component_data(additional_salary.component), + additional_salary.amount, + component_type, + additional_salary + ) def add_tax_components(self, payroll_period): # Calculate variable_based_on_taxable_salary after all components updated in salary slip @@ -583,47 +584,59 @@ class SalarySlip(TransactionBase): for d in tax_components: tax_amount = self.calculate_variable_based_on_taxable_salary(d, payroll_period) - tax_row = self.get_salary_slip_row(d) + tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, struct_row, amount, key, overwrite=1, additional_salary = ''): + def update_component_row(self, component_data, amount, component_type, additional_salary=None): component_row = None - for d in self.get(key): - if d.salary_component == struct_row.salary_component: + for d in self.get(component_type): + if d.salary_component != component_data.salary_component: + continue + + if ( + not d.additional_salary + and (not additional_salary or additional_salary.overwrite) + or additional_salary + and additional_salary.name == d.additional_salary + ): component_row = d + break - if not component_row or (struct_row.get("is_additional_component") and not overwrite): - if amount: - self.append(key, { - 'amount': amount, - 'default_amount': amount if not struct_row.get("is_additional_component") else 0, - 'depends_on_payment_days' : struct_row.depends_on_payment_days, - 'salary_component' : struct_row.salary_component, - 'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"), - 'additional_salary': additional_salary, - 'do_not_include_in_total' : struct_row.do_not_include_in_total, - 'is_tax_applicable': struct_row.is_tax_applicable, - 'is_flexible_benefit': struct_row.is_flexible_benefit, - 'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary, - 'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date, - 'additional_amount': amount if struct_row.get("is_additional_component") else 0, - 'exempted_from_income_tax': struct_row.exempted_from_income_tax - }) + if additional_salary and additional_salary.overwrite: + # Additional Salary with overwrite checked, remove default rows of same component + self.set(component_type, [ + d for d in self.get(component_type) + if d.salary_component != component_data.salary_component + or d.additional_salary and additional_salary.name != d.additional_salary + or d == component_row + ]) + + if not component_row: + if not amount: + return + + component_row = self.append(component_type) + for attr in ( + 'depends_on_payment_days', 'salary_component', 'abbr' + 'do_not_include_in_total', 'is_tax_applicable', + 'is_flexible_benefit', 'variable_based_on_taxable_salary', + 'exempted_from_income_tax' + ): + component_row.set(attr, component_data.get(attr)) + + if additional_salary: + component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name + component_row.deduct_full_tax_on_selected_payroll_date = \ + additional_salary.deduct_full_tax_on_selected_payroll_date else: - if struct_row.get("is_additional_component"): - if overwrite: - component_row.additional_amount = amount - component_row.get("default_amount", 0) - component_row.additional_salary = additional_salary - else: - component_row.additional_amount = amount + component_row.default_amount = amount + component_row.additional_amount = 0 + component_row.deduct_full_tax_on_selected_payroll_date = \ + component_data.deduct_full_tax_on_selected_payroll_date - if not overwrite and component_row.default_amount: - amount += component_row.default_amount - else: - component_row.default_amount = amount - - component_row.amount = amount - component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date + component_row.amount = amount def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): if not payroll_period: @@ -950,26 +963,13 @@ class SalarySlip(TransactionBase): return frappe.safe_eval(condition, self.whitelisted_globals, data) except NameError as err: frappe.throw(_("{0}
This error can be due to missing or deleted field.").format(err), - title=_("Name error")) + title=_("Name error")) except SyntaxError as err: frappe.throw(_("Syntax error in condition: {0}").format(err)) except Exception as e: frappe.throw(_("Error in formula or condition: {0}").format(e)) raise - def get_salary_slip_row(self, salary_component): - component = frappe.get_doc("Salary Component", salary_component) - # Data for update_component_row - struct_row = frappe._dict() - struct_row['depends_on_payment_days'] = component.depends_on_payment_days - struct_row['salary_component'] = component.name - struct_row['abbr'] = component.salary_component_abbr - struct_row['do_not_include_in_total'] = component.do_not_include_in_total - struct_row['is_tax_applicable'] = component.is_tax_applicable - struct_row['is_flexible_benefit'] = component.is_flexible_benefit - struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary - return struct_row - def get_component_totals(self, component_type, depends_on_payment_days=0): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -1032,7 +1032,6 @@ class SalarySlip(TransactionBase): self.total_loan_repayment += payment.total_payment def get_loan_details(self): - return frappe.get_all("Loan", fields=["name", "interest_income_account", "loan_account", "loan_type"], filters = { @@ -1263,3 +1262,19 @@ def unlink_ref_doc_from_salary_slip(ref_no): def generate_password_for_pdf(policy_template, employee): employee = frappe.get_doc("Employee", employee) return policy_template.format(**employee.as_dict()) + +def get_salary_component_data(component): + return frappe.get_value( + "Salary Component", + component, + [ + "name as salary_component", + "depends_on_payment_days", + "salary_component_abbr as abbr", + "do_not_include_in_total", + "is_tax_applicable", + "is_flexible_benefit", + "variable_based_on_taxable_salary", + ], + as_dict=1, + ) From 6727792aaf206726b6e71ee6913da291abcd57e6 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 11 Mar 2021 18:12:01 +0530 Subject: [PATCH 03/92] test: fix syntax error --- erpnext/payroll/doctype/salary_slip/test_salary_slip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 7289933d99..143a306eb3 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -246,7 +246,7 @@ class TestSalarySlip(unittest.TestCase): make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR', payroll_period=payroll_period) - frappe.db.sql("""delete from `tabLoan""") + frappe.db.sql("delete from tabLoan") loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) loan.repay_from_salary = 1 loan.submit() From 6ef213e5c64ddb14683000259008a29ce768c5f5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 16 Mar 2021 11:59:54 +0530 Subject: [PATCH 04/92] fix(minor): patch add_state_code_for_ladakh --- .../v12_0/add_state_code_for_ladakh.py | 7 ++++--- ...fields_for_80g_certificate_and_donation.py | 19 ++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/patches/v12_0/add_state_code_for_ladakh.py b/erpnext/patches/v12_0/add_state_code_for_ladakh.py index d41101cc46..29a7b4bd60 100644 --- a/erpnext/patches/v12_0/add_state_code_for_ladakh.py +++ b/erpnext/patches/v12_0/add_state_code_for_ladakh.py @@ -11,6 +11,7 @@ def execute(): # Update options in gst_state custom fields for field in custom_fields: - gst_state_field = frappe.get_doc('Custom Field', field) - gst_state_field.options = '\n'.join(states) - gst_state_field.save() + if frappe.db.exists('Custom Field', field): + gst_state_field = frappe.get_doc('Custom Field', field) + gst_state_field.options = '\n'.join(states) + gst_state_field.save() diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py index aea53f8add..833c355d5f 100644 --- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py +++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py @@ -2,15 +2,12 @@ import frappe from erpnext.regional.india.setup import make_custom_fields def execute(): - company = frappe.get_all('Company', filters = {'country': 'India'}) - if not company: - return + if frappe.get_all('Company', filters = {'country': 'India'}): + make_custom_fields() - make_custom_fields() - - if not frappe.db.exists('Party Type', 'Donor'): - frappe.get_doc({ - 'doctype': 'Party Type', - 'party_type': 'Donor', - 'account_type': 'Receivable' - }).insert(ignore_permissions=True) \ No newline at end of file + if not frappe.db.exists('Party Type', 'Donor'): + frappe.get_doc({ + 'doctype': 'Party Type', + 'party_type': 'Donor', + 'account_type': 'Receivable' + }).insert(ignore_permissions=True) From 45735b35c4ae274ea1b83333fff21bdf1c080be8 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 16 Mar 2021 12:22:31 +0530 Subject: [PATCH 05/92] fix(minor): login: set as Login to ERPNext --- erpnext/patches.txt | 1 + erpnext/setup/install.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 59b12f319e..7016ecdd96 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -759,3 +759,4 @@ erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae +execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 0bb480bd4b..1e424dd4b3 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -161,5 +161,4 @@ def add_standard_navbar_items(): navbar_settings.save() def add_app_name(): - settings = frappe.get_doc("System Settings") - settings.app_name = _("ERPNext") \ No newline at end of file + frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') From 54482fd83f420bcad4d4bbb3810985b43b292fd6 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 15 Mar 2021 02:02:36 +0530 Subject: [PATCH 06/92] feat: added for disabling leave notification in HR settings --- erpnext/hr/doctype/hr_settings/hr_settings.json | 13 ++++++++++++- .../doctype/leave_application/leave_application.py | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index d8aae66796..09666c5db5 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -13,6 +13,7 @@ "stop_birthday_reminders", "expense_approver_mandatory_in_expense_claim", "leave_settings", + "send_leave_notification", "leave_approval_notification_template", "leave_status_notification_template", "role_allowed_to_create_backdated_leave_application", @@ -69,15 +70,19 @@ "label": "Leave Settings" }, { + "depends_on": "eval: doc.send_leave_notification == 1", "fieldname": "leave_approval_notification_template", "fieldtype": "Link", "label": "Leave Approval Notification Template", + "mandatory_depends_on": "eval: doc.send_leave_notification == 1", "options": "Email Template" }, { + "depends_on": "eval: doc.send_leave_notification == 1", "fieldname": "leave_status_notification_template", "fieldtype": "Link", "label": "Leave Status Notification Template", + "mandatory_depends_on": "eval: doc.send_leave_notification == 1", "options": "Email Template" }, { @@ -132,13 +137,19 @@ "fieldname": "automatically_allocate_leaves_based_on_leave_policy", "fieldtype": "Check", "label": "Automatically Allocate Leaves Based On Leave Policy" + }, + { + "default": "1", + "fieldname": "send_leave_notification", + "fieldtype": "Check", + "label": "Send Leave Notification" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-02-25 12:31:14.947865", + "modified": "2021-03-14 02:04:22.907159", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 132c3bd3b9..6d921ae296 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -40,7 +40,8 @@ class LeaveApplication(Document): def on_update(self): if self.status == "Open" and self.docstatus < 1: # notify leave approver about creation - self.notify_leave_approver() + if frappe.db.get_single_value("HR Settings", "send_leave_notification"): + self.notify_leave_approver() def on_submit(self): if self.status == "Open": From 6bafbffee02b107c370cca7cb88021a2cb93d647 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 16 Mar 2021 12:44:49 +0530 Subject: [PATCH 07/92] fix: added also for leave applier as requested --- erpnext/hr/doctype/leave_application/leave_application.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 6d921ae296..350ceadccd 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -51,7 +51,8 @@ class LeaveApplication(Document): self.update_attendance() # notify leave applier about approval - self.notify_employee() + if frappe.db.get_single_value("HR Settings", "send_leave_notification"): + self.notify_employee() self.create_leave_ledger_entry() self.reload() @@ -61,7 +62,8 @@ class LeaveApplication(Document): def on_cancel(self): self.create_leave_ledger_entry(submit=False) # notify leave applier about cancellation - self.notify_employee() + if frappe.db.get_single_value("HR Settings", "send_leave_notification"): + self.notify_employee() self.cancel_attendance() def validate_applicable_after(self): From 004f9e6b0c6d05f732de577fbeba1919677df102 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 16 Mar 2021 13:09:59 +0530 Subject: [PATCH 08/92] fix: Add method for regional round off account back --- erpnext/regional/india/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0fdf82a383..e24bd6c3d0 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -808,3 +808,24 @@ def get_gst_tax_amount(doc): gst_tax += tax.tax_amount_after_discount_amount return gst_tax, base_gst_tax + +@frappe.whitelist() +def get_regional_round_off_accounts(company, account_list): + country = frappe.get_cached_value('Company', company, 'country') + + if country != 'India': + return + + if isinstance(account_list, string_types): + account_list = json.loads(account_list) + + if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'): + return + + gst_accounts = get_gst_accounts(company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + account_list.extend(gst_account_list) + + return account_list From f1dbc021fc3088e05432a812524b34f54aaf4f5a Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 16 Mar 2021 14:15:59 +0530 Subject: [PATCH 09/92] fix: issue web list style --- erpnext/templates/includes/issue_row.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/issue_row.html b/erpnext/templates/includes/issue_row.html index d909c5feea..a04f558509 100644 --- a/erpnext/templates/includes/issue_row.html +++ b/erpnext/templates/includes/issue_row.html @@ -1,6 +1,6 @@
-
+
{% set indicator = 'red' if doc.status == 'Open' else 'gray' %} {% set indicator = 'green' if doc.status == 'Closed' else indicator %} From b8b89a02afaef322a84703e8fc42050706b8fcdd Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 16 Mar 2021 14:16:22 +0530 Subject: [PATCH 10/92] fix: item variant dialog dropdown issue --- erpnext/stock/doctype/item/item.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 55391235cb..7489d1fefb 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -717,6 +717,18 @@ $.extend(erpnext.item, { .on('focus', function(e) { $(e.target).val('').trigger('input'); }) + .on("awesomplete-open", () => { + let modal = field.$input.parents('.modal-dialog')[0]; + 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 1cdef8bd970d6dea3b999e96d7503943ee77efd3 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 16 Mar 2021 15:39:16 +0530 Subject: [PATCH 11/92] style: missing semicolon --- erpnext/stock/doctype/item/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 7489d1fefb..2079cf88dd 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -728,7 +728,7 @@ $.extend(erpnext.item, { if (modal) { $(modal).addClass("modal-dialog-scrollable"); } - }) + }); }); }, From 1482b2883fb23431655561927574a98384dc0e50 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 16 Mar 2021 20:08:51 +0530 Subject: [PATCH 12/92] fix(Non Profit): Membership and Donation API fixes (#24900) * fix: Donation fixes - differentiate between subscription payment and payment - issue with donation amount * fix: existing membership validation * fix: ignore subscription payments while capturing donations --- erpnext/non_profit/doctype/donation/donation.py | 6 +++++- erpnext/non_profit/doctype/membership/membership.py | 4 ++-- .../tax_exemption_80g_certificate.py | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index e947588482..6a2a06dbc8 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -91,6 +91,10 @@ def capture_razorpay_donations(*args, **kwargs): if not data.event == 'payment.captured': return + # to avoid capturing subscription payments as donations + if payment.description and 'subscription' in str(payment.description).lower(): + return + donor = get_donor(payment.email) if not donor: donor = create_donor(payment) @@ -119,7 +123,7 @@ def create_donation(donor, payment): 'donor_name': donor.donor_name, 'email': donor.email, 'date': getdate(), - 'amount': flt(payment.amount), + 'amount': flt(payment.amount) / 100, # Convert to rupees from paise 'mode_of_payment': payment.method, 'razorpay_payment_id': payment.id }).insert(ignore_mandatory=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 191281f4ce..c41a2f5165 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and not frappe.session.user == "Administrator": + if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -287,7 +287,7 @@ def trigger_razorpay_subscription(*args, **kwargs): membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: - message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id) + message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) return { "status": "Failed", "reason": e} diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index d734a18c3a..ef384d4602 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -29,7 +29,10 @@ class TaxExemption80GCertificate(Document): def validate_duplicates(self): if self.recipient == 'Donor': - certificate = frappe.db.exists(self.doctype, {'donation': self.donation}) + certificate = frappe.db.exists(self.doctype, { + 'donation': self.donation, + 'name': ('!=', self.name) + }) if certificate: frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format( get_link_to_form(self.doctype, certificate), frappe.bold(self.donation) From 65d7f2c79809db71b5a9bcaae8cd053e050977d3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 10:00:35 +0530 Subject: [PATCH 13/92] fix(Project): get correct holiday list when calculating dates; test fixes (#24901) --- erpnext/projects/doctype/project/project.py | 16 ++++++++-------- erpnext/projects/doctype/project/test_project.py | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 8ba0b6cb54..f9e1359b45 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -81,12 +81,18 @@ class Project(Document): def calculate_start_date(self, task_details): self.start_date = add_days(self.expected_start_date, task_details.start) - self.start_date = update_if_holiday(self.holiday_list, self.start_date) + self.start_date = self.update_if_holiday(self.start_date) return self.start_date def calculate_end_date(self, task_details): self.end_date = add_days(self.start_date, task_details.duration) - return update_if_holiday(self.holiday_list, self.end_date) + return self.update_if_holiday(self.end_date) + + def update_if_holiday(self, date): + holiday_list = self.holiday_list or get_holiday_list(self.company) + while is_holiday(holiday_list, date): + date = add_days(date, 1) + return date def dependency_mapping(self, template_tasks, project_tasks): for template_task in template_tasks: @@ -541,9 +547,3 @@ def set_project_status(project, status): project.status = status project.save() - -def update_if_holiday(holiday_list, date): - holiday_list = holiday_list or get_holiday_list() - while is_holiday(holiday_list, date): - date = add_days(date, 1) - return date diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 62905385a3..15a2873ade 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -8,7 +8,6 @@ test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] from erpnext.projects.doctype.project_template.test_project_template import make_project_template -from erpnext.projects.doctype.project.project import update_if_holiday from erpnext.projects.doctype.task.test_task import create_task from frappe.utils import getdate, nowdate, add_days @@ -97,7 +96,8 @@ def get_project(name, template): project_name = name, status = 'Open', project_template = template.name, - expected_start_date = nowdate() + expected_start_date = nowdate(), + company="_Test Company" )).insert() return project @@ -131,7 +131,7 @@ def task_exists(subject): def calculate_end_date(project, start, duration): start = add_days(project.expected_start_date, start) - start = update_if_holiday(project.holiday_list, start) + start = project.update_if_holiday(start) end = add_days(start, duration) - end = update_if_holiday(project.holiday_list, end) - return getdate(end) \ No newline at end of file + end = project.update_if_holiday(end) + return getdate(end) From bce904cf975099588d274d83b8875a8f3fc038a8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 10:03:30 +0530 Subject: [PATCH 14/92] test: fix Employee Not Found errors (#24906) --- .../hr/doctype/attendance_request/test_attendance_request.py | 4 +++- .../test_compensatory_leave_request.py | 4 +++- .../hr/doctype/leave_application/test_leave_application.py | 4 ++-- .../leave_policy_assignment/test_leave_policy_assignment.py | 2 ++ erpnext/support/doctype/issue/test_issue.py | 1 - 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 92b1eaee2c..3c42bd9fc3 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -8,6 +8,8 @@ import unittest from frappe.utils import nowdate from datetime import date +test_dependencies = ["Employee"] + class TestAttendanceRequest(unittest.TestCase): def setUp(self): for doctype in ["Attendance Request", "Attendance"]: @@ -56,4 +58,4 @@ class TestAttendanceRequest(unittest.TestCase): self.assertEqual(attendance.docstatus, 2) def get_employee(): - return frappe.get_doc("Employee", "_T-Employee-00001") \ No newline at end of file + return frappe.get_doc("Employee", "_T-Employee-00001") diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index 1615ab30f1..74ce30108f 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -10,6 +10,8 @@ from erpnext.hr.doctype.attendance_request.test_attendance_request import get_em from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on +test_dependencies = ["Employee"] + class TestCompensatoryLeaveRequest(unittest.TestCase): def setUp(self): frappe.db.sql(''' delete from `tabCompensatory Leave Request`''') @@ -129,4 +131,4 @@ def create_holiday_list(): ], "holiday_list_name": "_Test Compensatory Leave" }) - holiday_list.save() \ No newline at end of file + holiday_list.save() diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 53b7a39e51..b335c48594 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -12,7 +12,7 @@ from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees -test_dependencies = ["Leave Allocation", "Leave Block List"] +test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] _test_records = [ { @@ -639,4 +639,4 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el "docstatus": 1 }).insert() - allocate_leave.submit() \ No newline at end of file + allocate_leave.submit() diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py index c7bc6fb775..838e794795 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py @@ -9,6 +9,8 @@ from erpnext.hr.doctype.leave_application.test_leave_application import get_leav from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy +test_dependencies = ["Employee"] + class TestLeavePolicyAssignment(unittest.TestCase): def setUp(self): diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py index 483bb155db..46d02d8bf2 100644 --- a/erpnext/support/doctype/issue/test_issue.py +++ b/erpnext/support/doctype/issue/test_issue.py @@ -12,7 +12,6 @@ from datetime import timedelta class TestIssue(unittest.TestCase): def setUp(self): frappe.db.sql("delete from `tabService Level Agreement`") - frappe.db.sql("delete from `tabEmployee`") frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) create_service_level_agreements_for_issues() From 68aaf129022c25b651427e9ccca61cd6b1899a56 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 10:06:05 +0530 Subject: [PATCH 15/92] test: use _Test Company to avoid Holiday List error (#24902) --- erpnext/projects/doctype/timesheet/test_timesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 4cb38049ff..f93c6be570 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -49,7 +49,7 @@ class TestTimesheet(unittest.TestCase): self.assertEqual(timesheet.total_billable_amount, 0) def test_salary_slip_from_timesheet(self): - emp = make_employee("test_employee_6@salary.com") + emp = make_employee("test_employee_6@salary.com", company="_Test Company") salary_structure = make_salary_structure_for_timesheet(emp) timesheet = make_timesheet(emp, simulate = True, billable=1) From 22f5ff8492df6e821066dbf94f81829e5406c964 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 10:56:52 +0530 Subject: [PATCH 16/92] fix: Allow user to update exchange rate in Multi-currency LCV --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index aab5770a94..c33e556c25 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -795,7 +795,7 @@ class init_landed_taxes_and_totals(object): for d in self.doc.get(self.tax_field): if d.account_currency == company_currency: d.exchange_rate = 1 - elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date: + elif not d.exchange_rate: d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account, account_currency=d.account_currency, company=self.doc.company) From ff70e61d7a28cd923626acf364875f2894d683cd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 6 Mar 2021 22:08:08 +0530 Subject: [PATCH 17/92] feat: recursive product discount --- .../doctype/pricing_rule/pricing_rule.json | 37 +++++++++++------- .../doctype/pricing_rule/pricing_rule.py | 1 + .../accounts/doctype/pricing_rule/utils.py | 38 +++++++++++-------- .../promotional_scheme/promotional_scheme.py | 2 +- .../promotional_scheme_product_discount.json | 15 +++++++- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/taxes_and_totals.py | 5 ++- erpnext/public/js/controllers/transaction.js | 38 ++++++++++++------- 8 files changed, 91 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 33771645fe..428989aa96 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -44,6 +44,14 @@ "column_break_21", "min_amt", "max_amt", + "product_discount_scheme_section", + "same_item", + "free_item", + "free_qty", + "free_item_rate", + "column_break_42", + "free_item_uom", + "is_recursive", "section_break_23", "valid_from", "valid_upto", @@ -62,13 +70,6 @@ "discount_amount", "discount_percentage", "for_price_list", - "product_discount_scheme_section", - "same_item", - "free_item", - "free_qty", - "column_break_51", - "free_item_uom", - "free_item_rate", "section_break_13", "threshold_percentage", "priority", @@ -458,10 +459,6 @@ "fieldtype": "Float", "label": "Qty" }, - { - "fieldname": "column_break_51", - "fieldtype": "Column Break" - }, { "fieldname": "free_item_uom", "fieldtype": "Link", @@ -552,19 +549,33 @@ "fieldname": "promotional_scheme", "fieldtype": "Link", "label": "Promotional Scheme", - "options": "Promotional Scheme" + "no_copy": 1, + "options": "Promotional Scheme", + "print_hide": 1, + "read_only": 1 }, { "description": "Simple Python Expression, Example: territory != 'All Territories'", "fieldname": "condition", "fieldtype": "Code", "label": "Condition" + }, + { + "fieldname": "column_break_42", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on", + "fieldname": "is_recursive", + "fieldtype": "Check", + "label": "Is Recursive" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2021-03-01 23:18:38.717613", + "modified": "2021-03-06 22:01:24.840422", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index f0b4e2976d..aedf1c6f1a 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa "doctype": args.doctype, "has_margin": False, "name": args.name, + "free_item_data": [], "parent": args.parent, "parenttype": args.parenttype, "child_docname": args.get('child_docname') diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index d163335996..210cd16009 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -367,7 +367,7 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): if items and doc.get("items"): for row in doc.get('items'): - if row.get(apply_on) not in items: continue + if (row.get(apply_on) or args.get(apply_on)) not in items: continue if pr_doc.mixed_conditions: amt = args.get('qty') * args.get("price_list_rate") @@ -479,7 +479,7 @@ def apply_pricing_rule_on_transaction(doc): doc.calculate_taxes_and_totals() elif d.price_or_product_discount == 'Product': - item_details = frappe._dict({'parenttype': doc.doctype}) + item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []}) get_product_discount_rule(d, item_details, doc=doc) apply_pricing_rule_for_free_items(doc, item_details.free_item_data) doc.set_missing_values() @@ -508,9 +508,15 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): frappe.throw(_("Free item not set in the pricing rule {0}") .format(get_link_to_form("Pricing Rule", pricing_rule.name))) - item_details.free_item_data = { + qty = pricing_rule.free_qty or 1 + if pricing_rule.is_recursive: + transaction_qty = args.get('qty') if args else doc.total_qty + if transaction_qty: + qty = flt(transaction_qty) * qty + + free_item_data_args = { 'item_code': free_item, - 'qty': pricing_rule.free_qty or 1, + 'qty': qty, 'rate': pricing_rule.free_item_rate or 0, 'price_list_rate': pricing_rule.free_item_rate or 0, 'is_free_item': 1 @@ -519,24 +525,26 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): item_data = frappe.get_cached_value('Item', free_item, ['item_name', 'description', 'stock_uom'], as_dict=1) - item_details.free_item_data.update(item_data) - item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom - item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, - item_details.free_item_data['uom']).get("conversion_factor", 1) + free_item_data_args.update(item_data) + free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom + free_item_data_args['conversion_factor'] = get_conversion_factor(free_item, + free_item_data_args['uom']).get("conversion_factor", 1) if item_details.get("parenttype") == 'Purchase Order': - item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() + free_item_data_args['schedule_date'] = doc.schedule_date if doc else today() if item_details.get("parenttype") == 'Sales Order': - item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() + free_item_data_args['delivery_date'] = doc.delivery_date if doc else today() + + item_details.free_item_data.append(free_item_data_args) def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): - if pricing_rule_args.get('item_code'): - items = [d.item_code for d in doc.items - if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item] + if pricing_rule_args: + items = tuple([d.item_code for d in doc.items if d.is_free_item]) - if not items: - doc.append('items', pricing_rule_args) + for args in pricing_rule_args: + if not items or args.get('item_code') not in items: + doc.append('items', args) def get_pricing_rule_items(pr_doc): apply_on_data = [] diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 89f7238a06..6e13f0694c 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -21,7 +21,7 @@ price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discoun 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule'] product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', - 'free_item_rate', 'same_item'] + 'free_item_rate', 'same_item', 'is_recursive'] class PromotionalScheme(Document): def validate(self): diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json index 72d53bfa01..3eab51510d 100644 --- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json +++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json @@ -1,10 +1,12 @@ { + "actions": [], "creation": "2019-03-24 14:48:59.649168", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "disable", + "apply_multiple_pricing_rules", "column_break_2", "rule_description", "section_break_1", @@ -25,7 +27,7 @@ "threshold_percentage", "column_break_15", "priority", - "apply_multiple_pricing_rules" + "is_recursive" ], "fields": [ { @@ -152,10 +154,19 @@ "fieldname": "apply_multiple_pricing_rules", "fieldtype": "Check", "label": "Apply Multiple Pricing Rules" + }, + { + "default": "0", + "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on", + "fieldname": "is_recursive", + "fieldtype": "Check", + "label": "Is Recursive" } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-07-21 00:00:56.674284", + "links": [], + "modified": "2021-03-06 21:58:18.162346", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme Product Discount", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 12a81c7887..198e0e541d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1394,7 +1394,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) def get_new_child_item(item_row): - child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" + child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) def validate_quantity(child_item, d): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index aab5770a94..0639927d5e 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -113,7 +113,10 @@ class calculate_taxes_and_totals(object): item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) - item.discount_amount = item.rate_with_margin - item.rate + if not item.discount_amount: + item.discount_amount = item.rate_with_margin - item.rate + elif not item.discount_percentage: + item.rate -= item.discount_amount elif flt(item.price_list_rate) > 0: item.discount_amount = item.price_list_rate - item.rate elif flt(item.price_list_rate) > 0 and not item.discount_amount: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1c0abdffcf..56838a83f8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -577,7 +577,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); if (d.free_item_data) { - me.apply_product_discount(d.free_item_data); + me.apply_product_discount(d); } }, () => { @@ -1204,7 +1204,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ calculate_stock_uom_rate: function(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); - item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); + item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor); refresh_field("stock_uom_rate", item.name, item.parentfield); }, service_stop_date: function(frm, cdt, cdn) { @@ -1545,7 +1545,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } if (d.free_item_data) { - me.apply_product_discount(d.free_item_data); + me.apply_product_discount(d); } if (d.apply_rule_on_other_items) { @@ -1579,20 +1579,30 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, - apply_product_discount: function(free_item_data) { - const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code - && d.is_free_item)) || []; + apply_product_discount: function(args) { + const items = this.frm.doc.items.filter(d => (d.is_free_item)) || []; - if (!items.length) { - let row_to_modify = frappe.model.add_child(this.frm.doc, - this.frm.doc.doctype + ' Item', 'items'); + const exist_items = items.map(row => row.item_code); - for (let key in free_item_data) { - row_to_modify[key] = free_item_data[key]; + args.free_item_data.forEach(pr_row => { + let row_to_modify = {}; + if (!items || !in_list(exist_items, pr_row.item_code)) { + + row_to_modify = frappe.model.add_child(this.frm.doc, + this.frm.doc.doctype + ' Item', 'items'); + + } else if(items) { + row_to_modify = items.filter(d => d.item_code === pr_row.item_code)[0]; } - } if (items && items.length && free_item_data) { - items[0].qty = free_item_data.qty - } + + for (let key in pr_row) { + row_to_modify[key] = pr_row[key]; + } + }); + + // free_item_data is a temporary variable + args.free_item_data = ''; + refresh_field('items'); }, apply_price_list: function(item, reset_plc_conversion) { From a9b13476009beab864fccddc2bb128512bc3b7be Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 7 Mar 2021 11:43:22 +0530 Subject: [PATCH 18/92] fix: nonetype object has no attribute options --- .../promotional_scheme/promotional_scheme.py | 4 +- .../promotional_scheme_price_discount.json | 721 ++---------------- erpnext/public/js/controllers/transaction.js | 5 +- 3 files changed, 61 insertions(+), 669 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 6e13f0694c..e1725bc89e 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -18,10 +18,10 @@ other_fields = ['min_qty', 'max_qty', 'min_amt', 'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description'] price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate', - 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule'] + 'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules'] product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', - 'free_item_rate', 'same_item', 'is_recursive'] + 'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules'] class PromotionalScheme(Document): def validate(self): diff --git a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json index 224b8de779..795fb1c6f4 100644 --- a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json +++ b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json @@ -1,792 +1,181 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, + "actions": [], "creation": "2019-03-24 14:48:59.649168", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "disable", + "apply_multiple_pricing_rules", + "column_break_2", + "rule_description", + "section_break_2", + "min_qty", + "max_qty", + "column_break_3", + "min_amount", + "max_amount", + "section_break_6", + "rate_or_discount", + "column_break_10", + "rate", + "discount_amount", + "discount_percentage", + "section_break_11", + "warehouse", + "threshold_percentage", + "validate_applied_rule", + "column_break_14", + "priority", + "apply_discount_on_rate" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "disable", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Disable" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "rule_description", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Rule Description", - "length": 0, "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 1, "default": "0", "fieldname": "min_qty", "fieldtype": "Float", - "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": "Min Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Min Qty" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 1, "default": "0", "fieldname": "max_qty", "fieldtype": "Float", - "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": "Max Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Max Qty" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", "fieldname": "min_amount", "fieldtype": "Currency", - "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": "Min Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Min Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "depends_on": "", "fieldname": "max_amount", "fieldtype": "Currency", - "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": "Max Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Max Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Discount Percentage", - "depends_on": "", "fieldname": "rate_or_discount", "fieldtype": "Select", - "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": "Discount Type", - "length": 0, - "no_copy": 0, - "options": "\nRate\nDiscount Percentage\nDiscount Amount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nRate\nDiscount Percentage\nDiscount Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, "columns": 2, "depends_on": "eval:doc.rate_or_discount==\"Rate\"", "fieldname": "rate", "fieldtype": "Currency", - "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": "Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Rate" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"", "fieldname": "discount_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discount Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Discount Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"", "fieldname": "discount_percentage", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discount Percentage", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Discount Percentage" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_11", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "warehouse", "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": "Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Warehouse" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "threshold_percentage", "fieldtype": "Percent", - "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": "Threshold for Suggestion", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Threshold for Suggestion" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "fieldname": "validate_applied_rule", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Validate Applied Rule", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Validate Applied Rule" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "priority", "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": "Priority", - "length": 0, - "no_copy": 0, - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "depends_on": "priority", "fieldname": "apply_multiple_pricing_rules", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Multiple Pricing Rules", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Apply Multiple Pricing Rules" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", "depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules", "fieldname": "apply_discount_on_rate", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Discount on Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Apply Discount on Rate" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, + "index_web_pages_for_search": 1, "istable": 1, - "max_attachments": 0, - "modified": "2019-03-24 14:48:59.649168", + "links": [], + "modified": "2021-03-07 11:56:23.424137", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme Price Discount", - "name_case": "", "owner": "Administrator", "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 56838a83f8..567666f012 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1533,7 +1533,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(k=="price_list_rate") { if(flt(v) != flt(d.price_list_rate)) price_list_rate_changed = true; } - frappe.model.set_value(d.doctype, d.name, k, v); + + if (k !== 'free_item_data') { + frappe.model.set_value(d.doctype, d.name, k, v); + } } } From 56c20fb79fffb0db95013c3d62485b0f2d137f99 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 15:47:03 +0530 Subject: [PATCH 19/92] fix(India): Incorrect Nil Exempt and Non GST amount in GSTR3B report --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 68c8a0d4d3..0d08a35787 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -353,7 +353,7 @@ class GSTR3BReport(Document): inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent - and i.is_nil_exempt = 1 or i.is_non_gst = 1 and + and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s group by p.place_of_supply """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) From 084ec9773ecf404ae9d8df176eaf0cd694a411c2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 18:00:08 +0530 Subject: [PATCH 20/92] fix: Group nil exempted and non gst items separately --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 0d08a35787..a8e843b61d 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -349,14 +349,15 @@ class GSTR3BReport(Document): return inter_state_supply_details def get_inward_nil_exempt(self, state): - + print("@@@@@@@@@@@@") inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s - group by p.place_of_supply """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + print(inward_nil_exempt, "$#$#$#$") inward_nil_exempt_details = { "gst": { "intra": 0.0, From f9f0e347ed57f227879591037498250a1f3ec0b2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 17 Mar 2021 18:03:17 +0530 Subject: [PATCH 21/92] fix: Remove print statement --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index a8e843b61d..a49996d107 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -349,7 +349,6 @@ class GSTR3BReport(Document): return inter_state_supply_details def get_inward_nil_exempt(self, state): - print("@@@@@@@@@@@@") inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i where p.docstatus = 1 and p.name = i.parent @@ -357,7 +356,6 @@ class GSTR3BReport(Document): month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - print(inward_nil_exempt, "$#$#$#$") inward_nil_exempt_details = { "gst": { "intra": 0.0, From 77add49c6a45de89e6446647e55317a8d2e37769 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Mar 2021 18:49:54 +0530 Subject: [PATCH 22/92] fix: don't club same free item --- erpnext/accounts/doctype/pricing_rule/utils.py | 5 +++-- .../doctype/promotional_scheme/promotional_scheme.py | 2 +- erpnext/public/js/controllers/transaction.js | 7 ++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 210cd16009..c676abd4c6 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -517,6 +517,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): free_item_data_args = { 'item_code': free_item, 'qty': qty, + 'pricing_rules': pricing_rule.name, 'rate': pricing_rule.free_item_rate or 0, 'price_list_rate': pricing_rule.free_item_rate or 0, 'is_free_item': 1 @@ -540,10 +541,10 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): if pricing_rule_args: - items = tuple([d.item_code for d in doc.items if d.is_free_item]) + items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item]) for args in pricing_rule_args: - if not items or args.get('item_code') not in items: + if not items or (args.get('item_code'), args.get('pricing_rules')) not in items: doc.append('items', args) def get_pricing_rule_items(pr_doc): diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index e1725bc89e..523e9ee08a 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -12,7 +12,7 @@ from frappe.model.document import Document pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group' 'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from', 'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier', - 'supplier_group', 'company', 'currency'] + 'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules'] other_fields = ['min_qty', 'max_qty', 'min_amt', 'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description'] diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 567666f012..6aa4524a2e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1585,17 +1585,18 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ apply_product_discount: function(args) { const items = this.frm.doc.items.filter(d => (d.is_free_item)) || []; - const exist_items = items.map(row => row.item_code); + const exist_items = items.map(row => (row.item_code, row.pricing_rules)); args.free_item_data.forEach(pr_row => { let row_to_modify = {}; - if (!items || !in_list(exist_items, pr_row.item_code)) { + if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) { row_to_modify = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + ' Item', 'items'); } else if(items) { - row_to_modify = items.filter(d => d.item_code === pr_row.item_code)[0]; + row_to_modify = items.filter(d => (d.item_code === pr_row.item_code + && d.pricing_rules === pr_row.pricing_rules))[0]; } for (let key in pr_row) { From cbd67e662fa392790ae0138c8b286cdd8287dd97 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 17 Mar 2021 19:49:27 +0530 Subject: [PATCH 23/92] fix: calculate 80g certificate amount on validate for memberships (#24925) --- .../tax_exemption_80g_certificate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index ef384d4602..5bbd5750f9 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -16,6 +16,7 @@ class TaxExemption80GCertificate(Document): self.validate_duplicates() self.validate_company_details() self.set_company_address() + self.calculate_total() self.set_title() def validate_date(self): @@ -54,8 +55,17 @@ class TaxExemption80GCertificate(Document): self.company_address = address.company_address self.company_address_display = address.company_address_display + def calculate_total(self): + if self.recipient == 'Donor': + return + + total = 0 + for entry in self.payments: + total += flt(entry.amount) + self.total = total + def set_title(self): - if self.recipient == "Member": + if self.recipient == 'Member': self.title = self.member_name else: self.title = self.donor_name From cf3fc99b12057561209c56464f7ea654e4946235 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 19:54:08 +0530 Subject: [PATCH 24/92] test: fix salary component not found; other optimisation (#24920) --- erpnext/payroll/doctype/gratuity/test_gratuity.py | 5 ++++- erpnext/payroll/doctype/salary_slip/test_salary_slip.py | 1 - erpnext/projects/doctype/timesheet/test_timesheet.py | 9 +++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index e89e3dd077..7daea2da47 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -15,9 +15,12 @@ from frappe.utils import getdate, add_days, get_datetime, flt test_dependencies = ["Salary Component", "Salary Slip", "Account"] class TestGratuity(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) + + def setUp(self): frappe.db.sql("DELETE FROM `tabGratuity`") frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 143a306eb3..7672695653 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -361,7 +361,6 @@ class TestSalarySlip(unittest.TestCase): # as per assigned salary structure 40500 in monthly salary so 236000*5/100/12 frappe.db.sql("""delete from `tabPayroll Period`""") frappe.db.sql("""delete from `tabSalary Component`""") - frappe.db.sql("""delete from `tabAdditional Salary`""") payroll_period = create_payroll_period() diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index f93c6be570..f7c764e1bd 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -13,9 +13,18 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.payroll.doctype.salary_structure.test_salary_structure \ import make_salary_structure, create_salary_structure_assignment +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_earning_salary_component, + make_deduction_salary_component +) from erpnext.hr.doctype.employee.test_employee import make_employee class TestTimesheet(unittest.TestCase): + @classmethod + def setUpClass(cls): + make_earning_salary_component(setup=True, company_list=['_Test Company']) + make_deduction_salary_component(setup=True, company_list=['_Test Company']) + def setUp(self): for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]: frappe.db.sql("delete from `tab%s`" % dt) From f4a6c7823a368c99b5cf46ea31ef0d372144ae2d Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 19:57:53 +0530 Subject: [PATCH 25/92] test: create new item instead of using _Test Item to prevent error (#24915) --- .../doctype/pos_invoice/test_pos_invoice.py | 22 ++++++++++++------- erpnext/controllers/selling_controller.py | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index eb52fd6275..054afe5bbb 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -9,8 +9,16 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.stock.doctype.item.test_item import make_item class TestPOSInvoice(unittest.TestCase): + def tearDown(self): + if frappe.session.user != "Administrator": + 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) + def test_timestamp_change(self): w = create_pos_invoice(do_not_save=1) w.docstatus = 0 @@ -370,7 +378,6 @@ class TestPOSInvoice(unittest.TestCase): pos_inv.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") self.assertEqual(rounded_total, 3470) - frappe.set_user("Administrator") def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile @@ -412,7 +419,6 @@ class TestPOSInvoice(unittest.TestCase): pos_inv.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") self.assertEqual(rounded_total, 840) - frappe.set_user("Administrator") def test_merging_with_validate_selling_price(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile @@ -421,10 +427,12 @@ 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) - make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300) + item = "Test Selling Price Validation" + make_item(item, {"is_stock_item": 1}) + make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300) frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile() - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1) pos_inv.append('payments', { 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 }) @@ -438,7 +446,7 @@ class TestPOSInvoice(unittest.TestCase): }) self.assertRaises(frappe.ValidationError, pos_inv.submit) - pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1) + pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1) pos_inv2.append('payments', { 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400 }) @@ -457,8 +465,6 @@ class TestPOSInvoice(unittest.TestCase): pos_inv2.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") self.assertEqual(rounded_total, 400) - frappe.set_user("Administrator") - frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0) def create_pos_invoice(**args): args = frappe._dict(args) @@ -508,4 +514,4 @@ def create_pos_invoice(**args): else: pos_inv.payment_schedule = [] - return pos_inv \ No newline at end of file + return pos_inv diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index fb52c1f6ca..edc40c430a 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -144,7 +144,7 @@ class SellingController(StockController): if sales_person.commission_rate: sales_person.incentives = flt( - sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0, + sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0, self.precision("incentives", sales_person)) total += sales_person.allocated_percentage @@ -502,4 +502,4 @@ def set_default_income_account_for_item(obj): for d in obj.get("items"): if d.item_code: if getattr(d, "income_account", None): - set_item_default(d.item_code, obj.company, 'income_account', d.income_account) \ No newline at end of file + set_item_default(d.item_code, obj.company, 'income_account', d.income_account) From 764531542436a52964cd505e986b82b219d8cc9f Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 20:01:21 +0530 Subject: [PATCH 26/92] test: set correct qty when creating subcontract transfer entry (#24903) --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 02d4865320..604c88682f 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -723,7 +723,7 @@ class TestPurchaseOrder(unittest.TestCase): is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") make_stock_entry(target="_Test Warehouse - _TC", - item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100) + item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 1", qty=100, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", From 19bb8c1137d234d8bd36ac10323d0dde665f8c3f Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 20:03:54 +0530 Subject: [PATCH 27/92] ci: get branch name from GITHUB_REF if GITHUB_BASE_REF not available (#24913) --- .github/helper/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 253ad70198..7b0f944c66 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF}" --depth 1 +git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site @@ -43,4 +43,4 @@ sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile bench get-app erpnext "${GITHUB_WORKSPACE}" bench start & -bench --site test_site reinstall --yes \ No newline at end of file +bench --site test_site reinstall --yes From c647b6088ae066aeff915b90d3cc189c8e795e09 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 20:04:59 +0530 Subject: [PATCH 28/92] ci: update badge in README and add push event trigger (#24911) --- .github/workflows/ci-tests.yml | 4 +--- README.md | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 2a1db14d95..3f7d4bb741 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -1,8 +1,6 @@ name: CI -on: - pull_request: - workflow_dispatch: +on: [pull_request, workflow_dispatch, push] jobs: test: diff --git a/README.md b/README.md index 15782a2e0c..bb592ae75c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

ERP made simple

-[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext) +[![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop) From 7f906f9aaa1ee22897620d9666beb210060634af Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 17 Mar 2021 20:06:35 +0530 Subject: [PATCH 29/92] chore: add sider config (#24892) Co-authored-by: Sagar Vora --- .flake8 | 32 ++++++++++++++++++++++++++++++++ sider.yml | 3 +++ 2 files changed, 35 insertions(+) create mode 100644 .flake8 create mode 100644 sider.yml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..399b176e1d --- /dev/null +++ b/.flake8 @@ -0,0 +1,32 @@ +[flake8] +ignore = + E121, + E126, + E127, + E128, + E203, + E225, + E226, + E231, + E241, + E251, + E261, + E265, + E302, + E303, + E305, + E402, + E501, + E741, + W291, + W292, + W293, + W391, + W503, + W504, + F403, + B007, + B950, + W191, + +max-line-length = 200 \ No newline at end of file diff --git a/sider.yml b/sider.yml new file mode 100644 index 0000000000..2ca6e8deb1 --- /dev/null +++ b/sider.yml @@ -0,0 +1,3 @@ +linter: + flake8: + config: .flake8 \ No newline at end of file From 52bbd4e182ac968f2f35f7abe9a4b2adad791014 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 22:37:59 +0530 Subject: [PATCH 30/92] test: clear Leave Period before running Leave Allocation tests (#24928) --- .../hr/doctype/leave_allocation/test_leave_allocation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 26f077a649..0b71036c86 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -6,6 +6,10 @@ from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation class TestLeaveAllocation(unittest.TestCase): + @classmethod + def setUpClass(cls): + frappe.db.sql("delete from `tabLeave Period`") + def test_overlapping_allocation(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -177,4 +181,4 @@ def create_leave_allocation(**args): }) return leave_allocation -test_dependencies = ["Employee", "Leave Type"] \ No newline at end of file +test_dependencies = ["Employee", "Leave Type"] From 500f54e5150bab9806c6023d22abeadb5f713112 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 17 Mar 2021 23:25:04 +0530 Subject: [PATCH 31/92] test: fix shift type not found (#24929) --- .../hr/doctype/shift_request/test_shift_request.py | 4 +++- erpnext/hr/doctype/shift_type/test_records.json | 8 ++++++++ erpnext/hr/doctype/shift_type/test_shift_type.py | 12 +----------- 3 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 erpnext/hr/doctype/shift_type/test_records.json diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 3dcfcbf4a5..230bb2b0e4 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -7,6 +7,8 @@ import frappe import unittest from frappe.utils import nowdate, add_days +test_dependencies = ["Shift Type"] + class TestShiftRequest(unittest.TestCase): def setUp(self): for doctype in ["Shift Request", "Shift Assignment"]: @@ -46,4 +48,4 @@ def set_shift_approver(department): department_doc = frappe.get_doc("Department", department) department_doc.append('shift_request_approver',{'approver': "test1@example.com"}) department_doc.save() - department_doc.reload() \ No newline at end of file + department_doc.reload() diff --git a/erpnext/hr/doctype/shift_type/test_records.json b/erpnext/hr/doctype/shift_type/test_records.json new file mode 100644 index 0000000000..9040b915a1 --- /dev/null +++ b/erpnext/hr/doctype/shift_type/test_records.json @@ -0,0 +1,8 @@ +[ + { + "doctype": "Shift Type", + "name": "Day Shift", + "start_time": "9:00:00", + "end_time": "18:00:00" + } +] diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py index 535072a035..bc4f0eafcd 100644 --- a/erpnext/hr/doctype/shift_type/test_shift_type.py +++ b/erpnext/hr/doctype/shift_type/test_shift_type.py @@ -7,14 +7,4 @@ import frappe import unittest class TestShiftType(unittest.TestCase): - def test_make_shift_type(self): - if frappe.db.exists("Shift Type", "Day Shift"): - return - shift_type = frappe.get_doc({ - "doctype": "Shift Type", - "name": "Day Shift", - "start_time": "9:00:00", - "end_time": "18:00:00" - }) - shift_type.insert() - \ No newline at end of file + pass From 2c8a38d9fa7afdf6a51f9636cc18250c419053e3 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 18 Mar 2021 10:36:46 +0530 Subject: [PATCH 32/92] fix: report issue summary fix for zero issues --- erpnext/support/report/issue_summary/issue_summary.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index 3d735314f4..7861e30d25 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -260,8 +260,7 @@ class IssueSummary(object): self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0 def get_chart_data(self): - if not self.data: - return None + self.chart = [] labels = [] open_issues = [] @@ -310,8 +309,7 @@ class IssueSummary(object): } def get_report_summary(self): - if not self.data: - return None + self.report_summary = [] open_issues = 0 replied = 0 From 14c3c5172dcc6c5649d7397085ca4038ddbe6787 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 18 Mar 2021 10:45:28 +0530 Subject: [PATCH 33/92] fix(minor): rearrange the cards on home workspace --- erpnext/setup/workspace/home/home.json | 397 +++++++++++++------------ 1 file changed, 199 insertions(+), 198 deletions(-) diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index 69ca7cf9ad..305456b266 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -10,13 +10,14 @@ "hide_custom": 0, "icon": "getting-started", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Home", "links": [ { "hidden": 0, "is_query_report": 0, - "label": "Healthcare", + "label": "Accounting", "onboard": 0, "type": "Card Break" }, @@ -24,8 +25,8 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Patient", - "link_to": "Patient", + "label": "Chart of Accounts", + "link_to": "Account", "link_type": "DocType", "onboard": 1, "type": "Link" @@ -34,25 +35,8 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Diagnosis", - "link_to": "Diagnosis", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Agriculture", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop", - "link_to": "Crop", + "label": "Company", + "link_to": "Company", "link_type": "DocType", "onboard": 1, "type": "Link" @@ -61,8 +45,8 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Crop Cycle", - "link_to": "Crop Cycle", + "label": "Customer", + "link_to": "Customer", "link_type": "DocType", "onboard": 1, "type": "Link" @@ -71,112 +55,8 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Location", - "link_to": "Location", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Fertilizer", - "link_to": "Fertilizer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Education", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Student", - "link_to": "Student", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Course", - "link_to": "Course", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Instructor", - "link_to": "Instructor", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Room", - "link_to": "Room", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Non Profit", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Member", - "link_to": "Member", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Volunteer", - "link_to": "Volunteer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Chapter", - "link_to": "Chapter", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Donor", - "link_to": "Donor", + "label": "Supplier", + "link_to": "Supplier", "link_type": "DocType", "onboard": 1, "type": "Link" @@ -188,6 +68,16 @@ "onboard": 0, "type": "Card Break" }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item", + "link_to": "Item", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -302,73 +192,6 @@ "onboard": 1, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Accounting", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Item", - "link_to": "Item", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Customer", - "link_to": "Customer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Supplier", - "link_to": "Supplier", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Company", - "link_to": "Company", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Chart of Accounts", - "link_to": "Account", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Opening Invoice Creation Tool", - "link_to": "Opening Invoice Creation Tool", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -386,6 +209,16 @@ "onboard": 1, "type": "Link" }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Opening Invoice Creation Tool", + "link_to": "Opening Invoice Creation Tool", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -415,9 +248,177 @@ "link_type": "DocType", "onboard": 1, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient", + "link_to": "Patient", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Diagnosis", + "link_to": "Diagnosis", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Education", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student", + "link_to": "Student", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Instructor", + "link_to": "Instructor", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course", + "link_to": "Course", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Room", + "link_to": "Room", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Non Profit", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Donor", + "link_to": "Donor", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Member", + "link_to": "Member", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Volunteer", + "link_to": "Volunteer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chapter", + "link_to": "Chapter", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Agriculture", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Location", + "link_to": "Location", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Crop", + "link_to": "Crop", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Crop Cycle", + "link_to": "Crop Cycle", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fertilizer", + "link_to": "Fertilizer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" } ], - "modified": "2021-01-01 12:13:16.055668", + "modified": "2021-03-16 15:59:58.416154", "modified_by": "Administrator", "module": "Setup", "name": "Home", From 9bf1083d9e24221d01f0b05e77ecf1444e8bcaa7 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 18 Mar 2021 11:22:24 +0530 Subject: [PATCH 34/92] fix: payment references from disappearing on selecting cost center (#24831) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- .../doctype/payment_entry/payment_entry.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 6412772073..b5f6a401df 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -605,12 +605,22 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Column Break"}, {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, {fieldtype:"Section Break"}, + {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center", + "get_query": function() { + return { + "filters": {"company": frm.doc.company} + } + } + }, + {fieldtype:"Column Break"}, + {fieldtype:"Section Break"}, {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, ]; frappe.prompt(fields, function(filters){ frappe.flags.allocate_payment_amount = true; frm.events.validate_filters_data(frm, filters); + frm.doc.cost_center = filters.cost_center; frm.events.get_outstanding_documents(frm, filters); }, __("Filters"), __("Get Outstanding Documents")); }, @@ -1066,11 +1076,6 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("paid_from_account_balance", r.message.paid_from_account_balance); frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance); frm.set_value("party_balance", r.message.party_balance); - }, - () => { - if(frm.doc.payment_type != "Internal") { - frm.clear_table("references"); - } } ]); From c0a9c391ebeb18fd1ec0595fd55f2cf1603af7f5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 18 Mar 2021 14:07:24 +0530 Subject: [PATCH 35/92] fix: hide alt tag if item is not shown in website --- erpnext/stock/doctype/item/item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 33a8fe7c8d..6fed9efa63 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1054,6 +1054,7 @@ "read_only": 1 }, { + "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website", "fieldname": "website_image_alt", "fieldtype": "Data", "label": "Image Description" @@ -1066,7 +1067,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 1, - "modified": "2021-03-15 13:41:04.108932", + "modified": "2021-03-18 14:04:38.575519", "modified_by": "Administrator", "module": "Stock", "name": "Item", @@ -1137,4 +1138,4 @@ "sort_order": "DESC", "title_field": "item_name", "track_changes": 1 -} \ No newline at end of file +} From 78e576ee6abc2ca38dcb152689cfc94390aeb3f2 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 18 Mar 2021 15:23:52 +0530 Subject: [PATCH 36/92] fix: default total_estimated_cost to zero (#24939) Co-authored-by: walstanb --- .../doctype/job_applicant/test_job_applicant.py | 16 +++++++++++++--- erpnext/hr/doctype/job_offer/test_job_offer.py | 11 ++++++----- .../hr/doctype/staffing_plan/staffing_plan.py | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py index 6d275c82d9..872834230e 100644 --- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py @@ -13,11 +13,21 @@ class TestJobApplicant(unittest.TestCase): def create_job_applicant(**args): args = frappe._dict(args) - job_applicant = frappe.get_doc({ - "doctype": "Job Applicant", + + filters = { "applicant_name": args.applicant_name or "_Test Applicant", "email_id": args.email_id or "test_applicant@example.com", + } + + if frappe.db.exists("Job Applicant", filters): + return frappe.get_doc("Job Applicant", filters) + + job_applicant = frappe.get_doc({ + "doctype": "Job Applicant", "status": args.status or "Open" }) + + job_applicant.update(filters) job_applicant.save() - return job_applicant \ No newline at end of file + + return job_applicant diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 8886596450..690a692ddc 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -13,14 +13,15 @@ from erpnext.hr.doctype.staffing_plan.test_staffing_plan import make_company class TestJobOffer(unittest.TestCase): def test_job_offer_creation_against_vacancies(self): - create_staffing_plan(staffing_details=[{ - "designation": "Designer", + frappe.db.set_value("HR Settings", None, "check_vacancies", 1) + job_applicant = create_job_applicant(email_id="test_job_offer@example.com") + job_offer = create_job_offer(job_applicant=job_applicant.name, designation="UX Designer") + + create_staffing_plan(name='Test No Vacancies', staffing_details=[{ + "designation": "UX Designer", "vacancies": 0, "estimated_cost_per_position": 5000 }]) - frappe.db.set_value("HR Settings", None, "check_vacancies", 1) - job_applicant = create_job_applicant(email_id="test_job_offer@example.com") - job_offer = create_job_offer(job_applicant=job_applicant.name, designation="Researcher") self.assertRaises(frappe.ValidationError, job_offer.submit) # test creation of job offer when vacancies are not present diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 5b84d00bd6..533149a823 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -39,6 +39,7 @@ class StaffingPlan(Document): detail.current_count = designation_counts['employee_count'] detail.current_openings = designation_counts['job_openings'] + detail.total_estimated_cost = 0 if detail.number_of_positions > 0: if detail.vacancies > 0 and detail.estimated_cost_per_position: detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position) From b134b89fb033580907ec6b54a2ee3e4131b03433 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 18 Mar 2021 16:36:40 +0530 Subject: [PATCH 37/92] test: fix NegativeStockError (#24936) --- .../test_accounting_period.py | 50 +++++++++---------- .../test_opening_invoice_creation_tool.py | 33 ++++++------ .../doctype/sales_invoice/sales_invoice.py | 2 +- .../shopify_settings/test_shopify_settings.py | 34 ++++++++----- 4 files changed, 67 insertions(+), 52 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 022d7a7e80..10cd939894 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -11,36 +11,36 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import Overlap from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice class TestAccountingPeriod(unittest.TestCase): - def test_overlap(self): - ap1 = create_accounting_period(start_date = "2018-04-01", - end_date = "2018-06-30", company = "Wind Power LLC") - ap1.save() + def test_overlap(self): + ap1 = create_accounting_period(start_date = "2018-04-01", + end_date = "2018-06-30", company = "Wind Power LLC") + ap1.save() - ap2 = create_accounting_period(start_date = "2018-06-30", - end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1") - self.assertRaises(OverlapError, ap2.save) + ap2 = create_accounting_period(start_date = "2018-06-30", + end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1") + self.assertRaises(OverlapError, ap2.save) - def test_accounting_period(self): - ap1 = create_accounting_period(period_name = "Test Accounting Period 2") - ap1.save() + def test_accounting_period(self): + ap1 = create_accounting_period(period_name = "Test Accounting Period 2") + ap1.save() - doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC") - self.assertRaises(ClosedAccountingPeriod, doc.submit) + doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC") + self.assertRaises(ClosedAccountingPeriod, doc.submit) - def tearDown(self): - for d in frappe.get_all("Accounting Period"): - frappe.delete_doc("Accounting Period", d.name) + def tearDown(self): + for d in frappe.get_all("Accounting Period"): + frappe.delete_doc("Accounting Period", d.name) def create_accounting_period(**args): - args = frappe._dict(args) + args = frappe._dict(args) - accounting_period = frappe.new_doc("Accounting Period") - accounting_period.start_date = args.start_date or nowdate() - accounting_period.end_date = args.end_date or add_months(nowdate(), 1) - accounting_period.company = args.company or "_Test Company" - accounting_period.period_name =args.period_name or "_Test_Period_Name_1" - accounting_period.append("closed_documents", { - "document_type": 'Sales Invoice', "closed": 1 - }) + accounting_period = frappe.new_doc("Accounting Period") + accounting_period.start_date = args.start_date or nowdate() + accounting_period.end_date = args.end_date or add_months(nowdate(), 1) + accounting_period.company = args.company or "_Test Company" + accounting_period.period_name =args.period_name or "_Test_Period_Name_1" + accounting_period.append("closed_documents", { + "document_type": 'Sales Invoice', "closed": 1 + }) - return accounting_period \ No newline at end of file + return accounting_period diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index bdfe532b9f..8d6de2d562 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -6,10 +6,12 @@ from __future__ import unicode_literals import frappe import unittest -test_dependencies = ["Customer", "Supplier"] +from frappe.cache_manager import clear_doctype_cache from frappe.custom.doctype.property_setter.property_setter import make_property_setter from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account +test_dependencies = ["Customer", "Supplier"] + class TestOpeningInvoiceCreationTool(unittest.TestCase): def setUp(self): if not frappe.db.exists("Company", "_Test Opening Invoice Company"): @@ -24,22 +26,25 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): def test_opening_sales_invoice_creation(self): property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") - invoices = self.make_invoices(company="_Test Opening Invoice Company") + try: + invoices = self.make_invoices(company="_Test Opening Invoice Company") - self.assertEqual(len(invoices), 2) - expected_value = { - "keys": ["customer", "outstanding_amount", "status"], - 0: ["_Test Customer", 300, "Overdue"], - 1: ["_Test Customer 1", 250, "Overdue"], - } - self.check_expected_values(invoices, expected_value) + self.assertEqual(len(invoices), 2) + expected_value = { + "keys": ["customer", "outstanding_amount", "status"], + 0: ["_Test Customer", 300, "Overdue"], + 1: ["_Test Customer 1", 250, "Overdue"], + } + self.check_expected_values(invoices, expected_value) - si = frappe.get_doc("Sales Invoice", invoices[0]) + si = frappe.get_doc("Sales Invoice", invoices[0]) - # Check if update stock is not enabled - self.assertEqual(si.update_stock, 0) + # Check if update stock is not enabled + self.assertEqual(si.update_stock, 0) - property_setter.delete() + finally: + property_setter.delete() + clear_doctype_cache("Sales Invoice") def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" @@ -143,4 +148,4 @@ def make_customer(customer=None): customer.insert(ignore_permissions=True) return customer.name else: - return frappe.db.exists("Customer", customer_name) \ No newline at end of file + return frappe.db.exists("Customer", customer_name) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4076be724b..a1bf66b03e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -76,7 +76,7 @@ class SalesInvoice(SellingController): if not self.is_pos: self.so_dn_required() - + self.set_tax_withholding() self.validate_proj_cust() diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py index 74ad456ea6..5f471ab2e7 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest, os, json -from frappe.utils import cstr +from frappe.utils import cstr, cint from erpnext.erpnext_integrations.connectors.shopify_connection import create_order from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer @@ -13,9 +13,14 @@ from frappe.core.doctype.data_import.data_import import import_doc class ShopifySettings(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): frappe.set_user("Administrator") + cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')) + if not cls.allow_negative_stock: + frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1) + # use the fixture data import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json")) @@ -24,9 +29,15 @@ class ShopifySettings(unittest.TestCase): frappe.reload_doctype("Delivery Note") frappe.reload_doctype("Sales Invoice") - self.setup_shopify() + cls.setup_shopify() - def setup_shopify(self): + @classmethod + def tearDownClass(cls): + if not cls.allow_negative_stock: + frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) + + @classmethod + def setup_shopify(cls): shopify_settings = frappe.get_doc("Shopify Settings") shopify_settings.taxes = [] @@ -56,21 +67,20 @@ class ShopifySettings(unittest.TestCase): "delivery_note_series": "DN-" }).save(ignore_permissions=True) - self.shopify_settings = shopify_settings + cls.shopify_settings = shopify_settings def test_order(self): - ### Create Customer ### + # Create Customer with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer: shopify_customer = json.load(shopify_customer) create_customer(shopify_customer.get("customer"), self.shopify_settings) - ### Create Item ### + # Create Item with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item: shopify_item = json.load(shopify_item) make_item("_Test Warehouse - _TC", shopify_item.get("product")) - - ### Create Order ### + # Create Order with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order: shopify_order = json.load(shopify_order) @@ -80,17 +90,17 @@ class ShopifySettings(unittest.TestCase): self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id) - #check for customer + # Check for customer shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id")) sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id") self.assertEqual(shopify_order_customer_id, sales_order_customer_id) - #check sales invoice + # Check sales invoice sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id}) self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total) - #check delivery note + # Check delivery note delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note` where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0] From a04091aaf7d8dbd6288a8f29ab15d73134c3816d Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 18 Mar 2021 16:40:43 +0530 Subject: [PATCH 38/92] fix: exclude current doc while validation. (#24914) --- .../sales_taxes_and_charges_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py index 429a9f3591..52d19d54a8 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py @@ -46,5 +46,5 @@ def validate_disabled(doc): frappe.throw(_("Disabled template must not be default template")) def validate_for_tax_category(doc): - if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}): + if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}): frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category))) From 192ab058692cb052e6a845a710fad0d780847106 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 18 Mar 2021 17:12:12 +0530 Subject: [PATCH 39/92] ci: use ubuntu 18 specifically (#24904) --- .github/workflows/ci-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 3f7d4bb741..78c2f5a187 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -4,7 +4,7 @@ on: [pull_request, workflow_dispatch, push] jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 strategy: fail-fast: false From 8ee1146f727b2c673800f9b8cfa0095fd2996c6f Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 18 Mar 2021 17:45:25 +0530 Subject: [PATCH 40/92] test: add stock to avoid NegativeStockError (#24941) --- .../doctype/sales_invoice/test_sales_invoice.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 1b9557839f..90e21444f5 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1800,6 +1800,15 @@ class TestSalesInvoice(unittest.TestCase): si.selling_price_list = "_Test Price List Rest of the World" si.update_stock = 1 si.items[0].target_warehouse = 'Work In Progress - TCP1' + + # Add stock to stores for succesful stock transfer + make_stock_entry( + target="Stores - TCP1", + company = "_Test Company with perpetual inventory", + qty=1, + basic_rate=100 + ) + add_taxes(si) si.save() @@ -2269,4 +2278,4 @@ def add_taxes(doc): "cost_center": "Main - TCP1", "description": "Excise Duty", "rate": 12 - }) \ No newline at end of file + }) From 6ac11a04b0a5e4e78bb6a153f0549c197e772c03 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 18 Mar 2021 18:07:21 +0530 Subject: [PATCH 41/92] test: added warehouse for TCS Item as it is a Stock Item (#24942) --- .../test_tax_withholding_category.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 9ce8e3fe83..dd3b49aa04 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 @@ -177,7 +177,7 @@ def cancel_invoices(): for d in purchase_invoices: frappe.get_doc('Purchase Invoice', d).cancel() - + for d in sales_invoices: frappe.get_doc('Sales Invoice', d).cancel() @@ -229,7 +229,8 @@ def create_sales_invoice(**args): 'qty': args.qty or 1, 'rate': args.rate or 10000, 'cost_center': 'Main - _TC', - 'expense_account': 'Cost of Goods Sold - _TC' + 'expense_account': 'Cost of Goods Sold - _TC', + 'warehouse': args.warehouse or '_Test Warehouse - _TC' }] }) @@ -353,4 +354,4 @@ def create_tax_with_holding_category(): 'company': '_Test Company', 'account': 'TDS - _TC' }] - }).insert() \ No newline at end of file + }).insert() From 6df61d217fe986f17b1e9f93c8398cf14c61b463 Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Thu, 18 Mar 2021 19:23:59 +0530 Subject: [PATCH 42/92] test: created a new user to test child perms (#24943) Co-authored-by: Sagar Vora --- .../doctype/sales_order/test_sales_order.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index ee16f44171..2eff80d253 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1,11 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe import json -from frappe.utils import flt, add_days, nowdate -import frappe.permissions import unittest +import frappe +import frappe.permissions +from frappe.utils import flt, add_days, nowdate +from frappe.core.doctype.user_permission.test_user_permission import create_user from erpnext.selling.doctype.sales_order.sales_order \ import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -444,10 +445,8 @@ class TestSalesOrder(unittest.TestCase): def test_update_child_perm(self): so = make_sales_order(item_code= "_Test Item", qty=4) - user = 'test@example.com' - test_user = frappe.get_doc('User', user) - test_user.add_roles("Accounts User") - frappe.set_user(user) + test_user = create_user("test_so_child_perms@example.com", "Accounts User") + frappe.set_user(test_user.name) # update qty trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}]) @@ -456,18 +455,14 @@ class TestSalesOrder(unittest.TestCase): # add new item trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) - test_user.remove_roles("Accounts User") - frappe.set_user("Administrator") def test_update_child_qty_rate_with_workflow(self): from frappe.model.workflow import apply_workflow - frappe.set_user("Administrator") workflow = make_sales_order_workflow() so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1) apply_workflow(so, 'Approve') - frappe.set_user("Administrator") user = 'test@example.com' test_user = frappe.get_doc('User', user) test_user.add_roles("Sales User", "Test Junior Approver") From f21a55c8efc42a0dac064dae9dc2e8b684b82761 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 13 Mar 2021 12:48:14 +0530 Subject: [PATCH 43/92] fix: revert stock balance value calculation --- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index e5d4d626c4..6dfede4590 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -198,7 +198,7 @@ def get_item_warehouse_map(filters, sle): else: qty_diff = flt(d.actual_qty) - value_diff = flt(d.stock_value) - flt(qty_dict.bal_val) + value_diff = flt(d.stock_value_difference) if d.posting_date < from_date: qty_dict.opening_qty += qty_diff From 08855182a3ae839cac83b692ce2518893d5adc1a Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 19 Mar 2021 16:34:14 +0530 Subject: [PATCH 44/92] test: fix test_loan for payroll module (#24946) * test: fix test_loan for payroll module * fix: minor style changes --- .../doctype/payroll_entry/test_payroll_entry.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index e098ec79b0..84c381489c 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -12,7 +12,7 @@ from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \ make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment -from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry +from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans class TestPayrollEntry(unittest.TestCase): @@ -168,15 +168,23 @@ class TestPayrollEntry(unittest.TestCase): salary_structure = "Test Salary Structure for Loan" make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency) + if not frappe.db.exists("Loan Type", "Car Loan"): + create_loan_accounts() + create_loan_type("Car Loan", 500000, 8.4, + is_term_loan=1, + mode_of_payment='Cash', + payment_account='Payment Account - _TC', + loan_account='Loan Account - _TC', + interest_income_account='Interest Income Account - _TC', + penalty_income_account='Penalty Income Account - _TC') + loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) loan.repay_from_salary = 1 loan.submit() make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1)) - process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) - dates = get_start_end_dates('Monthly', nowdate()) make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account, currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC") @@ -267,4 +275,4 @@ def get_salary_slip(user, period, salary_structure): salary_slip.calculate_net_pay() salary_slip.db_update() - return salary_slip \ No newline at end of file + return salary_slip From 22054044a31d552be9c50ccf986f8dd45c5297a7 Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Sat, 20 Mar 2021 16:35:14 +0530 Subject: [PATCH 45/92] test: created a new user to test warehouse user perms (#24954) Co-authored-by: Sagar Vora --- .../doctype/sales_order/test_sales_order.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 2eff80d253..0fdfb1b889 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -613,33 +613,31 @@ class TestSalesOrder(unittest.TestCase): frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value) def test_warehouse_user(self): - frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") - frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") - frappe.permissions.add_user_permission("Company", "_Test Company 1", "test2@example.com") - - test_user = frappe.get_doc("User", "test@example.com") - test_user.add_roles("Sales User", "Stock User") - test_user.remove_roles("Sales Manager") + test_user = create_user("test_so_warehouse_user@example.com", "Sales User", "Stock User") test_user_2 = frappe.get_doc("User", "test2@example.com") test_user_2.add_roles("Sales User", "Stock User") test_user_2.remove_roles("Sales Manager") - frappe.set_user("test@example.com") + frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name) + frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name) + frappe.permissions.add_user_permission("Company", "_Test Company 1", test_user_2.name) - so = make_sales_order(company="_Test Company 1", + frappe.set_user(test_user.name) + + so = make_sales_order(company="_Test Company 1", customer="_Test Customer 1", warehouse="_Test Warehouse 2 - _TC1", do_not_save=True) so.conversion_rate = 0.02 so.plc_conversion_rate = 0.02 self.assertRaises(frappe.PermissionError, so.insert) - frappe.set_user("test2@example.com") + frappe.set_user(test_user_2.name) so.insert() frappe.set_user("Administrator") - frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") - frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") - frappe.permissions.remove_user_permission("Company", "_Test Company 1", "test2@example.com") + frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name) + frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name) + frappe.permissions.remove_user_permission("Company", "_Test Company 1", test_user_2.name) def test_block_delivery_note_against_cancelled_sales_order(self): so = make_sales_order() From a85959f574ed4d06fba645bb4e0152b194ccc385 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 20 Mar 2021 16:37:33 +0530 Subject: [PATCH 46/92] fix: remove unwanted parameter in calculate_rate_and_amount (#24883) * fix: remove unwanted parameter in calculate_rate_and_amount * fix: translation syntax --- erpnext/stock/doctype/pick_list/pick_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 0da57b734b..d723fac11b 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -25,8 +25,8 @@ class PickList(Document): if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'): continue if not item.serial_no: - frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format( - frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))), + frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}").format( + frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse)), title=_("Serial Nos Required")) if len(item.serial_no.split('\n')) == item.picked_qty: continue @@ -380,7 +380,7 @@ def create_stock_entry(pick_list): stock_entry.set_incoming_rate() stock_entry.set_actual_qty() - stock_entry.calculate_rate_and_amount(update_finished_item_rate=False) + stock_entry.calculate_rate_and_amount() return stock_entry.as_dict() From 5e1cb5e4bcc00014d7dac5d10591789ce1912221 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 20 Mar 2021 22:22:01 +0530 Subject: [PATCH 47/92] fix: membership renewal validation (#24963) --- erpnext/non_profit/doctype/membership/membership.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index c41a2f5165..52447e4386 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": + if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -90,6 +90,7 @@ class Membership(Document): self.validate_membership_type_and_settings(plan, settings) invoice = make_invoice(self, member, plan, settings) + self.reload() self.invoice = invoice.name if with_payment_entry: @@ -284,6 +285,7 @@ def trigger_razorpay_subscription(*args, **kwargs): settings = frappe.get_doc("Non Profit Settings") if settings.allow_invoicing and settings.automate_membership_invoicing: + membership.reload() membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: From f112ce14cf7c9d24293e43b6413aba25a79a4ca4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Mar 2021 18:33:55 +0530 Subject: [PATCH 48/92] fix: Pending shortfall update on processing loan security shortfall --- .../loan_security_shortfall.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 6469806884..b5e78981d0 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -55,6 +55,9 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): 'total_interest_payable', 'disbursed_amount', 'status'], filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1}) + loan_shortfall_map = frappe._dict(frappe.get_all("Loan Security Shortfall", + fields=["loan", "name"], filters={"status": "Pending"}, as_list=1)) + loan_security_map = {} for loan in loans: @@ -71,14 +74,19 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): for security, qty in pledged_securities.items(): if not ltv_ratio: ltv_ratio = get_ltv_ratio(security) - security_value += loan_security_price_map.get(security) * qty + security_value += flt(loan_security_price_map.get(security)) * flt(qty) - current_ratio = (outstanding_amount/security_value) * 100 + current_ratio = (outstanding_amount/security_value) * 100 if security_value else 0 if current_ratio > ltv_ratio: shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100) create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount, process_loan_security_shortfall) + elif loan_shortfall_map.get(loan.name): + shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100) + if shortfall_amount <= 0: + shortfall = loan_shortfall_map.get(loan.name) + update_pending_shortfall(shortfall) def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall): existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name") @@ -101,3 +109,11 @@ def get_ltv_ratio(loan_security): ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio') return ltv_ratio +def update_pending_shortfall(shortfall): + # Get all pending loan security shortfall + frappe.db.set_value("Loan Security Shortfall", shortfall, + { + "status": "Completed", + "shortfall_amount": 0 + }) + From ea487b6c2e89e36610ca9cf95b3c8802bf91204a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 22 Mar 2021 20:54:07 +0530 Subject: [PATCH 49/92] Revert "fix: GSTR B2C report" --- erpnext/regional/report/gstr_1/gstr_1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 09b04ff367..62faa30e3f 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -78,7 +78,7 @@ class Gstr1Report(object): place_of_supply = invoice_details.get("place_of_supply") ecommerce_gstin = invoice_details.get("ecommerce_gstin") - b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin, inv),{ + b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{ "place_of_supply": "", "ecommerce_gstin": "", "rate": "", @@ -90,7 +90,7 @@ class Gstr1Report(object): "invoice_value": invoice_details.get("base_grand_total"), }) - row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin, inv)) + row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) row["place_of_supply"] = place_of_supply row["ecommerce_gstin"] = ecommerce_gstin row["rate"] = rate From 48d5a65ef59c67adfc64426af0b9796555a183bb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Mar 2021 21:19:19 +0530 Subject: [PATCH 50/92] fix: Add test for pending shortfall update --- erpnext/loan_management/doctype/loan/test_loan.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 13a209418d..4b9a89486a 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -275,6 +275,11 @@ class TestLoan(unittest.TestCase): frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250 where loan_security='Test Security 2'""") + create_process_loan_security_shortfall() + loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) + self.assertEquals(loan_security_shortfall.status, "Completed") + self.assertEquals(loan_security_shortfall.shortfall_amount, 0) + def test_loan_security_unpledge(self): pledge = [{ "loan_security": "Test Security 1", From bde159a77d7e151942df6ed4b090b324a5d7dfce Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 22 Mar 2021 23:36:48 +0530 Subject: [PATCH 51/92] fix: total weight not set for free items --- erpnext/controllers/accounts_controller.py | 3 ++- erpnext/stock/get_item_details.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 198e0e541d..73276f3646 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -26,7 +26,8 @@ from erpnext.controllers.print_settings import set_print_templates_for_item_tabl class AccountMissingError(frappe.ValidationError): pass -force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") +force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", + "pricing_rules", "weight_per_unit", "weight_uom", "total_weight") class AccountsController(TransactionBase): def __init__(self, *args, **kwargs): diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 873cfec85e..70e4c2c40e 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -314,7 +314,9 @@ def get_basic_details(args, item, overwrite_warehouse=True): "last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0, "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), - "bom_no": item.get("default_bom") + "bom_no": item.get("default_bom"), + "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), + "weight_uom": args.get("weight_uom") or item.get("weight_uom") }) if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): @@ -369,6 +371,9 @@ def get_basic_details(args, item, overwrite_warehouse=True): if meta.get_field("barcode"): update_barcode_value(out) + if out.get("weight_per_unit"): + out['total_weight'] = out.weight_per_unit * out.stock_qty + return out def get_item_warehouse(item, args, overwrite_warehouse, defaults={}): From ad9b65fcf216f69eebd443f36d9ee9218b753c76 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:45:20 +0530 Subject: [PATCH 52/92] fix: TDS check getting checked after reload (#24972) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 06aa20bfc5..66a8e206a8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -524,7 +524,7 @@ frappe.ui.form.on("Purchase Invoice", { }, onload: function(frm) { - if(frm.doc.__onload) { + if(frm.doc.__onload && frm.is_new()) { if(frm.doc.supplier) { frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; } From e6ad39038e2105bf3e764f9bc79d834561d02db6 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 23 Mar 2021 10:46:03 +0530 Subject: [PATCH 53/92] chore: Allow changing Work Stations in WO. (#24897) --- erpnext/manufacturing/doctype/job_card/job_card.py | 3 +++ erpnext/manufacturing/doctype/work_order/work_order.json | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 662a06b1ee..7aaf2a08ec 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -255,6 +255,9 @@ class JobCard(Document): 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 + if data.get("workstation") != self.workstation: + # workstations can change in a job card + data.workstation = self.workstation wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 585a09db2b..cd9edeeea8 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -333,8 +333,7 @@ "fieldname": "operations", "fieldtype": "Table", "label": "Operations", - "options": "Work Order Operation", - "read_only": 1 + "options": "Work Order Operation" }, { "depends_on": "operations", @@ -496,7 +495,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2020-05-05 19:32:43.323054", + "modified": "2021-03-16 13:27:51.116484", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", From 762d6e38f71423a2ada93be1885c7c1453514074 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 15 Mar 2021 19:10:54 +0530 Subject: [PATCH 54/92] fix: Allow zero valuation in stock reconciliation Stock reconciliation can not be done for customer provided item as they have zero valuation. This change adds a checkbox in item table to allow such items. Related issue: ISS-20-21-10248 --- .../stock_reconciliation/stock_reconciliation.py | 5 +++-- .../stock_reconciliation_item.json | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index f0a90f9754..4d9a01de4c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -31,6 +31,7 @@ class StockReconciliation(StockController): self.validate_expense_account() self.set_total_qty_and_amount() self.validate_putaway_capacity() + self.validate_customer_provided_item() if self._action=="submit": self.make_batches('warehouse') @@ -217,7 +218,7 @@ class StockReconciliation(StockController): if row.valuation_rate in ("", None): row.valuation_rate = previous_sle.get("valuation_rate", 0) - if row.qty and not row.valuation_rate: + if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate: frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx)) if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction") @@ -531,4 +532,4 @@ def get_difference_account(purpose, company): account = frappe.db.get_value('Account', {'is_group': 0, 'company': company, 'account_type': 'Temporary'}, 'name') - return account \ No newline at end of file + return account 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 e53db0772b..85c7ebe263 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -13,6 +13,7 @@ "qty", "valuation_rate", "amount", + "allow_zero_valuation_rate", "serial_no_and_batch_section", "serial_no", "column_break_11", @@ -166,10 +167,19 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "default": "0", + "fieldname": "allow_zero_valuation_rate", + "fieldtype": "Check", + "label": "Allow Zero Valuation Rate", + "print_hide": 1, + "read_only": 1 } ], "istable": 1, - "modified": "2019-06-14 17:10:53.188305", + "links": [], + "modified": "2021-03-23 11:09:44.407157", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -179,4 +189,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 714b78086895b20fe902aa84d07016e3e44ee1b6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Mar 2021 12:41:19 +0530 Subject: [PATCH 55/92] test: customer item in stock reconciliation --- .../stock_reconciliation/test_stock_reconciliation.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 088456f865..6690c6a606 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -193,6 +193,16 @@ class TestStockReconciliation(unittest.TestCase): stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() + def test_customer_provided_items(self): + item_code = 'Stock-Reco-customer-Item-100' + create_item(item_code, is_customer_provided_item = 1, + customer = '_Test Customer', is_purchase_item = 0) + + sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420) + + self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1) + self.assertEqual(sr.get("items")[0].valuation_rate, 0) + self.assertEqual(sr.get("items")[0].amount, 0) def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From e76ecb8f740818a761dac2d7441babb38c7a30e2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 23 Mar 2021 12:16:58 +0530 Subject: [PATCH 56/92] fix: set valuation rate for customer items to zero - In stock reconciliation always set valuation rate of customer provided items to zero during validation. - Let user know the valuation has been changed. --- .../stock_reconciliation.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 4d9a01de4c..b452e96c5e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -29,9 +29,10 @@ class StockReconciliation(StockController): self.remove_items_with_no_change() self.validate_data() self.validate_expense_account() + self.validate_customer_provided_item() + self.set_zero_value_for_customer_provided_items() self.set_total_qty_and_amount() self.validate_putaway_capacity() - self.validate_customer_provided_item() if self._action=="submit": self.make_batches('warehouse') @@ -437,6 +438,20 @@ class StockReconciliation(StockController): if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss": frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError) + def set_zero_value_for_customer_provided_items(self): + changed_any_values = False + + for d in self.get('items'): + is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item') + if is_customer_item and d.valuation_rate: + d.valuation_rate = 0.0 + changed_any_values = True + + if changed_any_values: + msgprint(_("Valuation rate for customer provided items has been set to zero."), + title=_("Note"), indicator="blue") + + def set_total_qty_and_amount(self): for d in self.get("items"): d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate")) From f114da2f67dd309d96130c98cf0b778d7c027ab6 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Tue, 23 Mar 2021 17:48:25 +0530 Subject: [PATCH 57/92] refactor: Make Discharge Schedule Date as Datetime (#24940) * refactor: Make Discharge Schedule Date as Datetime Like Admitted Datetime make discharge date as discharge datetime. Signed-off-by: Syed Mujeer Hashmi * fix: Add patch for renaming discharge_date to discharge_datetime Signed-off-by: Syed Mujeer Hashmi * fix: quote style Co-authored-by: Rucha Mahabal --- .../doctype/inpatient_record/inpatient_record.json | 9 +++++---- .../doctype/inpatient_record/inpatient_record.py | 2 +- erpnext/patches.txt | 1 + .../patches/v13_0/rename_discharge_date_in_ip_record.py | 8 ++++++++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index 5ced845c1b..aaf0e855d4 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -53,7 +53,7 @@ "discharge_ordered_date", "discharge_practitioner", "discharge_encounter", - "discharge_date", + "discharge_datetime", "cb_discharge", "discharge_instructions", "followup_date", @@ -404,14 +404,15 @@ "permlevel": 1 }, { - "fieldname": "discharge_date", - "fieldtype": "Date", + "fieldname": "discharge_datetime", + "fieldtype": "Datetime", "label": "Discharge Date", "read_only": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-05-21 02:26:22.144575", + "modified": "2021-03-18 14:44:11.689956", "modified_by": "Administrator", "module": "Healthcare", "name": "Inpatient Record", diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index 88d7f0b233..2934316c06 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -151,7 +151,7 @@ def check_out_inpatient(inpatient_record): def discharge_patient(inpatient_record): validate_inpatient_invoicing(inpatient_record) - inpatient_record.discharge_date = today() + inpatient_record.discharge_datetime = now_datetime() inpatient_record.status = "Discharged" inpatient_record.save(ignore_permissions = True) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7016ecdd96..36751843e2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -760,3 +760,4 @@ erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') +erpnext.patches.v13_0.rename_discharge_date_in_ip_record diff --git a/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py new file mode 100644 index 0000000000..491dc82f78 --- /dev/null +++ b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + frappe.reload_doc("Healthcare", "doctype", "Inpatient Record") + if frappe.db.has_column("Inpatient Record", "discharge_date"): + rename_field("Inpatient Record", "discharge_date", "discharge_datetime") From 02142c41bb8acb52a9383b94bac5b631e3d66100 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 23 Mar 2021 20:53:56 +0530 Subject: [PATCH 58/92] fix: validating party currency with doc currency (#24318) * fix: validating supplier currency * fix: compare current currency with company currency as well. * fix: replaced or with add in condition * fix: conditioning to get proper payment terms values Co-authored-by: Saqib --- erpnext/controllers/accounts_controller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 12a81c7887..e8f27c31fe 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -829,10 +829,10 @@ class AccountsController(TransactionBase): party_account_currency = get_party_account_currency(party_type, party, self.company) if (party_account_currency - and party_account_currency != self.company_currency - and self.currency != party_account_currency): + and (self.currency != party_account_currency + and self.currency != self.company_currency)): frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") - .format(party_type, party, party_account_currency), InvalidCurrency) + .format(party_type, party, frappe.bold(party_account_currency), InvalidCurrency)) # Note: not validating with gle account because we don't have the account # at quotation / sales order level and we shouldn't stop someone @@ -898,7 +898,7 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if party_account_currency == self.company_currency: + if self.company_currency == self.currency: grand_total = self.get("base_rounded_total") or self.base_grand_total else: grand_total = self.get("rounded_total") or self.grand_total @@ -959,7 +959,7 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): total += flt(d.payment_amount) - if party_account_currency == self.company_currency: + if self.company_currency == self.currency: total = flt(total, self.precision("base_grand_total")) grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) else: @@ -1394,7 +1394,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) def get_new_child_item(item_row): - child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" + child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) def validate_quantity(child_item, d): From 12f0923a61ea9302a07b719775938393a0a95d36 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 23 Mar 2021 21:09:49 +0530 Subject: [PATCH 59/92] fix: serial no trim issue (#24949) * fix: serial no trim issue * fix: valid serial nos * fix: sider Co-authored-by: Marica --- erpnext/public/js/controllers/transaction.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1c0abdffcf..365851aab6 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -738,21 +738,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } else { var valid_serial_nos = []; - + var serialnos = []; // Replacing all occurences of comma with carriage return - var serial_nos = item.serial_no.trim().replace(/,/g, '\n'); - - serial_nos = serial_nos.trim().split('\n'); - - // Trim each string and push unique string to new list - for (var x=0; x<=serial_nos.length - 1; x++) { - if (serial_nos[x].trim() != "" && valid_serial_nos.indexOf(serial_nos[x].trim()) == -1) { - valid_serial_nos.push(serial_nos[x].trim()); + item.serial_no = item.serial_no.replace(/,/g, '\n'); + serialnos = item.serial_no.split("\n"); + for (var i = 0; i < serialnos.length; i++) { + if (serialnos[i] != "") { + valid_serial_nos.push(serialnos[i]); } } - - // Add the new list to the serial no. field in grid with each in new line - item.serial_no = valid_serial_nos.join('\n'); item.conversion_factor = item.conversion_factor || 1; refresh_field("serial_no", item.name, item.parentfield); From 82983369fa75fc3c3ae8b925712d5fc508552620 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 23 Mar 2021 21:11:01 +0530 Subject: [PATCH 60/92] fix: Period list for exponential smoothing forecasting report (#24982) --- erpnext/accounts/report/financial_statements.py | 6 +++++- .../exponential_smoothing_forecasting.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 7dfce85629..14efa1f8fc 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -51,7 +51,11 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ "from_date": start_date }) - to_date = add_months(start_date, months_to_add) + if i==0 and filter_based_on == 'Date Range': + to_date = add_months(get_first_day(start_date), months_to_add) + else: + to_date = add_months(start_date, months_to_add) + start_date = to_date # Subtract one day from to_date, as it may be first day in next fiscal year or month diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 2ca9f1694b..fc27d35598 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -61,7 +61,7 @@ class ForecastingReport(ExponentialSmoothingForecast): from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1) self.period_list = get_period_list(from_date, self.filters.to_date, - from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True) + from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True) order_data = self.get_data_for_forecast() or [] From 5ce0569f80c622d6d7ff480015e8d79b82bf93b5 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 23 Mar 2021 21:13:41 +0530 Subject: [PATCH 61/92] fix: validate_series (#24984) --- erpnext/setup/doctype/naming_series/naming_series.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index abff97364c..2ea0bc08ca 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -10,6 +10,7 @@ from frappe import msgprint, throw, _ from frappe.model.document import Document from frappe.model.naming import parse_naming_series from frappe.permissions import get_doctypes_with_read +from frappe.core.doctype.doctype.doctype import validate_series class NamingSeriesNotSetError(frappe.ValidationError): pass @@ -126,7 +127,7 @@ class NamingSeries(Document): dt = frappe.get_doc("DocType", self.select_doc_for_series) options = self.scrub_options_list(self.set_options.split("\n")) for series in options: - dt.validate_series(series) + validate_series(dt, series) for i in sr: if i[0]: existing_series = [d.split('.')[0] for d in i[0].split("\n")] From 575f8f226b3f1095fd557aa23f509aad39422b06 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 24 Mar 2021 11:30:53 +0530 Subject: [PATCH 62/92] fix: repost not completed backdated transactions (#24980) --- erpnext/hooks.py | 1 + .../repost_item_valuation.py | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4b3597afd7..9b9a0dae2e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -324,6 +324,7 @@ scheduler_events = { "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", + "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" ], "daily": [ "erpnext.stock.reorder_item.reorder_item", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 8436acbed2..559f9a5ed9 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form +from frappe.utils import cint, get_link_to_form, add_to_date, today from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -29,7 +29,7 @@ class RepostItemValuation(Document): self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") elif self.warehouse: self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company") - + def set_status(self, status=None): if not status: status = 'Queued' @@ -54,7 +54,6 @@ def repost(doc): repost_sl_entries(doc) repost_gl_entries(doc) - check_if_stock_and_account_balance_synced(doc.posting_date, doc.company) doc.set_status('Completed') except Exception: @@ -103,7 +102,7 @@ def notify_error_to_stock_managers(doc, traceback): recipients = get_users_with_role("Stock Manager") if not recipients: get_users_with_role("System Manager") - + subject = _("Error while reposting item valuation") message = (_("Hi,") + "
" + _("An error has been appeared while reposting item valuation via {0}") @@ -112,4 +111,24 @@ def notify_error_to_stock_managers(doc, traceback): ) frappe.sendmail(recipients=recipients, subject=subject, message=message) +def repost_entries(): + riv_entries = get_repost_item_valuation_entries() + for row in riv_entries: + doc = frappe.get_cached_doc('Repost Item Valuation', row.name) + repost(doc) + + riv_entries = get_repost_item_valuation_entries() + if riv_entries: + return + + for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): + check_if_stock_and_account_balance_synced(today(), d.company) + +def get_repost_item_valuation_entries(): + date = add_to_date(today(), hours=-12) + + return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` + WHERE status != 'Completed' and creation <= %s and docstatus = 1 + ORDER BY timestamp(posting_date, posting_time) asc, creation asc + """, date, as_dict=1) \ No newline at end of file From 97aab3ac2162c80e990d95e2fd84200de9f8c94c Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 24 Mar 2021 16:44:49 +0530 Subject: [PATCH 63/92] fix: missing help links in navbar help dropdown --- erpnext/public/js/help_links.js | 244 ++++++++++++++++++++------------ 1 file changed, 152 insertions(+), 92 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 472c5374f5..66ff46405d 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -2,13 +2,13 @@ frappe.provide('frappe.help.help_links'); const docsUrl = 'https://erpnext.com/docs/'; -frappe.help.help_links['rename tool'] = [ +frappe.help.help_links['Form/Rename Tool'] = [ { label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' }, ] //Setup -frappe.help.help_links['user'] = [ +frappe.help.help_links['List/User'] = [ { label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' }, { label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' }, ] @@ -21,7 +21,7 @@ frappe.help.help_links['permission-manager'] = [ { label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' }, ] -frappe.help.help_links['system-settings'] = [ +frappe.help.help_links['Form/System Settings'] = [ { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' }, ] @@ -30,60 +30,64 @@ frappe.help.help_links['data-import-tool'] = [ { label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' }, ] -frappe.help.help_links['naming-series'] = [ +frappe.help.help_links['module_setup'] = [ + { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, +] + +frappe.help.help_links['Form/Naming Series'] = [ { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' }, { label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' }, ] -frappe.help.help_links['global-defaults'] = [ +frappe.help.help_links['Form/Global Defaults'] = [ { label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' }, ] -frappe.help.help_links['email-digest'] = [ +frappe.help.help_links['Form/Email Digest'] = [ { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, ] -frappe.help.help_links['print-heading'] = [ +frappe.help.help_links['List/Print Heading'] = [ { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, ] -frappe.help.help_links['letter-head'] = [ +frappe.help.help_links['List/Letter Head'] = [ { label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' }, ] -frappe.help.help_links['address-template'] = [ +frappe.help.help_links['List/Address Template'] = [ { label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' }, ] -frappe.help.help_links['terms-and-conditions'] = [ +frappe.help.help_links['List/Terms and Conditions'] = [ { label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' }, ] -frappe.help.help_links['cheque-print-template'] = [ +frappe.help.help_links['List/Cheque Print Template'] = [ { label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' }, ] -frappe.help.help_links['email-account'] = [ +frappe.help.help_links['List/Email Account'] = [ { label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' }, ] -frappe.help.help_links['notification'] = [ +frappe.help.help_links['List/Notification'] = [ { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, ] -frappe.help.help_links['notification'] = [ +frappe.help.help_links['Form/Notification'] = [ { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, ] -frappe.help.help_links['email-digest'] = [ +frappe.help.help_links['List/Email Digest'] = [ { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, ] -frappe.help.help_links['auto-email-report'] = [ +frappe.help.help_links['List/Auto Email Report'] = [ { label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' }, ] -frappe.help.help_links['print-settings'] = [ +frappe.help.help_links['Form/Print Settings'] = [ { label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, ] @@ -91,60 +95,66 @@ frappe.help.help_links['print-format-builder'] = [ { label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, ] -frappe.help.help_links['print-heading'] = [ +frappe.help.help_links['List/Print Heading'] = [ { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, ] //setup-integrations -frappe.help.help_links['paypal-settings'] = [ +frappe.help.help_links['Form/PayPal Settings'] = [ { label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' }, ] -frappe.help.help_links['razorpay-settings'] = [ +frappe.help.help_links['Form/Razorpay Settings'] = [ { label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' }, ] -frappe.help.help_links['dropbox-settings'] = [ +frappe.help.help_links['Form/Dropbox Settings'] = [ { label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' }, ] -frappe.help.help_links['ldap-settings'] = [ +frappe.help.help_links['Form/LDAP Settings'] = [ { label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' }, ] -frappe.help.help_links['stripe-settings'] = [ +frappe.help.help_links['Form/Stripe Settings'] = [ { label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' }, ] //Sales -frappe.help.help_links['quotation'] = [ +frappe.help.help_links['Form/Quotation'] = [ { label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' }, { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' }, { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, ] -frappe.help.help_links['customer'] = [ +frappe.help.help_links['List/Customer'] = [ { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, ] -frappe.help.help_links['customer'] = [ +frappe.help.help_links['Form/Customer'] = [ { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, ] -frappe.help.help_links['sales-taxes-and-charges-template'] = [ +frappe.help.help_links['List/Sales Taxes and Charges Template'] = [ { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, ] -frappe.help.help_links['sales-taxes-and-charges-template'] = [ +frappe.help.help_links['Form/Sales Taxes and Charges Template'] = [ { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, ] -frappe.help.help_links['sales-order'] = [ +frappe.help.help_links['List/Sales Order'] = [ + { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, + { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, + { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, +] + +frappe.help.help_links['Form/Sales Order'] = [ { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, @@ -154,34 +164,43 @@ frappe.help.help_links['sales-order'] = [ { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, ] -frappe.help.help_links['product-bundle'] = [ +frappe.help.help_links['Form/Product Bundle'] = [ { label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' }, ] -frappe.help.help_links['selling-settings'] = [ +frappe.help.help_links['Form/Selling Settings'] = [ { label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' }, ] //Buying -frappe.help.help_links['supplier'] = [ +frappe.help.help_links['List/Supplier'] = [ { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, ] -frappe.help.help_links['request-for-quotation'] = [ +frappe.help.help_links['Form/Supplier'] = [ + { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, +] + +frappe.help.help_links['Form/Request for Quotation'] = [ { label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' }, { label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' }, ] -frappe.help.help_links['supplier-quotation'] = [ +frappe.help.help_links['Form/Supplier Quotation'] = [ { label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' }, ] -frappe.help.help_links['buying-settings'] = [ +frappe.help.help_links['Form/Buying Settings'] = [ { label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' }, ] -frappe.help.help_links['purchase-order'] = [ +frappe.help.help_links['List/Purchase Order'] = [ + { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, + { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, +] + +frappe.help.help_links['Form/Purchase Order'] = [ { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, { label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' }, { label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' }, @@ -189,44 +208,44 @@ frappe.help.help_links['purchase-order'] = [ { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, ] -frappe.help.help_links['purchase-taxes-and-charges-template'] = [ +frappe.help.help_links['List/Purchase Taxes and Charges Template'] = [ { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, ] -frappe.help.help_links['pos-profile'] = [ +frappe.help.help_links['List/POS Profile'] = [ { label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, ] -frappe.help.help_links['price-list'] = [ +frappe.help.help_links['List/Price List'] = [ { label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' }, ] -frappe.help.help_links['authorization-rule'] = [ +frappe.help.help_links['List/Authorization Rule'] = [ { label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' }, ] -frappe.help.help_links['sms-settings'] = [ +frappe.help.help_links['Form/SMS Settings'] = [ { label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' }, ] -frappe.help.help_links['stock-reconciliation'] = [ +frappe.help.help_links['List/Stock Reconciliation'] = [ { label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' }, ] -frappe.help.help_links['territory/view/tree'] = [ +frappe.help.help_links['Tree/Territory'] = [ { label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' }, ] -frappe.help.help_links['dropbox-backup'] = [ +frappe.help.help_links['Form/Dropbox Backup'] = [ { label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' }, { label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' }, ] -frappe.help.help_links['workflow'] = [ +frappe.help.help_links['List/Workflow'] = [ { label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' }, ] -frappe.help.help_links['company'] = [ +frappe.help.help_links['List/Company'] = [ { label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' }, { label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' }, { label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' }, @@ -234,25 +253,25 @@ frappe.help.help_links['company'] = [ //Accounts -frappe.help.help_links['accounts'] = [ +frappe.help.help_links['modules/Accounts'] = [ { label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' }, { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' }, { label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' }, ] -frappe.help.help_links['account/view/tree'] = [ +frappe.help.help_links['Tree/Account'] = [ { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' }, { label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, ] -frappe.help.help_links['sales-invoice'] = [ +frappe.help.help_links['Form/Sales Invoice'] = [ { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, ] -frappe.help.help_links['sales-invoice'] = [ +frappe.help.help_links['List/Sales Invoice'] = [ { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, @@ -263,43 +282,43 @@ frappe.help.help_links['pos'] = [ { label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' }, ] -frappe.help.help_links['pos-profile'] = [ +frappe.help.help_links['List/POS Profile'] = [ { label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, ] -frappe.help.help_links['purchase-invoice'] = [ +frappe.help.help_links['List/Purchase Invoice'] = [ { label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, { label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, ] -frappe.help.help_links['journal-entry'] = [ +frappe.help.help_links['List/Journal Entry'] = [ { label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' }, { label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' }, { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, ] -frappe.help.help_links['payment-entry'] = [ +frappe.help.help_links['List/Payment Entry'] = [ { label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' }, ] -frappe.help.help_links['payment-request'] = [ +frappe.help.help_links['List/Payment Request'] = [ { label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' }, ] -frappe.help.help_links['asset'] = [ +frappe.help.help_links['List/Asset'] = [ { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, ] -frappe.help.help_links['asset-category'] = [ +frappe.help.help_links['List/Asset Category'] = [ { label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, ] -frappe.help.help_links['cost-center/view/tree'] = [ +frappe.help.help_links['Tree/Cost Center'] = [ { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, ] -frappe.help.help_links['item'] = [ +frappe.help.help_links['List/Item'] = [ { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, @@ -310,42 +329,61 @@ frappe.help.help_links['item'] = [ { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, ] -frappe.help.help_links['purchase-receipt'] = [ +frappe.help.help_links['Form/Item'] = [ + { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, + { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, + { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, + { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' }, + { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, + { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' }, + { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' }, + { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, +] + +frappe.help.help_links['List/Purchase Receipt'] = [ { label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, ] -frappe.help.help_links['delivery-note'] = [ +frappe.help.help_links['List/Delivery Note'] = [ { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, ] -frappe.help.help_links['delivery-note'] = [ +frappe.help.help_links['Form/Delivery Note'] = [ { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, ] -frappe.help.help_links['installation-note'] = [ +frappe.help.help_links['List/Installation Note'] = [ { label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' }, ] +frappe.help.help_links['Tree'] = [ + { label: 'Managing Tree Structure Masters', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, +] -frappe.help.help_links['budget'] = [ +frappe.help.help_links['List/Budget'] = [ { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, ] //Stock -frappe.help.help_links['material-request'] = [ +frappe.help.help_links['List/Material Request'] = [ { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, ] -frappe.help.help_links['stock-entry'] = [ +frappe.help.help_links['Form/Material Request'] = [ + { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, + { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, +] + +frappe.help.help_links['Form/Stock Entry'] = [ { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, { label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' }, { label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' }, @@ -353,114 +391,136 @@ frappe.help.help_links['stock-entry'] = [ { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, ] -frappe.help.help_links['warehouse/view/tree'] = [ +frappe.help.help_links['List/Stock Entry'] = [ + { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, +] + +frappe.help.help_links['Tree/Warehouse'] = [ { label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' }, ] -frappe.help.help_links['serial-no'] = [ +frappe.help.help_links['List/Serial No'] = [ { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, ] -frappe.help.help_links['batch'] = [ +frappe.help.help_links['Form/Serial No'] = [ + { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, +] + +frappe.help.help_links['Form/Batch'] = [ { label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' }, ] -frappe.help.help_links['packing-slip'] = [ +frappe.help.help_links['Form/Packing Slip'] = [ { label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' }, ] -frappe.help.help_links['quality-inspection'] = [ +frappe.help.help_links['Form/Quality Inspection'] = [ { label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' }, ] -frappe.help.help_links['landed-cost-voucher'] = [ +frappe.help.help_links['Form/Landed Cost Voucher'] = [ { label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' }, ] -frappe.help.help_links['item-group/view/tree'] = [ +frappe.help.help_links['Tree/Item Group'] = [ { label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' }, ] -frappe.help.help_links['item-attribute'] = [ +frappe.help.help_links['Form/Item Attribute'] = [ { label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' }, ] -frappe.help.help_links['uom'] = [ +frappe.help.help_links['Form/UOM'] = [ { label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' }, ] -frappe.help.help_links['stock-reconciliation'] = [ +frappe.help.help_links['Form/Stock Reconciliation'] = [ { label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' }, ] //CRM -frappe.help.help_links['lead'] = [ +frappe.help.help_links['Form/Lead'] = [ { label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' }, ] -frappe.help.help_links['opportunity'] = [ +frappe.help.help_links['Form/Opportunity'] = [ { label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' }, ] -frappe.help.help_links['address'] = [ +frappe.help.help_links['Form/Address'] = [ { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' }, ] -frappe.help.help_links['contact'] = [ +frappe.help.help_links['Form/Contact'] = [ { label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' }, ] -frappe.help.help_links['newsletter'] = [ +frappe.help.help_links['Form/Newsletter'] = [ { label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' }, ] -frappe.help.help_links['campaign'] = [ +frappe.help.help_links['Form/Campaign'] = [ { label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' }, ] -frappe.help.help_links['sales-person/view/tree'] = [ +frappe.help.help_links['Tree/Sales Person'] = [ { label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' }, ] -frappe.help.help_links['sales-person'] = [ +frappe.help.help_links['Form/Sales Person'] = [ { label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' }, ] +//Support + +frappe.help.help_links['List/Feedback Trigger'] = [ + { label: 'Feedback Trigger', url: docsUrl + 'user/manual/en/setting-up/feedback/setting-up-feedback' }, +] + +frappe.help.help_links['List/Feedback Request'] = [ + { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, +] + +frappe.help.help_links['List/Feedback Request'] = [ + { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, +] + //Manufacturing -frappe.help.help_links['bom'] = [ +frappe.help.help_links['Form/BOM'] = [ { label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' }, { label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' }, ] -frappe.help.help_links['work-order'] = [ +frappe.help.help_links['Form/Work Order'] = [ { label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' }, ] -frappe.help.help_links['workstation'] = [ +frappe.help.help_links['Form/Workstation'] = [ { label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' }, ] -frappe.help.help_links['operation'] = [ +frappe.help.help_links['Form/Operation'] = [ { label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' }, ] -frappe.help.help_links['bom-update-tool'] = [ +frappe.help.help_links['Form/BOM Update Tool'] = [ { label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' }, ] //Customize -frappe.help.help_links['customize-form'] = [ +frappe.help.help_links['Form/Customize Form'] = [ { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, { label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' }, ] -frappe.help.help_links['custom-field'] = [ +frappe.help.help_links['Form/Custom Field'] = [ { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, ] -frappe.help.help_links['custom-field'] = [ +frappe.help.help_links['Form/Custom Field'] = [ { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, ] From 194dd12882735affc29d1f16c39b3161e48818c9 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 24 Mar 2021 16:45:32 +0530 Subject: [PATCH 64/92] fix: re-execute add_standard_navbar_items patch - check that same items aren't appended again --- erpnext/patches.txt | 2 +- erpnext/setup/install.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7016ecdd96..7af5ad1c76 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -720,7 +720,7 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v13_0.move_branch_code_to_bank_account erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes -erpnext.patches.v13_0.add_standard_navbar_items #4 +erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24 erpnext.patches.v13_0.stock_entry_enhancements erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 1e424dd4b3..82f191d0b7 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -142,13 +142,15 @@ def add_standard_navbar_items(): } ] - current_nabvar_items = navbar_settings.help_dropdown + current_navbar_items = navbar_settings.help_dropdown navbar_settings.set('help_dropdown', []) for item in erpnext_navbar_items: - navbar_settings.append('help_dropdown', item) + current_labels = [item.get('item_label') for item in current_navbar_items] + if not item.get('item_label') in current_labels: + navbar_settings.append('help_dropdown', item) - for item in current_nabvar_items: + for item in current_navbar_items: navbar_settings.append('help_dropdown', { 'item_label': item.item_label, 'item_type': item.item_type, From dd39a6b47cb29d6c9b03798cc698e874c078a220 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 24 Mar 2021 17:56:24 +0530 Subject: [PATCH 65/92] fix(pos): mode of payments disappear on loading draft pos invoice (#24917) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 6 ++++++ erpnext/selling/page/point_of_sale/pos_controller.js | 1 + 2 files changed, 7 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 76e00923c4..92fa5ef19a 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -376,6 +376,12 @@ class POSInvoice(SalesInvoice): "campaign": profile.get("campaign"), "allow_print_before_pay": profile.get("allow_print_before_pay") } + + def reset_mode_of_payments(self): + if self.pos_profile: + pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile) + update_multi_mode_option(self, pos_profile) + self.paid_amount = 0 def set_account_for_mode_of_payment(self): self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default] diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 278821e392..9e3c9a5656 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -397,6 +397,7 @@ erpnext.PointOfSale.Controller = class { this.recent_order_list.toggle_component(false); frappe.run_serially([ () => this.frm.refresh(name), + () => this.frm.call('reset_mode_of_payments'), () => this.cart.load_invoice(), () => this.item_selector.toggle_component(true) ]); From 8a87ffc8f6bc025ff65e71feeef24479ad69fc9e Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Wed, 24 Mar 2021 19:52:39 +0530 Subject: [PATCH 66/92] fix: POS print receipt (#24924) * fix: POS print receipt * fix: print recepit dialog --- .../point_of_sale/pos_past_order_summary.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index be2b769a8a..b10a9e33c5 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -64,10 +64,7 @@ erpnext.PointOfSale.PastOrderSummary = class { {fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'} ], primary_action: () => { - const frm = this.events.get_frm(); - frm.doc = this.doc; - frm.print_preview.lang_code = frm.doc.language; - frm.print_preview.printit(true); + this.print_receipt(); }, primary_action_label: __('Print'), }); @@ -192,13 +189,21 @@ erpnext.PointOfSale.PastOrderSummary = class { }); this.$summary_container.on('click', '.print-btn', () => { - const frm = this.events.get_frm(); - frm.doc = this.doc; - frm.print_preview.lang_code = frm.doc.language; - frm.print_preview.printit(true); + this.print_receipt(); }); } + print_receipt() { + const frm = this.events.get_frm(); + frappe.utils.print( + frm.doctype, + frm.docname, + frm.pos_print_format, + frm.doc.letter_head, + frm.doc.language || frappe.boot.lang + ); + } + attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`); From 1a6e6320cf6b62c6ce08880b454d1409b499f4c9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 24 Mar 2021 21:26:04 +0530 Subject: [PATCH 67/92] Revert "fix: validating party currency with doc currency (#24318)" This reverts commit 02142c41bb8acb52a9383b94bac5b631e3d66100. --- erpnext/controllers/accounts_controller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e8f27c31fe..12a81c7887 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -829,10 +829,10 @@ class AccountsController(TransactionBase): party_account_currency = get_party_account_currency(party_type, party, self.company) if (party_account_currency - and (self.currency != party_account_currency - and self.currency != self.company_currency)): + and party_account_currency != self.company_currency + and self.currency != party_account_currency): frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") - .format(party_type, party, frappe.bold(party_account_currency), InvalidCurrency)) + .format(party_type, party, party_account_currency), InvalidCurrency) # Note: not validating with gle account because we don't have the account # at quotation / sales order level and we shouldn't stop someone @@ -898,7 +898,7 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if self.company_currency == self.currency: + if party_account_currency == self.company_currency: grand_total = self.get("base_rounded_total") or self.base_grand_total else: grand_total = self.get("rounded_total") or self.grand_total @@ -959,7 +959,7 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): total += flt(d.payment_amount) - if self.company_currency == self.currency: + if party_account_currency == self.company_currency: total = flt(total, self.precision("base_grand_total")) grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) else: @@ -1394,7 +1394,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) def get_new_child_item(item_row): - child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" + child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) def validate_quantity(child_item, d): From b4e842e25e06a085ec031b06d3e9a9165cc8e252 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 25 Mar 2021 11:45:57 +0530 Subject: [PATCH 68/92] fix: opportunity-quotation mapping order status (#25001) --- erpnext/crm/doctype/opportunity/opportunity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 47b05f306b..0522ace1e5 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -248,7 +248,6 @@ def make_quotation(source_name, target_doc=None): "doctype": "Quotation", "field_map": { "opportunity_from": "quotation_to", - "opportunity_type": "order_type", "name": "enq_no", } }, From f8b44e05c4c04f67c87632d939b74449c3ee7464 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Thu, 25 Mar 2021 11:48:29 +0530 Subject: [PATCH 69/92] refactor: Add student mobile number to student attendance (#24990) To setup SMS alert notification for student attendance we need student mobile number in this doctype. Signed-off-by: Syed Mujeer Hashmi --- .../student_attendance/student_attendance.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/education/doctype/student_attendance/student_attendance.json b/erpnext/education/doctype/student_attendance/student_attendance.json index 55384b9e53..e6e46d1c1b 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance.json +++ b/erpnext/education/doctype/student_attendance/student_attendance.json @@ -10,6 +10,7 @@ "naming_series", "student", "student_name", + "student_mobile_number", "course_schedule", "student_group", "column_break_3", @@ -93,11 +94,19 @@ "options": "Student Attendance", "print_hide": 1, "read_only": 1 + }, + { + "fetch_from": "student.student_mobile_number", + "fieldname": "student_mobile_number", + "fieldtype": "Read Only", + "label": "Student Mobile Number", + "options": "Phone" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-07-08 13:55:42.580181", + "modified": "2021-03-24 00:02:11.005895", "modified_by": "Administrator", "module": "Education", "name": "Student Attendance", From 52701dcbbc333bb3b2a56f31d00777ecff181f8f Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 25 Mar 2021 12:56:33 +0530 Subject: [PATCH 70/92] fix: patch regional fields for old companies (#24988) --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/setup_uae_vat_fields.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 erpnext/patches/v13_0/setup_uae_vat_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 36751843e2..5ef04a3fa0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -759,5 +759,6 @@ erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae +erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py new file mode 100644 index 0000000000..d7a5c682df --- /dev/null +++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py @@ -0,0 +1,12 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from erpnext.regional.united_arab_emirates.setup import setup + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'}) + if not company: + return + + setup() \ No newline at end of file From b2b12f303e216aaa5a26b2c6fad4ff1de494d4e4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 25 Mar 2021 14:48:08 +0530 Subject: [PATCH 71/92] fix(Payroll): Exchange Rate not getting set in Salary Slip --- .../doctype/salary_slip/salary_slip.js | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index 7460c75227..d5278393a1 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -74,43 +74,46 @@ frappe.ui.form.on("Salary Slip", { if (!frm.doc.letter_head && company.default_letter_head) { frm.set_value('letter_head', company.default_letter_head); } + }, + + currency: function(frm) { frm.trigger("set_dynamic_labels"); }, set_dynamic_labels: function(frm) { var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency"); - frappe.run_serially([ - () => frm.events.set_exchange_rate(frm, company_currency), - () => frm.events.change_form_labels(frm, company_currency), - () => frm.events.change_grid_labels(frm), - () => frm.refresh_fields() - ]); + if (frm.doc.employee && frm.doc.currency) { + frappe.run_serially([ + () => frm.events.set_exchange_rate(frm, company_currency), + () => frm.events.change_form_labels(frm, company_currency), + () => frm.events.change_grid_labels(frm), + () => frm.refresh_fields() + ]); + } }, set_exchange_rate: function(frm, company_currency) { - if (frm.doc.docstatus === 0) { - if (frm.doc.currency) { - var from_currency = frm.doc.currency; - if (from_currency != company_currency) { - frm.events.hide_loan_section(frm); - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: from_currency, - to_currency: company_currency, - }, - callback: function(r) { - frm.set_value("exchange_rate", flt(r.message)); - frm.set_df_property('exchange_rate', 'hidden', 0); - frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency - + " = [?] " + company_currency); - } - }); - } else { - frm.set_value("exchange_rate", 1.0); - frm.set_df_property('exchange_rate', 'hidden', 1); - frm.set_df_property("exchange_rate", "description", "" ); - } + if (frm.doc.currency) { + var from_currency = frm.doc.currency; + if (from_currency != company_currency) { + frm.events.hide_loan_section(frm); + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: from_currency, + to_currency: company_currency, + }, + callback: function(r) { + frm.set_value("exchange_rate", flt(r.message)); + frm.set_df_property("exchange_rate", "hidden", 0); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + + " = [?] " + company_currency); + } + }); + } else { + frm.set_value("exchange_rate", 1.0); + frm.set_df_property("exchange_rate", "hidden", 1); + frm.set_df_property("exchange_rate", "description", ""); } } }, From dd2a315d06e45b3be017bdfd8681ab73f09f87da Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 26 Mar 2021 14:35:47 +0530 Subject: [PATCH 72/92] style: fix formatting --- erpnext/public/js/help_links.js | 1319 +++++++++++++++++++++---------- 1 file changed, 922 insertions(+), 397 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 66ff46405d..e78992302f 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -1,526 +1,1051 @@ -frappe.provide('frappe.help.help_links'); +frappe.provide("frappe.help.help_links"); -const docsUrl = 'https://erpnext.com/docs/'; +const docsUrl = "https://erpnext.com/docs/"; -frappe.help.help_links['Form/Rename Tool'] = [ - { label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' }, -] +frappe.help.help_links["Form/Rename Tool"] = [ + { + label: "Bulk Rename", + url: docsUrl + "user/manual/en/setting-up/data/bulk-rename", + }, +]; //Setup -frappe.help.help_links['List/User'] = [ - { label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' }, - { label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' }, -] +frappe.help.help_links["List/User"] = [ + { + label: "New User", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/adding-users", + }, + { + label: "Rename User", + url: docsUrl + "user/manual/en/setting-up/articles/rename-user", + }, +]; -frappe.help.help_links['permission-manager'] = [ - { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, - { label: 'Managing Perm Level in Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/articles/managing-perm-level' }, - { label: 'User Permissions', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/user-permissions' }, - { label: 'Sharing', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/sharing' }, - { label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' }, -] +frappe.help.help_links["permission-manager"] = [ + { + label: "Role Permissions Manager", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/role-based-permissions", + }, + { + label: "Managing Perm Level in Permissions Manager", + url: docsUrl + "user/manual/en/setting-up/articles/managing-perm-level", + }, + { + label: "User Permissions", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/user-permissions", + }, + { + label: "Sharing", + url: + docsUrl + "user/manual/en/setting-up/users-and-permissions/sharing", + }, + { + label: "Password", + url: docsUrl + "user/manual/en/setting-up/articles/change-password", + }, +]; -frappe.help.help_links['Form/System Settings'] = [ - { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' }, -] +frappe.help.help_links["Form/System Settings"] = [ + { + label: "Naming Series", + url: docsUrl + "user/manual/en/setting-up/settings/system-settings", + }, +]; -frappe.help.help_links['data-import-tool'] = [ - { label: 'Importing and Exporting Data', url: docsUrl + 'user/manual/en/setting-up/data/data-import-tool' }, - { label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' }, -] +frappe.help.help_links["data-import-tool"] = [ + { + label: "Importing and Exporting Data", + url: docsUrl + "user/manual/en/setting-up/data/data-import-tool", + }, + { + label: "Overwriting Data from Data Import Tool", + url: + docsUrl + + "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool", + }, +]; -frappe.help.help_links['module_setup'] = [ - { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, -] +frappe.help.help_links["module_setup"] = [ + { + label: "Role Permissions Manager", + url: + docsUrl + + "user/manual/en/setting-up/users-and-permissions/role-based-permissions", + }, +]; -frappe.help.help_links['Form/Naming Series'] = [ - { label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' }, - { label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' }, -] +frappe.help.help_links["Form/Naming Series"] = [ + { + label: "Naming Series", + url: docsUrl + "user/manual/en/setting-up/settings/naming-series", + }, + { + label: "Setting the Current Value for Naming Series", + url: + docsUrl + + "user/manual/en/setting-up/articles/naming-series-current-value", + }, +]; -frappe.help.help_links['Form/Global Defaults'] = [ - { label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' }, -] +frappe.help.help_links["Form/Global Defaults"] = [ + { + label: "Global Settings", + url: docsUrl + "user/manual/en/setting-up/settings/global-defaults", + }, +]; -frappe.help.help_links['Form/Email Digest'] = [ - { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, -] +frappe.help.help_links["Form/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; -frappe.help.help_links['List/Print Heading'] = [ - { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, -] +frappe.help.help_links["List/Print Heading"] = [ + { + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", + }, +]; -frappe.help.help_links['List/Letter Head'] = [ - { label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' }, -] +frappe.help.help_links["List/Letter Head"] = [ + { + label: "Letter Head", + url: docsUrl + "user/manual/en/setting-up/print/letter-head", + }, +]; -frappe.help.help_links['List/Address Template'] = [ - { label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' }, -] +frappe.help.help_links["List/Address Template"] = [ + { + label: "Address Template", + url: docsUrl + "user/manual/en/setting-up/print/address-template", + }, +]; -frappe.help.help_links['List/Terms and Conditions'] = [ - { label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' }, -] +frappe.help.help_links["List/Terms and Conditions"] = [ + { + label: "Terms and Conditions", + url: docsUrl + "user/manual/en/setting-up/print/terms-and-conditions", + }, +]; -frappe.help.help_links['List/Cheque Print Template'] = [ - { label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' }, -] +frappe.help.help_links["List/Cheque Print Template"] = [ + { + label: "Cheque Print Template", + url: docsUrl + "user/manual/en/setting-up/print/cheque-print-template", + }, +]; -frappe.help.help_links['List/Email Account'] = [ - { label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' }, -] +frappe.help.help_links["List/Email Account"] = [ + { + label: "Email Account", + url: docsUrl + "user/manual/en/setting-up/email/email-account", + }, +]; -frappe.help.help_links['List/Notification'] = [ - { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, -] +frappe.help.help_links["List/Notification"] = [ + { + label: "Notification", + url: docsUrl + "user/manual/en/setting-up/email/notifications", + }, +]; -frappe.help.help_links['Form/Notification'] = [ - { label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' }, -] +frappe.help.help_links["Form/Notification"] = [ + { + label: "Notification", + url: docsUrl + "user/manual/en/setting-up/email/notifications", + }, +]; -frappe.help.help_links['List/Email Digest'] = [ - { label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' }, -] +frappe.help.help_links["List/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; -frappe.help.help_links['List/Auto Email Report'] = [ - { label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' }, -] +frappe.help.help_links["List/Auto Email Report"] = [ + { + label: "Auto Email Reports", + url: docsUrl + "user/manual/en/setting-up/email/email-reports", + }, +]; -frappe.help.help_links['Form/Print Settings'] = [ - { label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, -] +frappe.help.help_links["Form/Print Settings"] = [ + { + label: "Print Settings", + url: docsUrl + "user/manual/en/setting-up/print/print-settings", + }, +]; -frappe.help.help_links['print-format-builder'] = [ - { label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' }, -] +frappe.help.help_links["print-format-builder"] = [ + { + label: "Print Format Builder", + url: docsUrl + "user/manual/en/setting-up/print/print-settings", + }, +]; -frappe.help.help_links['List/Print Heading'] = [ - { label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' }, -] +frappe.help.help_links["List/Print Heading"] = [ + { + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", + }, +]; //setup-integrations -frappe.help.help_links['Form/PayPal Settings'] = [ - { label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' }, -] +frappe.help.help_links["Form/PayPal Settings"] = [ + { + label: "PayPal Settings", + url: + docsUrl + + "user/manual/en/setting-up/integrations/paypal-integration", + }, +]; -frappe.help.help_links['Form/Razorpay Settings'] = [ - { label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' }, -] +frappe.help.help_links["Form/Razorpay Settings"] = [ + { + label: "Razorpay Settings", + url: + docsUrl + + "user/manual/en/setting-up/integrations/razorpay-integration", + }, +]; -frappe.help.help_links['Form/Dropbox Settings'] = [ - { label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' }, -] +frappe.help.help_links["Form/Dropbox Settings"] = [ + { + label: "Dropbox Settings", + url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup", + }, +]; -frappe.help.help_links['Form/LDAP Settings'] = [ - { label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' }, -] +frappe.help.help_links["Form/LDAP Settings"] = [ + { + label: "LDAP Settings", + url: + docsUrl + "user/manual/en/setting-up/integrations/ldap-integration", + }, +]; -frappe.help.help_links['Form/Stripe Settings'] = [ - { label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' }, -] +frappe.help.help_links["Form/Stripe Settings"] = [ + { + label: "Stripe Settings", + url: + docsUrl + + "user/manual/en/setting-up/integrations/stripe-integration", + }, +]; //Sales -frappe.help.help_links['Form/Quotation'] = [ - { label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' }, - { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, - { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' }, - { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, -] +frappe.help.help_links["Form/Quotation"] = [ + { label: "Quotation", url: docsUrl + "user/manual/en/selling/quotation" }, + { + label: "Applying Discount", + url: docsUrl + "user/manual/en/selling/articles/applying-discount", + }, + { + label: "Sales Person", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", + }, + { + label: "Applying Margin", + url: docsUrl + "user/manual/en/selling/articles/adding-margin", + }, +]; -frappe.help.help_links['List/Customer'] = [ - { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, - { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, -] +frappe.help.help_links["List/Customer"] = [ + { label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" }, + { + label: "Credit Limit", + url: docsUrl + "user/manual/en/accounts/credit-limit", + }, +]; -frappe.help.help_links['Form/Customer'] = [ - { label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' }, - { label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' }, -] +frappe.help.help_links["Form/Customer"] = [ + { label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" }, + { + label: "Credit Limit", + url: docsUrl + "user/manual/en/accounts/credit-limit", + }, +]; -frappe.help.help_links['List/Sales Taxes and Charges Template'] = [ - { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, -] +frappe.help.help_links["List/Sales Taxes and Charges Template"] = [ + { + label: "Setting Up Taxes", + url: docsUrl + "user/manual/en/setting-up/setting-up-taxes", + }, +]; -frappe.help.help_links['Form/Sales Taxes and Charges Template'] = [ - { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, -] +frappe.help.help_links["Form/Sales Taxes and Charges Template"] = [ + { + label: "Setting Up Taxes", + url: docsUrl + "user/manual/en/setting-up/setting-up-taxes", + }, +]; -frappe.help.help_links['List/Sales Order'] = [ - { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, - { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, - { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, -] +frappe.help.help_links["List/Sales Order"] = [ + { + label: "Sales Order", + url: docsUrl + "user/manual/en/selling/sales-order", + }, + { + label: "Recurring Sales Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, + { + label: "Applying Discount", + url: docsUrl + "user/manual/en/selling/articles/applying-discount", + }, +]; -frappe.help.help_links['Form/Sales Order'] = [ - { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' }, - { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, - { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' }, - { label: 'Drop Shipping', url: docsUrl + 'user/manual/en/selling/articles/drop-shipping' }, - { label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' }, - { label: 'Close Sales Order', url: docsUrl + 'user/manual/en/selling/articles/close-sales-order' }, - { label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' }, -] +frappe.help.help_links["Form/Sales Order"] = [ + { + label: "Sales Order", + url: docsUrl + "user/manual/en/selling/sales-order", + }, + { + label: "Recurring Sales Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, + { + label: "Applying Discount", + url: docsUrl + "user/manual/en/selling/articles/applying-discount", + }, + { + label: "Drop Shipping", + url: docsUrl + "user/manual/en/selling/articles/drop-shipping", + }, + { + label: "Sales Person", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", + }, + { + label: "Close Sales Order", + url: docsUrl + "user/manual/en/selling/articles/close-sales-order", + }, + { + label: "Applying Margin", + url: docsUrl + "user/manual/en/selling/articles/adding-margin", + }, +]; -frappe.help.help_links['Form/Product Bundle'] = [ - { label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' }, -] +frappe.help.help_links["Form/Product Bundle"] = [ + { + label: "Product Bundle", + url: docsUrl + "user/manual/en/selling/setup/product-bundle", + }, +]; -frappe.help.help_links['Form/Selling Settings'] = [ - { label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' }, -] +frappe.help.help_links["Form/Selling Settings"] = [ + { + label: "Selling Settings", + url: docsUrl + "user/manual/en/selling/setup/selling-settings", + }, +]; //Buying -frappe.help.help_links['List/Supplier'] = [ - { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, -] +frappe.help.help_links["List/Supplier"] = [ + { label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" }, +]; -frappe.help.help_links['Form/Supplier'] = [ - { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' }, -] +frappe.help.help_links["Form/Supplier"] = [ + { label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" }, +]; -frappe.help.help_links['Form/Request for Quotation'] = [ - { label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' }, - { label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' }, -] +frappe.help.help_links["Form/Request for Quotation"] = [ + { + label: "Request for Quotation", + url: docsUrl + "user/manual/en/buying/request-for-quotation", + }, + { + label: "RFQ Video", + url: docsUrl + "user/videos/learn/request-for-quotation.html", + }, +]; -frappe.help.help_links['Form/Supplier Quotation'] = [ - { label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' }, -] +frappe.help.help_links["Form/Supplier Quotation"] = [ + { + label: "Supplier Quotation", + url: docsUrl + "user/manual/en/buying/supplier-quotation", + }, +]; -frappe.help.help_links['Form/Buying Settings'] = [ - { label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' }, -] +frappe.help.help_links["Form/Buying Settings"] = [ + { + label: "Buying Settings", + url: docsUrl + "user/manual/en/buying/setup/buying-settings", + }, +]; -frappe.help.help_links['List/Purchase Order'] = [ - { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, - { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["List/Purchase Order"] = [ + { + label: "Purchase Order", + url: docsUrl + "user/manual/en/buying/purchase-order", + }, + { + label: "Recurring Purchase Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['Form/Purchase Order'] = [ - { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' }, - { label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' }, - { label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' }, - { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, - { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, -] +frappe.help.help_links["Form/Purchase Order"] = [ + { + label: "Purchase Order", + url: docsUrl + "user/manual/en/buying/purchase-order", + }, + { + label: "Item UoM", + url: + docsUrl + + "user/manual/en/buying/articles/purchasing-in-different-unit", + }, + { + label: "Supplier Item Code", + url: + docsUrl + + "user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item", + }, + { + label: "Recurring Purchase Order", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, + { + label: "Subcontracting", + url: docsUrl + "user/manual/en/manufacturing/subcontracting", + }, +]; -frappe.help.help_links['List/Purchase Taxes and Charges Template'] = [ - { label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' }, -] +frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [ + { + label: "Setting Up Taxes", + url: docsUrl + "user/manual/en/setting-up/setting-up-taxes", + }, +]; -frappe.help.help_links['List/POS Profile'] = [ - { label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, -] +frappe.help.help_links["List/POS Profile"] = [ + { + label: "POS Profile", + url: docsUrl + "user/manual/en/setting-up/pos-setting", + }, +]; -frappe.help.help_links['List/Price List'] = [ - { label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' }, -] +frappe.help.help_links["List/Price List"] = [ + { + label: "Price List", + url: docsUrl + "user/manual/en/setting-up/price-lists", + }, +]; -frappe.help.help_links['List/Authorization Rule'] = [ - { label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' }, -] +frappe.help.help_links["List/Authorization Rule"] = [ + { + label: "Authorization Rule", + url: docsUrl + "user/manual/en/setting-up/authorization-rule", + }, +]; -frappe.help.help_links['Form/SMS Settings'] = [ - { label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' }, -] +frappe.help.help_links["Form/SMS Settings"] = [ + { + label: "SMS Settings", + url: docsUrl + "user/manual/en/setting-up/sms-setting", + }, +]; -frappe.help.help_links['List/Stock Reconciliation'] = [ - { label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' }, -] +frappe.help.help_links["List/Stock Reconciliation"] = [ + { + label: "Stock Reconciliation", + url: + docsUrl + + "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item", + }, +]; -frappe.help.help_links['Tree/Territory'] = [ - { label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' }, -] +frappe.help.help_links["Tree/Territory"] = [ + { + label: "Territory", + url: docsUrl + "user/manual/en/setting-up/territory", + }, +]; -frappe.help.help_links['Form/Dropbox Backup'] = [ - { label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' }, - { label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' }, -] +frappe.help.help_links["Form/Dropbox Backup"] = [ + { + label: "Dropbox Backup", + url: docsUrl + "user/manual/en/setting-up/third-party-backups", + }, + { + label: "Setting Up Dropbox Backup", + url: + docsUrl + + "user/manual/en/setting-up/articles/setting-up-dropbox-backups", + }, +]; -frappe.help.help_links['List/Workflow'] = [ - { label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' }, -] +frappe.help.help_links["List/Workflow"] = [ + { label: "Workflow", url: docsUrl + "user/manual/en/setting-up/workflows" }, +]; -frappe.help.help_links['List/Company'] = [ - { label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' }, - { label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' }, - { label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' }, -] +frappe.help.help_links["List/Company"] = [ + { + label: "Company", + url: docsUrl + "user/manual/en/setting-up/company-setup", + }, + { + label: "Managing Multiple Companies", + url: + docsUrl + + "user/manual/en/setting-up/articles/managing-multiple-companies", + }, + { + label: "Delete All Related Transactions for a Company", + url: + docsUrl + + "user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions", + }, +]; //Accounts -frappe.help.help_links['modules/Accounts'] = [ - { label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' }, - { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' }, - { label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' }, -] +frappe.help.help_links["modules/Accounts"] = [ + { + label: "Introduction to Accounts", + url: docsUrl + "user/manual/en/accounts/", + }, + { + label: "Chart of Accounts", + url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html", + }, + { + label: "Multi Currency Accounting", + url: docsUrl + "user/manual/en/accounts/multi-currency-accounting", + }, +]; -frappe.help.help_links['Tree/Account'] = [ - { label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' }, - { label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, -] +frappe.help.help_links["Tree/Account"] = [ + { + label: "Chart of Accounts", + url: docsUrl + "user/manual/en/accounts/chart-of-accounts", + }, + { + label: "Managing Tree Mastes", + url: + docsUrl + + "user/manual/en/setting-up/articles/managing-tree-structure-masters", + }, +]; -frappe.help.help_links['Form/Sales Invoice'] = [ - { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, - { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["Form/Sales Invoice"] = [ + { + label: "Sales Invoice", + url: docsUrl + "user/manual/en/accounts/sales-invoice", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, + { + label: "Recurring Sales Invoice", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['List/Sales Invoice'] = [ - { label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, - { label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["List/Sales Invoice"] = [ + { + label: "Sales Invoice", + url: docsUrl + "user/manual/en/accounts/sales-invoice", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, + { + label: "Recurring Sales Invoice", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['pos'] = [ - { label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' }, -] +frappe.help.help_links["pos"] = [ + { + label: "Point of Sale Invoice", + url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice", + }, +]; -frappe.help.help_links['List/POS Profile'] = [ - { label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' }, -] +frappe.help.help_links["List/POS Profile"] = [ + { + label: "Point of Sale Profile", + url: docsUrl + "user/manual/en/setting-up/pos-setting", + }, +]; -frappe.help.help_links['List/Purchase Invoice'] = [ - { label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, - { label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' }, -] +frappe.help.help_links["List/Purchase Invoice"] = [ + { + label: "Purchase Invoice", + url: docsUrl + "user/manual/en/accounts/purchase-invoice", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, + { + label: "Recurring Purchase Invoice", + url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + }, +]; -frappe.help.help_links['List/Journal Entry'] = [ - { label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' }, - { label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' }, - { label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' }, -] +frappe.help.help_links["List/Journal Entry"] = [ + { + label: "Journal Entry", + url: docsUrl + "user/manual/en/accounts/journal-entry", + }, + { + label: "Advance Payment Entry", + url: docsUrl + "user/manual/en/accounts/advance-payment-entry", + }, + { + label: "Accounts Opening Balance", + url: docsUrl + "user/manual/en/accounts/opening-accounts", + }, +]; -frappe.help.help_links['List/Payment Entry'] = [ - { label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' }, -] +frappe.help.help_links["List/Payment Entry"] = [ + { + label: "Payment Entry", + url: docsUrl + "user/manual/en/accounts/payment-entry", + }, +]; -frappe.help.help_links['List/Payment Request'] = [ - { label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' }, -] +frappe.help.help_links["List/Payment Request"] = [ + { + label: "Payment Request", + url: docsUrl + "user/manual/en/accounts/payment-request", + }, +]; -frappe.help.help_links['List/Asset'] = [ - { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, -] +frappe.help.help_links["List/Asset"] = [ + { + label: "Managing Fixed Assets", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, +]; -frappe.help.help_links['List/Asset Category'] = [ - { label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, -] +frappe.help.help_links["List/Asset Category"] = [ + { + label: "Asset Category", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, +]; -frappe.help.help_links['Tree/Cost Center'] = [ - { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, -] +frappe.help.help_links["Tree/Cost Center"] = [ + { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, +]; -frappe.help.help_links['List/Item'] = [ - { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, - { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' }, - { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, - { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' }, - { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' }, - { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, -] +frappe.help.help_links["List/Item"] = [ + { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, + { + label: "Item Price", + url: docsUrl + "user/manual/en/stock/item/item-price", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Item Wise Taxation", + url: docsUrl + "user/manual/en/accounts/item-wise-taxation", + }, + { + label: "Managing Fixed Assets", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, + { + label: "Item Codification", + url: docsUrl + "user/manual/en/stock/item/item-codification", + }, + { + label: "Item Variants", + url: docsUrl + "user/manual/en/stock/item/item-variants", + }, + { + label: "Item Valuation", + url: + docsUrl + + "user/manual/en/stock/item/item-valuation-fifo-and-moving-average", + }, +]; -frappe.help.help_links['Form/Item'] = [ - { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' }, - { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' }, - { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' }, - { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' }, - { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' }, - { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' }, -] +frappe.help.help_links["Form/Item"] = [ + { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, + { + label: "Item Price", + url: docsUrl + "user/manual/en/stock/item/item-price", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Item Wise Taxation", + url: docsUrl + "user/manual/en/accounts/item-wise-taxation", + }, + { + label: "Managing Fixed Assets", + url: docsUrl + "user/manual/en/accounts/managing-fixed-assets", + }, + { + label: "Item Codification", + url: docsUrl + "user/manual/en/stock/item/item-codification", + }, + { + label: "Item Variants", + url: docsUrl + "user/manual/en/stock/item/item-variants", + }, + { + label: "Item Valuation", + url: + docsUrl + + "user/manual/en/stock/item/item-valuation-fifo-and-moving-average", + }, +]; -frappe.help.help_links['List/Purchase Receipt'] = [ - { label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, -] +frappe.help.help_links["List/Purchase Receipt"] = [ + { + label: "Purchase Receipt", + url: docsUrl + "user/manual/en/stock/purchase-receipt", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, +]; -frappe.help.help_links['List/Delivery Note'] = [ - { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, -] +frappe.help.help_links["List/Delivery Note"] = [ + { + label: "Delivery Note", + url: docsUrl + "user/manual/en/stock/delivery-note", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, +]; -frappe.help.help_links['Form/Delivery Note'] = [ - { label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' }, - { label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' }, - { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' }, - { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, -] +frappe.help.help_links["Form/Delivery Note"] = [ + { + label: "Delivery Note", + url: docsUrl + "user/manual/en/stock/delivery-note", + }, + { + label: "Sales Return", + url: docsUrl + "user/manual/en/stock/sales-return", + }, + { + label: "Barcode", + url: + docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", + }, + { + label: "Subcontracting", + url: docsUrl + "user/manual/en/manufacturing/subcontracting", + }, +]; -frappe.help.help_links['List/Installation Note'] = [ - { label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' }, -] +frappe.help.help_links["List/Installation Note"] = [ + { + label: "Installation Note", + url: docsUrl + "user/manual/en/stock/installation-note", + }, +]; +frappe.help.help_links["Tree"] = [ + { + label: "Managing Tree Structure Masters", + url: + docsUrl + + "user/manual/en/setting-up/articles/managing-tree-structure-masters", + }, +]; -frappe.help.help_links['Tree'] = [ - { label: 'Managing Tree Structure Masters', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' }, -] - -frappe.help.help_links['List/Budget'] = [ - { label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' }, -] +frappe.help.help_links["List/Budget"] = [ + { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, +]; //Stock -frappe.help.help_links['List/Material Request'] = [ - { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, - { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, -] +frappe.help.help_links["List/Material Request"] = [ + { + label: "Material Request", + url: docsUrl + "user/manual/en/stock/material-request", + }, + { + label: "Auto-creation of Material Request", + url: + docsUrl + + "user/manual/en/stock/articles/auto-creation-of-material-request", + }, +]; -frappe.help.help_links['Form/Material Request'] = [ - { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' }, - { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' }, -] +frappe.help.help_links["Form/Material Request"] = [ + { + label: "Material Request", + url: docsUrl + "user/manual/en/stock/material-request", + }, + { + label: "Auto-creation of Material Request", + url: + docsUrl + + "user/manual/en/stock/articles/auto-creation-of-material-request", + }, +]; -frappe.help.help_links['Form/Stock Entry'] = [ - { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, - { label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' }, - { label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' }, - { label: 'Opening Stock', url: docsUrl + 'user/manual/en/stock/opening-stock' }, - { label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' }, -] +frappe.help.help_links["Form/Stock Entry"] = [ + { label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" }, + { + label: "Stock Entry Types", + url: docsUrl + "user/manual/en/stock/articles/stock-entry-purpose", + }, + { + label: "Repack Entry", + url: docsUrl + "user/manual/en/stock/articles/repack-entry", + }, + { + label: "Opening Stock", + url: docsUrl + "user/manual/en/stock/opening-stock", + }, + { + label: "Subcontracting", + url: docsUrl + "user/manual/en/manufacturing/subcontracting", + }, +]; -frappe.help.help_links['List/Stock Entry'] = [ - { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' }, -] +frappe.help.help_links["List/Stock Entry"] = [ + { label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" }, +]; -frappe.help.help_links['Tree/Warehouse'] = [ - { label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' }, -] +frappe.help.help_links["Tree/Warehouse"] = [ + { label: "Warehouse", url: docsUrl + "user/manual/en/stock/warehouse" }, +]; -frappe.help.help_links['List/Serial No'] = [ - { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, -] +frappe.help.help_links["List/Serial No"] = [ + { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, +]; -frappe.help.help_links['Form/Serial No'] = [ - { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' }, -] +frappe.help.help_links["Form/Serial No"] = [ + { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, +]; -frappe.help.help_links['Form/Batch'] = [ - { label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' }, -] +frappe.help.help_links["Form/Batch"] = [ + { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, +]; -frappe.help.help_links['Form/Packing Slip'] = [ - { label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' }, -] +frappe.help.help_links["Form/Packing Slip"] = [ + { + label: "Packing Slip", + url: docsUrl + "user/manual/en/stock/tools/packing-slip", + }, +]; -frappe.help.help_links['Form/Quality Inspection'] = [ - { label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' }, -] +frappe.help.help_links["Form/Quality Inspection"] = [ + { + label: "Quality Inspection", + url: docsUrl + "user/manual/en/stock/tools/quality-inspection", + }, +]; -frappe.help.help_links['Form/Landed Cost Voucher'] = [ - { label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' }, -] +frappe.help.help_links["Form/Landed Cost Voucher"] = [ + { + label: "Landed Cost Voucher", + url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher", + }, +]; -frappe.help.help_links['Tree/Item Group'] = [ - { label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' }, -] +frappe.help.help_links["Tree/Item Group"] = [ + { + label: "Item Group", + url: docsUrl + "user/manual/en/stock/setup/item-group", + }, +]; -frappe.help.help_links['Form/Item Attribute'] = [ - { label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' }, -] +frappe.help.help_links["Form/Item Attribute"] = [ + { + label: "Item Attribute", + url: docsUrl + "user/manual/en/stock/setup/item-attribute", + }, +]; -frappe.help.help_links['Form/UOM'] = [ - { label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' }, -] +frappe.help.help_links["Form/UOM"] = [ + { + label: "Fractions in UOM", + url: + docsUrl + "user/manual/en/stock/articles/managing-fractions-in-uom", + }, +]; -frappe.help.help_links['Form/Stock Reconciliation'] = [ - { label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' }, -] +frappe.help.help_links["Form/Stock Reconciliation"] = [ + { + label: "Opening Stock Entry", + url: docsUrl + "user/manual/en/stock/opening-stock", + }, +]; //CRM -frappe.help.help_links['Form/Lead'] = [ - { label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' }, -] +frappe.help.help_links["Form/Lead"] = [ + { label: "Lead", url: docsUrl + "user/manual/en/CRM/lead" }, +]; -frappe.help.help_links['Form/Opportunity'] = [ - { label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' }, -] +frappe.help.help_links["Form/Opportunity"] = [ + { label: "Opportunity", url: docsUrl + "user/manual/en/CRM/opportunity" }, +]; -frappe.help.help_links['Form/Address'] = [ - { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' }, -] +frappe.help.help_links["Form/Address"] = [ + { label: "Address", url: docsUrl + "user/manual/en/CRM/address" }, +]; -frappe.help.help_links['Form/Contact'] = [ - { label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' }, -] +frappe.help.help_links["Form/Contact"] = [ + { label: "Contact", url: docsUrl + "user/manual/en/CRM/contact" }, +]; -frappe.help.help_links['Form/Newsletter'] = [ - { label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' }, -] +frappe.help.help_links["Form/Newsletter"] = [ + { label: "Newsletter", url: docsUrl + "user/manual/en/CRM/newsletter" }, +]; -frappe.help.help_links['Form/Campaign'] = [ - { label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' }, -] +frappe.help.help_links["Form/Campaign"] = [ + { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" }, +]; -frappe.help.help_links['Tree/Sales Person'] = [ - { label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' }, -] +frappe.help.help_links["Tree/Sales Person"] = [ + { + label: "Sales Person", + url: docsUrl + "user/manual/en/CRM/setup/sales-person", + }, +]; -frappe.help.help_links['Form/Sales Person'] = [ - { label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' }, -] +frappe.help.help_links["Form/Sales Person"] = [ + { + label: "Sales Person Target", + url: + docsUrl + + "user/manual/en/selling/setup/sales-person-target-allocation", + }, +]; //Support -frappe.help.help_links['List/Feedback Trigger'] = [ - { label: 'Feedback Trigger', url: docsUrl + 'user/manual/en/setting-up/feedback/setting-up-feedback' }, -] +frappe.help.help_links["List/Feedback Trigger"] = [ + { + label: "Feedback Trigger", + url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback", + }, +]; -frappe.help.help_links['List/Feedback Request'] = [ - { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, -] +frappe.help.help_links["List/Feedback Request"] = [ + { + label: "Feedback Request", + url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + }, +]; -frappe.help.help_links['List/Feedback Request'] = [ - { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' }, -] +frappe.help.help_links["List/Feedback Request"] = [ + { + label: "Feedback Request", + url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + }, +]; //Manufacturing -frappe.help.help_links['Form/BOM'] = [ - { label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' }, - { label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' }, -] +frappe.help.help_links["Form/BOM"] = [ + { + label: "Bill of Material", + url: docsUrl + "user/manual/en/manufacturing/bill-of-materials", + }, + { + label: "Nested BOM Structure", + url: + docsUrl + + "user/manual/en/manufacturing/articles/nested-bom-structure", + }, +]; -frappe.help.help_links['Form/Work Order'] = [ - { label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' }, -] +frappe.help.help_links["Form/Work Order"] = [ + { + label: "Work Order", + url: docsUrl + "user/manual/en/manufacturing/work-order", + }, +]; -frappe.help.help_links['Form/Workstation'] = [ - { label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' }, -] +frappe.help.help_links["Form/Workstation"] = [ + { + label: "Workstation", + url: docsUrl + "user/manual/en/manufacturing/workstation", + }, +]; -frappe.help.help_links['Form/Operation'] = [ - { label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' }, -] +frappe.help.help_links["Form/Operation"] = [ + { + label: "Operation", + url: docsUrl + "user/manual/en/manufacturing/operation", + }, +]; -frappe.help.help_links['Form/BOM Update Tool'] = [ - { label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' }, -] +frappe.help.help_links["Form/BOM Update Tool"] = [ + { + label: "BOM Update Tool", + url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool", + }, +]; //Customize -frappe.help.help_links['Form/Customize Form'] = [ - { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, - { label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' }, -] +frappe.help.help_links["Form/Customize Form"] = [ + { + label: "Custom Field", + url: docsUrl + "user/manual/en/customize-erpnext/custom-field", + }, + { + label: "Customize Field", + url: docsUrl + "user/manual/en/customize-erpnext/customize-form", + }, +]; -frappe.help.help_links['Form/Custom Field'] = [ - { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, -] +frappe.help.help_links["Form/Custom Field"] = [ + { + label: "Custom Field", + url: docsUrl + "user/manual/en/customize-erpnext/custom-field", + }, +]; -frappe.help.help_links['Form/Custom Field'] = [ - { label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' }, -] +frappe.help.help_links["Form/Custom Field"] = [ + { + label: "Custom Field", + url: docsUrl + "user/manual/en/customize-erpnext/custom-field", + }, +]; From 0b569b3e43211f1c92b58012da77322c0577dd2e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 26 Mar 2021 14:40:15 +0530 Subject: [PATCH 73/92] fix: allow creating stock entry based on work order for customer provided items (#24885) --- .../doctype/work_order/test_work_order.py | 62 ++++++++++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 9 +-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 00e8c5418a..08291d1eae 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -82,7 +82,7 @@ class TestWorkOrder(unittest.TestCase): wo_order.set_work_order_operations() self.assertEqual(wo_order.planned_operating_cost, cost*2) - def test_resered_qty_for_partial_completion(self): + def test_reserved_qty_for_partial_completion(self): item = "_Test Item" warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC") @@ -109,7 +109,7 @@ class TestWorkOrder(unittest.TestCase): s.submit() bin1_at_completion = get_bin(item, warehouse) - + self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production), reserved_qty_on_submission - 1) @@ -592,6 +592,55 @@ class TestWorkOrder(unittest.TestCase): frappe.db.set_value("Manufacturing Settings", None, "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' + make_item(finished_item, { + "include_item_in_manufacturing": 1, + "is_stock_item": 1 + }) + + customer_provided_item = 'CUST-0987' + make_item(customer_provided_item, { + 'is_purchase_item': 0, + 'is_customer_provided_item': 1, + "is_stock_item": 1, + "include_item_in_manufacturing": 1, + 'customer': '_Test Customer' + }) + + if not frappe.db.exists('BOM', {'item': finished_item}): + make_bom(item=finished_item, raw_materials=[customer_provided_item], rm_qty=1) + + company = "_Test Company with perpetual inventory" + customer_warehouse = create_warehouse("Test Customer Provided Warehouse", company=company) + wo = make_wo_order_test_record(item=finished_item, qty=1, source_warehouse=customer_warehouse, + company=company) + + ste = frappe.get_doc(make_stock_entry(wo.name, purpose='Material Transfer for Manufacture')) + ste.insert() + + self.assertEqual(len(ste.items), 1) + for item in ste.items: + self.assertEqual(item.allow_zero_valuation_rate, 1) + self.assertEqual(item.valuation_rate, 0) + + def test_valuation_rate_missing_on_make_stock_entry(self): + item_name = 'Test Valuation Rate Missing' + make_item(item_name, { + "is_stock_item": 1, + "include_item_in_manufacturing": 1, + }) + + if not frappe.db.get_value('BOM', {'item': item_name}): + make_bom(item=item_name, raw_materials=[item_name], rm_qty=1) + + company = "_Test Company with perpetual inventory" + source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company) + wo = make_wo_order_test_record(item=item_name, qty=1, source_warehouse=source_warehouse, + company=company) + + self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture') + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` @@ -609,6 +658,15 @@ def allow_overproduction(fieldname, percentage): def make_wo_order_test_record(**args): args = frappe._dict(args) + if args.company and args.company != "_Test Company": + warehouse_map = { + "fg_warehouse": "_Test FG Warehouse", + "wip_warehouse": "_Test WIP Warehouse" + } + + for attr, wh_name in warehouse_map.items(): + if not args.get(attr): + args[attr] = create_warehouse(wh_name, company=args.company) wo_order = frappe.new_doc("Work Order") wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item" diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ea1b3873ea..b5f7e05f22 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -458,7 +458,7 @@ class StockEntry(StockController): Set rate for outgoing, scrapped and finished items """ # Set rate for outgoing items - outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate) + outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate) finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item]) # Set basic rate for incoming items @@ -482,13 +482,13 @@ class StockEntry(StockController): d.basic_rate = flt(d.basic_rate, d.precision("basic_rate")) d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) - def set_rate_for_outgoing_items(self, reset_outgoing_rate=True): + def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): outgoing_items_cost = 0.0 for d in self.get('items'): if d.s_warehouse: if reset_outgoing_rate: args = self.get_args_for_incoming_rate(d) - rate = get_incoming_rate(args) + rate = get_incoming_rate(args, raise_error_if_no_rate) if rate > 0: d.basic_rate = rate @@ -1010,7 +1010,8 @@ class StockEntry(StockController): self.set_scrap_items() self.set_actual_qty() - self.calculate_rate_and_amount(raise_error_if_no_rate=False) + self.validate_customer_provided_item() + self.calculate_rate_and_amount() def set_scrap_items(self): if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]: From 1989375be4addc783ddd9a8b5a3ef6696bd08e07 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Fri, 26 Mar 2021 16:34:22 +0530 Subject: [PATCH 74/92] fix: Employee profile pic upload access for erpnext user --- erpnext/hooks.py | 4 ++++ erpnext/hr/doctype/employee/employee.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9b9a0dae2e..c2798a36b6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -195,6 +195,10 @@ sounds = [ {"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2}, ] +has_upload_permission = { + "Employee": "erpnext.hr.doctype.employee.employee.has_upload_permission" +} + has_website_permission = { "Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission", "Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index d0e7d0537b..c9946d8ac4 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -8,7 +8,7 @@ from frappe.utils import getdate, validate_email_address, today, add_years, form from frappe.model.naming import set_name_by_naming_series from frappe import throw, _, scrub from frappe.permissions import add_user_permission, remove_user_permission, \ - set_user_permission_if_allowed, has_permission + set_user_permission_if_allowed, has_permission, get_doc_permissions from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet @@ -66,7 +66,7 @@ class Employee(NestedSet): def validate_user_details(self): data = frappe.db.get_value('User', self.user_id, ['enabled', 'user_image'], as_dict=1) - if data.get("user_image"): + if data.get("user_image") and self.image == '': self.image = data.get("user_image") self.validate_for_enabled_user_id(data.get("enabled", 0)) self.validate_duplicate_user_id() @@ -501,3 +501,9 @@ def has_user_permission_for_employee(user_name, employee_name): 'allow': 'Employee', 'for_value': employee_name }) + +def has_upload_permission(doc, ptype='read', user=None): + if not user: user = frappe.session.user + if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype): + return True + return doc.user_id == user \ No newline at end of file From 82d7986a2f6a6e70139b50d412e6191201f34cd9 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Fri, 26 Mar 2021 17:02:37 +0530 Subject: [PATCH 75/92] fix: sider fix --- erpnext/hr/doctype/employee/employee.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index c9946d8ac4..629bc57118 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -503,7 +503,8 @@ def has_user_permission_for_employee(user_name, employee_name): }) def has_upload_permission(doc, ptype='read', user=None): - if not user: user = frappe.session.user + if not user: + user = frappe.session.user if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype): return True return doc.user_id == user \ No newline at end of file From 66bae19c162784ddb685f3b22e19030d41fa073b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 8 Mar 2021 20:58:57 +0530 Subject: [PATCH 76/92] feat(Production Plan): Consider Safety Stock in Required Qty Calculation --- .../material_request_plan_item.json | 9 ++++++++- .../doctype/production_plan/production_plan.json | 9 ++++++++- .../doctype/production_plan/production_plan.py | 13 +++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index f93b244a50..88f8d6075d 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -15,6 +15,7 @@ "uom", "projected_qty", "actual_qty", + "safety_stock", "item_details", "description", "min_order_qty", @@ -129,11 +130,17 @@ "fieldtype": "Link", "label": "From Warehouse", "options": "Warehouse" + }, + { + "fetch_from": "item_code.safety_stock", + "fieldname": "safety_stock", + "fieldtype": "Float", + "label": "Safety Stock" } ], "istable": 1, "links": [], - "modified": "2020-02-03 12:22:29.913302", + "modified": "2021-03-08 18:39:17.553611", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 7daf7069f3..f11470086a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -32,6 +32,7 @@ "material_request_planning", "include_non_stock_items", "include_subcontracted_items", + "include_safety_stock", "ignore_existing_ordered_qty", "column_break_25", "for_warehouse", @@ -309,13 +310,19 @@ "fieldtype": "Select", "label": "Sales Order Status", "options": "\nTo Deliver and Bill\nTo Bill\nTo Deliver" + }, + { + "default": "0", + "fieldname": "include_safety_stock", + "fieldtype": "Check", + "label": "Include Safety Stock in Required Qty Calculation" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-10 18:01:54.991970", + "modified": "2021-03-08 11:17:25.470147", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 8f9dd05217..88962aa8c7 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -482,7 +482,7 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty, item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse, item.default_bom as default_bom, bom_item.description as description, - bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, + bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, item.safety_stock as safety_stock, item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor FROM `tabBOM Item` bom_item @@ -518,8 +518,8 @@ def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_ite include_non_stock_items, include_subcontracted_items, d.qty) return item_details -def get_material_request_items(row, sales_order, - company, ignore_existing_ordered_qty, warehouse, bin_dict): +def get_material_request_items(row, sales_order, company, + ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict): total_qty = row['qty'] required_qty = 0 @@ -543,6 +543,9 @@ def get_material_request_items(row, sales_order, if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): required_qty = ceil(required_qty) + if include_safety_stock: + required_qty += flt(row['safety_stock']) + if required_qty > 0: return { 'item_code': row.item_code, @@ -660,6 +663,7 @@ def get_items_for_material_requests(doc, warehouses=None): company = doc.get('company') ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') + include_safety_stock = doc.get('include_safety_stock') so_item_details = frappe._dict() for data in po_items: @@ -711,6 +715,7 @@ def get_items_for_material_requests(doc, warehouses=None): 'description' : item_master.description, 'stock_uom' : item_master.stock_uom, 'conversion_factor' : conversion_factor, + 'safety_stock': item_master.safety_stock } ) @@ -732,7 +737,7 @@ def get_items_for_material_requests(doc, warehouses=None): if details.qty > 0: items = get_material_request_items(details, sales_order, company, - ignore_existing_ordered_qty, warehouse, bin_dict) + ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict) if items: mr_items.append(items) From 1f86471656ba89e81829e2fa33c1e188c46c5c25 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 22 Mar 2021 12:21:23 +0530 Subject: [PATCH 77/92] feat: show ordered and reserved qty in Material Request Plan Item table --- .../material_request_plan_item.json | 18 +++++++++++++++++- .../doctype/production_plan/production_plan.py | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 88f8d6075d..8d67827c81 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -15,6 +15,8 @@ "uom", "projected_qty", "actual_qty", + "ordered_qty", + "reserved_qty_for_production", "safety_stock", "item_details", "description", @@ -136,11 +138,25 @@ "fieldname": "safety_stock", "fieldtype": "Float", "label": "Safety Stock" + }, + { + "fieldname": "ordered_qty", + "fieldtype": "Float", + "label": "Ordered Qty", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "reserved_qty_for_production", + "fieldtype": "Float", + "label": "Reserved Qty for Production", + "no_copy": 1, + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-03-08 18:39:17.553611", + "modified": "2021-03-22 12:11:10.993737", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 88962aa8c7..6cd8876fd2 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -557,6 +557,8 @@ def get_material_request_items(row, sales_order, company, or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), 'actual_qty': bin_dict.get("actual_qty", 0), 'projected_qty': bin_dict.get("projected_qty", 0), + 'ordered_qty': bin_dict.get("ordered_qty", 0), + 'reserved_qty_for_production': bin_dict.get("reserved_qty_for_production", 0), 'min_order_qty': row['min_order_qty'], 'material_request_type': row.get("default_material_request_type"), 'sales_order': sales_order, From 07409990a09745da5c20e40d46103256b82c19df Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Mar 2021 11:50:54 +0530 Subject: [PATCH 78/92] fix: Ordered and Reserved Qty for Production not getting fetched in Items --- .../manufacturing/doctype/production_plan/production_plan.js | 3 ++- .../manufacturing/doctype/production_plan/production_plan.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index b723387a09..cf892650c6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -251,7 +251,8 @@ frappe.ui.form.on('Production Plan', { get_items_for_material_requests: function(frm, warehouses) { const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse', - 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; + 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty', + 'reserved_qty_for_production', 'material_request_type']; frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 6cd8876fd2..89a6bb4f87 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -625,7 +625,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): """.format(lft, rgt, company) return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, - ifnull(sum(actual_qty),0) as actual_qty, warehouse from `tabBin` + ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty, + ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse from `tabBin` where item_code = %(item_code)s {conditions} group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) From 9dbabb92df0ae34ea763c4241326f951838f8963 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Mar 2021 12:33:21 +0530 Subject: [PATCH 79/92] feat: Add more fields to raw material download --- .../doctype/production_plan/production_plan.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 89a6bb4f87..e741d903f1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -435,11 +435,13 @@ def download_raw_materials(doc): doc = frappe._dict(json.loads(doc)) item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', - 'projected Qty', 'Actual Qty']] + 'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production', + 'Safety Stock']] for d in get_items_for_material_requests(doc): item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'), - d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty')]) + d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), + d.get('reserved_qty_for_production'), d.get('safety_stock')]) if not doc.get('for_warehouse'): row = {'item_code': d.get('item_code')} @@ -448,7 +450,8 @@ def download_raw_materials(doc): continue item_list.append(['', '', '', '', bin_dict.get('warehouse'), - bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)]) + bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)], + bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)) build_csv_response(item_list, doc.name) @@ -555,6 +558,7 @@ def get_material_request_items(row, sales_order, company, 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), + 'safety_stock': row.safety_stock, 'actual_qty': bin_dict.get("actual_qty", 0), 'projected_qty': bin_dict.get("projected_qty", 0), 'ordered_qty': bin_dict.get("ordered_qty", 0), From 87a70fe8cb9b4123755490f6b0f06e6515095d02 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Mar 2021 13:39:35 +0530 Subject: [PATCH 80/92] feat: Show Required Qty as per BOM in Material Request Items --- .../material_request_plan_item.json | 14 ++++++++++++-- .../doctype/production_plan/production_plan.js | 2 +- .../doctype/production_plan/production_plan.py | 17 +++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 8d67827c81..6c60bbde86 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -11,6 +11,7 @@ "from_warehouse", "warehouse", "column_break_4", + "required_bom_qty", "quantity", "uom", "projected_qty", @@ -137,7 +138,9 @@ "fetch_from": "item_code.safety_stock", "fieldname": "safety_stock", "fieldtype": "Float", - "label": "Safety Stock" + "label": "Safety Stock", + "no_copy": 1, + "read_only": 1 }, { "fieldname": "ordered_qty", @@ -152,11 +155,18 @@ "label": "Reserved Qty for Production", "no_copy": 1, "read_only": 1 + }, + { + "fieldname": "required_bom_qty", + "fieldtype": "Float", + "label": "Required Qty as per BOM", + "no_copy": 1, + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-03-22 12:11:10.993737", + "modified": "2021-03-26 12:41:13.013149", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index cf892650c6..15ec6209c1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -251,7 +251,7 @@ frappe.ui.form.on('Production Plan', { get_items_for_material_requests: function(frm, warehouses) { const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse', - 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty', + 'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty', 'reserved_qty_for_production', 'material_request_type']; frappe.call({ diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index e741d903f1..109c8b5647 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -434,14 +434,14 @@ def download_raw_materials(doc): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) - item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', + item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM', 'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production', - 'Safety Stock']] + 'Safety Stock', 'Required Qty']] for d in get_items_for_material_requests(doc): - item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'), - d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), - d.get('reserved_qty_for_production'), d.get('safety_stock')]) + item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'), + d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'), + d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')]) if not doc.get('for_warehouse'): row = {'item_code': d.get('item_code')} @@ -449,9 +449,9 @@ def download_raw_materials(doc): if d.get("warehouse") == bin_dict.get('warehouse'): continue - item_list.append(['', '', '', '', bin_dict.get('warehouse'), - bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)], - bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)) + item_list.append(['', '', '', bin_dict.get('warehouse'), '', + bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0), + bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)]) build_csv_response(item_list, doc.name) @@ -554,6 +554,7 @@ def get_material_request_items(row, sales_order, company, 'item_code': row.item_code, 'item_name': row.item_name, 'quantity': required_qty, + 'required_bom_qty': total_qty, 'description': row.description, 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ From 98dcf8a1fd2ad36d67a46e1325e48041c0c783df Mon Sep 17 00:00:00 2001 From: walstanb Date: Sat, 27 Mar 2021 10:09:28 +0530 Subject: [PATCH 81/92] fix: type casting for None type value --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 92fa5ef19a..402d157009 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -57,7 +57,7 @@ class POSInvoice(SalesInvoice): self.apply_loyalty_points() self.check_phone_payments() self.set_status(update=True) - + def before_cancel(self): if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1: pos_closing_entry = frappe.get_all( @@ -221,7 +221,7 @@ class POSInvoice(SalesInvoice): base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total) if not flt(self.change_amount) and grand_total < flt(self.paid_amount): self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount)) - self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount)) + self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount) if flt(self.change_amount) and not self.account_for_change_amount: frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1) @@ -376,7 +376,7 @@ class POSInvoice(SalesInvoice): "campaign": profile.get("campaign"), "allow_print_before_pay": profile.get("allow_print_before_pay") } - + def reset_mode_of_payments(self): if self.pos_profile: pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile) @@ -406,7 +406,7 @@ class POSInvoice(SalesInvoice): pay_req.request_phone_payment() return pay_req - + def get_new_payment_request(self, mop): payment_gateway_account = frappe.db.get_value("Payment Gateway Account", { "payment_account": mop.account, From 9826ac4602a4b3d4f60989ca9c03ecde105da4f8 Mon Sep 17 00:00:00 2001 From: walstanb Date: Sat, 27 Mar 2021 10:11:31 +0530 Subject: [PATCH 82/92] test: reset session user if test errors --- .../test_pos_invoice_merge_log.py | 126 +++++++++--------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index db046c9800..6588bc8ea3 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -14,85 +14,91 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): def test_consolidated_invoice_creation(self): frappe.db.sql("delete from `tabPOS Invoice`") - test_user, pos_profile = init_user_and_profile() + try: + test_user, pos_profile = init_user_and_profile() - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) - pos_inv.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 - }) - pos_inv.submit() + pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 + }) + pos_inv.submit() - pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) - pos_inv2.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 - }) - pos_inv2.submit() + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 + }) + pos_inv2.submit() - pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) - pos_inv3.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 - }) - pos_inv3.submit() + pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) + pos_inv3.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 + }) + pos_inv3.submit() - consolidate_pos_invoices() + consolidate_pos_invoices() - pos_inv.load_from_db() - self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) + pos_inv.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) - pos_inv3.load_from_db() - self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) + pos_inv3.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) - self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) + self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) + + finally: + + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") - frappe.db.sql("delete from `tabPOS Invoice`") - def test_consolidated_credit_note_creation(self): frappe.db.sql("delete from `tabPOS Invoice`") - test_user, pos_profile = init_user_and_profile() + try: + test_user, pos_profile = init_user_and_profile() - pos_inv = create_pos_invoice(rate=300, do_not_submit=1) - pos_inv.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 - }) - pos_inv.submit() + pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 + }) + pos_inv.submit() - pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) - pos_inv2.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 - }) - pos_inv2.submit() + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 + }) + pos_inv2.submit() - pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) - pos_inv3.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 - }) - pos_inv3.submit() + pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) + pos_inv3.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 + }) + pos_inv3.submit() - pos_inv_cn = make_sales_return(pos_inv.name) - pos_inv_cn.set("payments", []) - pos_inv_cn.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300 - }) - pos_inv_cn.paid_amount = -300 - pos_inv_cn.submit() + pos_inv_cn = make_sales_return(pos_inv.name) + pos_inv_cn.set("payments", []) + pos_inv_cn.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300 + }) + pos_inv_cn.paid_amount = -300 + pos_inv_cn.submit() - consolidate_pos_invoices() + consolidate_pos_invoices() - pos_inv.load_from_db() - self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) + pos_inv.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) - pos_inv3.load_from_db() - self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) + pos_inv3.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) - pos_inv_cn.load_from_db() - self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) - self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) + pos_inv_cn.load_from_db() + self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) + self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") - frappe.db.sql("delete from `tabPOS Invoice`") + finally: + + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") From 52403c537ba61094d7c8e2baf4eb84b85ef589fe Mon Sep 17 00:00:00 2001 From: walstanb Date: Sat, 27 Mar 2021 10:13:27 +0530 Subject: [PATCH 83/92] fix: check if gst account exists before appending --- erpnext/regional/india/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index e24bd6c3d0..0a2cf489ad 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -823,8 +823,11 @@ def get_regional_round_off_accounts(company, account_list): return gst_accounts = get_gst_accounts(company) - gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ - + gst_accounts.get('igst_account') + + gst_account_list = [] + for account in ['cgst_account', 'sgst_account', 'igst_account']: + if account in gst_accounts.keys(): + gst_account_list += gst_accounts.get(account) account_list.extend(gst_account_list) From 225c64a1ada2ccd7bbbc63e4e887dd7d5167b2ba Mon Sep 17 00:00:00 2001 From: walstanb Date: Sat, 27 Mar 2021 10:15:23 +0530 Subject: [PATCH 84/92] test: sets is_purchase_item while item creation --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index ded293b88d..50492f50b5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -898,7 +898,7 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.submit_journal_entries = 1 acc_settings.save() - item = create_item("_Test Item for Deferred Accounting") + item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True) item.enable_deferred_expense = 1 item.deferred_expense_account = deferred_account item.save() From ab673d9e4b2c77e11471431c44649cd40dc0db34 Mon Sep 17 00:00:00 2001 From: walstanb Date: Sat, 27 Mar 2021 12:52:23 +0530 Subject: [PATCH 85/92] fix: minor changes --- .../doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py | 2 -- erpnext/regional/india/utils.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index 6588bc8ea3..d880caa3c7 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -46,7 +46,6 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) finally: - frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Invoice`") @@ -96,7 +95,6 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) finally: - frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Invoice`") diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0a2cf489ad..3637de438c 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -826,7 +826,7 @@ def get_regional_round_off_accounts(company, account_list): gst_account_list = [] for account in ['cgst_account', 'sgst_account', 'igst_account']: - if account in gst_accounts.keys(): + if account in gst_accounts: gst_account_list += gst_accounts.get(account) account_list.extend(gst_account_list) From 868c0bf45b59daddd456271227e3ffddabe53b7c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 27 Mar 2021 16:10:20 +0530 Subject: [PATCH 86/92] perf: reduce no of queries for checking if future sl entry exists (#24881) --- erpnext/controllers/stock_controller.py | 45 ++++++++++++++----------- erpnext/stock/stock_ledger.py | 6 ++-- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 11ac703311..f352bae30e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -495,7 +495,7 @@ class StockController(AccountsController): "voucher_no": self.name, "company": self.company }) - if check_if_future_sle_exists(args): + if future_sle_exists(args): create_repost_item_valuation_entry(args) elif not is_reposting_pending(): check_if_stock_and_account_balance_synced(self.posting_date, @@ -506,37 +506,42 @@ def is_reposting_pending(): {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) -def check_if_future_sle_exists(args): - sl_entries = frappe.db.get_all("Stock Ledger Entry", +def future_sle_exists(args): + sl_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no}, fields=["item_code", "warehouse"], order_by="creation asc") - distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries])) + if not sl_entries: + return - sle_exists = False - for item_code, warehouse in distinct_item_warehouses: - args.update({ - "item_code": item_code, - "warehouse": warehouse - }) - if get_sle(args): - sle_exists = True - break - return sle_exists + warehouse_items_map = {} + for entry in sl_entries: + if entry.warehouse not in warehouse_items_map: + warehouse_items_map[entry.warehouse] = set() + + warehouse_items_map[entry.warehouse].add(entry.item_code) + + or_conditions = [] + for warehouse, items in warehouse_items_map.items(): + or_conditions.append( + "warehouse = '{}' and item_code in ({})".format( + warehouse, + ", ".join(frappe.db.escape(item) for item in items) + ) + ) -def get_sle(args): return frappe.db.sql(""" select name from `tabStock Ledger Entry` where - item_code=%(item_code)s - and warehouse=%(warehouse)s - and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) + ({}) + and timestamp(posting_date, posting_time) + >= timestamp(%(posting_date)s, %(posting_time)s) and voucher_no != %(voucher_no)s and is_cancelled = 0 limit 1 - """, args) + """.format(" or ".join(or_conditions)), args) def create_repost_item_valuation_entry(args): args = frappe._dict(args) @@ -554,4 +559,4 @@ def create_repost_item_valuation_entry(args): repost_entry.allow_zero_rate = args.allow_zero_rate repost_entry.flags.ignore_links = True repost_entry.save() - repost_entry.submit() \ No newline at end of file + repost_entry.submit() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f54b3c1bb2..121c51cf6a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -207,11 +207,11 @@ class update_entries_after(object): def build(self): - from erpnext.controllers.stock_controller import check_if_future_sle_exists + from erpnext.controllers.stock_controller import future_sle_exists if self.args.get("sle_id"): self.process_sle_against_current_timestamp() - if not check_if_future_sle_exists(self.args): + if not future_sle_exists(self.args): self.update_bin() else: entries_to_fix = self.get_future_entries_to_fix() @@ -856,4 +856,4 @@ def get_future_sle_with_negative_qty(args): and qty_after_transaction < 0 order by timestamp(posting_date, posting_time) asc limit 1 - """, args, as_dict=1) \ No newline at end of file + """, args, as_dict=1) From d6c7d8e631430bb5e0a28740214cc930feeee6dd Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Sat, 27 Mar 2021 18:30:07 +0530 Subject: [PATCH 87/92] test: reset session user if test errors in test_sle (#25042) --- .../test_stock_ledger_entry.py | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) 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 59f1f3961b..7ebd4e6cb2 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 @@ -125,7 +125,7 @@ class TestStockLedgerEntry(unittest.TestCase): pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10', warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100) - return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', + return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2) # check sle @@ -278,7 +278,7 @@ class TestStockLedgerEntry(unittest.TestCase): frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR") - + # Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100 pr = make_purchase_receipt(company=company, posting_date='2020-04-10', warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100) @@ -292,7 +292,7 @@ class TestStockLedgerEntry(unittest.TestCase): # Update raw material's valuation via LCV, Additional cost = 50 lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company) - + pr1.reload() self.assertEqual(pr1.items[0].valuation_rate, 125) @@ -310,31 +310,33 @@ class TestStockLedgerEntry(unittest.TestCase): # 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") - + # Set User with Stock User role but not Stock Manager - frappe.set_user("test@example.com") - user = frappe.get_doc("User", "test@example.com") - user.add_roles("Stock User") - user.remove_roles("Stock Manager") + try: + frappe.set_user("test@example.com") + user = frappe.get_doc("User", "test@example.com") + user.add_roles("Stock User") + user.remove_roles("Stock Manager") - stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) - back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, - posting_date=add_days(today(), -1), do_not_submit=True) + stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) + back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, + posting_date=add_days(today(), -1), do_not_submit=True) - # Block back-dated entry - self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit) + # Block back-dated entry + self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit) - user.add_roles("Stock Manager") + user.add_roles("Stock Manager") - # Back dated entry allowed to Stock Manager - back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, - posting_date=add_days(today(), -1)) + # Back dated entry allowed to Stock Manager + back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, + posting_date=add_days(today(), -1)) - back_dated_se_2.cancel() - stock_entry_on_today.cancel() + back_dated_se_2.cancel() + stock_entry_on_today.cancel() - frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None) - frappe.set_user("Administrator") + finally: + frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None) + frappe.set_user("Administrator") def create_repack_entry(**args): From ee822a9dcd21fa65c633bc5ac1286628b3e34359 Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Sat, 27 Mar 2021 19:42:25 +0530 Subject: [PATCH 88/92] test: use different product attribute value; cleanup (#25043) Co-authored-by: Sagar Vora --- .../test_product_configurator.py | 69 +++++++++---------- erpnext/portal/product_configurator/utils.py | 2 +- .../doctype/item_attribute/test_records.json | 4 +- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index 97042dba92..3521e7e8bf 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -10,8 +10,38 @@ from erpnext.stock.doctype.item.test_item import make_item_variant test_dependencies = ["Item"] class TestProductConfigurator(unittest.TestCase): - def setUp(self): - self.create_variant_item() + @classmethod + def setUpClass(cls): + cls.create_variant_item() + + @classmethod + def create_variant_item(cls): + if not frappe.db.exists('Item', '_Test Variant Item - 2XL'): + frappe.get_doc({ + "description": "_Test Variant Item - 2XL", + "item_code": "_Test Variant Item - 2XL", + "item_name": "_Test Variant Item - 2XL", + "doctype": "Item", + "is_stock_item": 1, + "variant_of": "_Test Variant Item", + "item_group": "_Test Item Group", + "stock_uom": "_Test UOM", + "item_defaults": [{ + "company": "_Test Company", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + "income_account": "Sales - _TC" + }], + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "2XL" + } + ], + "show_variant_in_website": 1 + }).insert() def test_product_list(self): template_items = frappe.get_all('Item', {'show_in_website': 1}) @@ -46,39 +76,6 @@ class TestProductConfigurator(unittest.TestCase): def test_get_products_for_website(self): items = get_products_for_website(attribute_filters={ - 'Test Size': ['Medium'] + 'Test Size': ['2XL'] }) self.assertEqual(len(items), 1) - - - def create_variant_item(self): - if not frappe.db.exists('Item', '_Test Variant Item 1'): - frappe.get_doc({ - "description": "_Test Variant Item 12", - "doctype": "Item", - "is_stock_item": 1, - "variant_of": "_Test Variant Item", - "item_code": "_Test Variant Item 1", - "item_group": "_Test Item Group", - "item_name": "_Test Variant Item 1", - "stock_uom": "_Test UOM", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" - }], - "attributes": [ - { - "attribute": "Test Size", - "attribute_value": "Medium" - } - ], - "show_variant_in_website": 1 - }).insert() - - - def tearDown(self): - frappe.db.rollback() \ No newline at end of file diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 21fd7c2878..d77eb2c396 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -298,7 +298,7 @@ def get_items_by_fields(field_filters): def get_items(filters=None, search=None): - start = frappe.form_dict.start or 0 + start = frappe.form_dict.get('start', 0) products_settings = get_product_settings() page_length = products_settings.products_per_page diff --git a/erpnext/stock/doctype/item_attribute/test_records.json b/erpnext/stock/doctype/item_attribute/test_records.json index d346979496..6aa6ffd6c9 100644 --- a/erpnext/stock/doctype/item_attribute/test_records.json +++ b/erpnext/stock/doctype/item_attribute/test_records.json @@ -4,10 +4,12 @@ "attribute_name": "Test Size", "priority": 1, "item_attribute_values": [ + {"attribute_value": "Extra Small", "abbr": "XSL"}, {"attribute_value": "Small", "abbr": "S"}, {"attribute_value": "Medium", "abbr": "M"}, {"attribute_value": "Large", "abbr": "L"}, - {"attribute_value": "Extra Small", "abbr": "XSL"} + {"attribute_value": "Extra Large", "abbr": "XL"}, + {"attribute_value": "2XL", "abbr": "2XL"} ] }, { From 5c907fa7f093416bc8244f9d23dadaeb6eea5d15 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 27 Mar 2021 20:30:26 +0530 Subject: [PATCH 89/92] test: make Stock Entry to avoid NegativeStockError (#25044) --- .../pos_closing_entry/test_pos_closing_entry.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index 40db09ec3b..b596c0cf25 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -5,12 +5,21 @@ from __future__ import unicode_literals import frappe import unittest from frappe.utils import nowdate +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile class TestPOSClosingEntry(unittest.TestCase): + def setUp(self): + # Make stock available for POS Sales + make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100) + + def tearDown(self): + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -41,9 +50,6 @@ class TestPOSClosingEntry(unittest.TestCase): self.assertEqual(pcv_doc.total_quantity, 2) self.assertEqual(pcv_doc.net_total, 6700) - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") - def test_cancelling_of_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -84,8 +90,6 @@ class TestPOSClosingEntry(unittest.TestCase): self.assertEqual(si_doc.docstatus, 2) self.assertEqual(pos_inv1.status, 'Paid') - frappe.set_user("Administrator") - frappe.db.sql("delete from `tabPOS Profile`") def init_user_and_profile(**args): user = 'test@example.com' @@ -103,4 +107,4 @@ def init_user_and_profile(**args): pos_profile.save() - return test_user, pos_profile \ No newline at end of file + return test_user, pos_profile From ba76f87e44879d89aaf745bf3d92604506075be3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 29 Mar 2021 20:18:45 +0530 Subject: [PATCH 90/92] fix(Italy): setup, validations, optimisations (#25054) * fix(Italy): setup, validations, optimisations * fix: remove unused import --- erpnext/regional/italy/sales_invoice.js | 13 +--- erpnext/regional/italy/setup.py | 7 +- erpnext/regional/italy/utils.py | 98 ++++++++++++++----------- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js index 586a52937b..b54ac53812 100644 --- a/erpnext/regional/italy/sales_invoice.js +++ b/erpnext/regional/italy/sales_invoice.js @@ -11,15 +11,10 @@ erpnext.setup_e_invoice_button = (doctype) => { callback: function(r) { frm.reload_doc(); if(r.message) { - var w = window.open( - frappe.urllib.get_full_url( - "/api/method/erpnext.regional.italy.utils.download_e_invoice_file?" - + "file_name=" + r.message - ) - ) - if (!w) { - frappe.msgprint(__("Please enable pop-ups")); return; - } + open_url_post(frappe.request.url, { + cmd: 'frappe.core.doctype.file.file.download_file', + file_url: r.message + }); } } }); diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 95b92e76a6..a1f5bb9836 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -128,11 +128,8 @@ def make_custom_fields(update=True): fetch_from="company.vat_collectability"), dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing', fieldtype='Section Break', insert_after='against_income_account', print_hide=1), - dict(fieldname='company_tax_id', label='Company Tax ID', - fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, - fetch_from="company.tax_id"), dict(fieldname='company_fiscal_code', label='Company Fiscal Code', - fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1, + fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, fetch_from="company.fiscal_code"), dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime', fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1, @@ -217,4 +214,4 @@ def add_permissions(): update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1) add_permission(doctype, 'Accounts Manager', 1) update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1) - update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1) \ No newline at end of file + update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1) diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 6842fb2a61..08573cddcd 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals -import frappe, json, os +import io +import json +import frappe from frappe.utils import flt, cstr from erpnext.controllers.taxes_and_totals import get_itemised_tax from frappe import _ @@ -28,20 +30,22 @@ def update_itemised_tax_data(doc): @frappe.whitelist() def export_invoices(filters=None): - saved_xmls = [] + frappe.has_permission('Sales Invoice', throw=True) - invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"]) + invoices = frappe.get_all( + "Sales Invoice", + filters=get_conditions(filters), + fields=["name", "company_tax_id"] + ) - for invoice in invoices: - attachments = get_e_invoice_attachments(invoice) - saved_xmls += [attachment.file_name for attachment in attachments] + attachments = get_e_invoice_attachments(invoices) - zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S")) + zip_filename = "{0}-einvoices.zip".format( + frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S")) - download_zip(saved_xmls, zip_filename) + download_zip(attachments, zip_filename) -@frappe.whitelist() def prepare_invoice(invoice, progressive_number): #set company information company = frappe.get_doc("Company", invoice.company) @@ -98,7 +102,7 @@ def prepare_invoice(invoice, progressive_number): def get_conditions(filters): filters = json.loads(filters) - conditions = {"docstatus": 1} + conditions = {"docstatus": 1, "company_tax_id": ("!=", "")} if filters.get("company"): conditions["company"] = filters["company"] if filters.get("customer"): conditions["customer"] = filters["customer"] @@ -111,23 +115,22 @@ def get_conditions(filters): return conditions -#TODO: Use function from frappe once PR #6853 is merged. + def download_zip(files, output_filename): - from zipfile import ZipFile + import zipfile - input_files = [frappe.get_site_path('private', 'files', filename) for filename in files] - output_path = frappe.get_site_path('private', 'files', output_filename) + zip_stream = io.BytesIO() + with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for file in files: + file_path = frappe.utils.get_files_path( + file.file_name, is_private=file.is_private) - with ZipFile(output_path, 'w') as output_zip: - for input_file in input_files: - output_zip.write(input_file, arcname=os.path.basename(input_file)) - - with open(output_path, 'rb') as fileobj: - filedata = fileobj.read() + zip_file.write(file_path, arcname=file.file_name) frappe.local.response.filename = output_filename - frappe.local.response.filecontent = filedata + frappe.local.response.filecontent = zip_stream.getvalue() frappe.local.response.type = "download" + zip_stream.close() def get_invoice_summary(items, taxes): summary_data = frappe._dict() @@ -307,23 +310,12 @@ def prepare_and_attach_invoice(doc, replace=False): @frappe.whitelist() def generate_single_invoice(docname): doc = frappe.get_doc("Sales Invoice", docname) - + frappe.has_permission("Sales Invoice", doc=doc, throw=True) e_invoice = prepare_and_attach_invoice(doc, True) + return e_invoice.file_url - return e_invoice.file_name - -@frappe.whitelist() -def download_e_invoice_file(file_name): - content = None - with open(frappe.get_site_path('private', 'files', file_name), "r") as f: - content = f.read() - - frappe.local.response.filename = file_name - frappe.local.response.filecontent = content - frappe.local.response.type = "download" - -#Delete e-invoice attachment on cancel. +# Delete e-invoice attachment on cancel. def sales_invoice_on_cancel(doc, method): if get_company_country(doc.company) not in ['Italy', 'Italia', 'Italian Republic', 'Repubblica Italiana']: @@ -335,16 +327,38 @@ def sales_invoice_on_cancel(doc, method): def get_company_country(company): return frappe.get_cached_value('Company', company, 'country') -def get_e_invoice_attachments(invoice): - if not invoice.company_tax_id: - return [] +def get_e_invoice_attachments(invoices): + if not isinstance(invoices, list): + if not invoices.company_tax_id: + return + + invoices = [invoices] + + tax_id_map = { + invoice.name: ( + invoice.company_tax_id + if invoice.company_tax_id.startswith("IT") + else "IT" + invoice.company_tax_id + ) for invoice in invoices + } + + attachments = frappe.get_all( + "File", + fields=("name", "file_name", "attached_to_name", "is_private"), + filters= { + "attached_to_name": ('in', tax_id_map), + "attached_to_doctype": 'Sales Invoice' + } + ) out = [] - attachments = get_attachments(invoice.doctype, invoice.name) - company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id - for attachment in attachments: - if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"): + if ( + attachment.file_name + and attachment.file_name.endswith(".xml") + and attachment.file_name.startswith( + tax_id_map.get(attachment.attached_to_name)) + ): out.append(attachment) return out From 30f67c3f57c416e43bcd801cdab4fe465bbcc6ef Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 30 Mar 2021 12:39:56 +0530 Subject: [PATCH 91/92] fix: add filters to account link fields in Non Profit Settings (#25060) - fetch memberships ordered by date in certificate --- .../non_profit/doctype/donation/donation.py | 5 ++-- .../non_profit_settings.js | 23 ++++++++++++++++++- .../tax_exemption_80g_certificate.py | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 6a2a06dbc8..4fd1a30ab9 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -42,7 +42,7 @@ class Donation(Document): self.load_from_db() self.create_payment_entry() - def create_payment_entry(self): + def create_payment_entry(self, date=None): settings = frappe.get_doc('Non Profit Settings') if not settings.automate_donation_payment_entries: return @@ -58,8 +58,9 @@ class Donation(Document): frappe.flags.ignore_account_permission = False pe.paid_from = settings.donation_debit_account pe.paid_to = settings.donation_payment_account + pe.posting_date = date or getdate() pe.reference_no = self.name - pe.reference_date = getdate() + pe.reference_date = date or getdate() pe.flags.ignore_mandatory = True pe.insert() pe.submit() diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js index cff92b42ab..4c4ca9834b 100644 --- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js @@ -19,7 +19,7 @@ frappe.ui.form.on("Non Profit Settings", { }; }); - frm.set_query("debit_account", function() { + frm.set_query("membership_debit_account", function() { return { filters: { "account_type": "Receivable", @@ -29,6 +29,16 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + frm.set_query("donation_debit_account", function() { + return { + filters: { + "account_type": "Receivable", + "is_group": 0, + "company": frm.doc.donation_company + } + }; + }); + frm.set_query("membership_payment_account", function () { var account_types = ["Bank", "Cash"]; return { @@ -40,6 +50,17 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + frm.set_query("donation_payment_account", function () { + var account_types = ["Bank", "Cash"]; + return { + filters: { + "account_type": ["in", account_types], + "is_group": 0, + "company": frm.doc.donation_company + } + }; + }); + let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership"; frm.set_intro(__("You can learn more about memberships in the manual. ") + `
${__('ERPNext Docs')}`, true); diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index 5bbd5750f9..41c7b23146 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -81,7 +81,7 @@ class TaxExemption80GCertificate(Document): 'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'membership_status': ('!=', 'Cancelled') - }, ['from_date', 'amount', 'name', 'invoice', 'payment_id']) + }, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date') if not memberships: frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member)) From 720f69caafb8e035b29e66db0c823b53a3162eec Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 31 Mar 2021 11:23:34 +0530 Subject: [PATCH 92/92] fix: column width in Recruitment analytics report (#25003) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- .../report/recruitment_analytics/recruitment_analytics.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py index e961114ac2..f5fece8049 100644 --- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py +++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py @@ -31,7 +31,7 @@ def get_columns(): "fieldtype": "Link", "fieldname": "job_opening", "options": "Job Opening", - "width": 100 + "width": 105 }, { "label": _("Job Applicant"), @@ -44,13 +44,13 @@ def get_columns(): "label": _("Applicant name"), "fieldtype": "data", "fieldname": "applicant_name", - "width": 120 + "width": 130 }, { "label": _("Application Status"), "fieldtype": "Data", "fieldname": "application_status", - "width": 100 + "width": 150 }, { "label": _("Job Offer"),