From 2744765757cfeb9a2f9e9671d91e945ad4b6bcaf Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 7 Sep 2020 12:31:19 +0530 Subject: [PATCH 001/154] feat(UAE VAT Format): add fields for emirates in address and sales invoice --- erpnext/regional/united_arab_emirates/setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 250659e54d..a2938bb300 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -41,6 +41,8 @@ def make_custom_fields(): dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), + dict(fieldname='emirate', label='Emirate', insert_after='customer_address', + fetch_from='customer_address.emirates'), ] invoice_item_fields = [ @@ -76,6 +78,10 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Data', insert_after='supplier_name'), ], + 'Address': [ + dict(fieldname='emirates', label='Emirates', + fieldtype='Data', insert_after='state'), + ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, 'Purchase Order': purchase_invoice_fields + invoice_fields, 'Purchase Receipt': purchase_invoice_fields + invoice_fields, From 7b7a8e1309ba0c262da7de07424858f5de9751d3 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Wed, 9 Sep 2020 20:54:30 +0530 Subject: [PATCH 002/154] feat: RCM for UAE VAT --- erpnext/hooks.py | 8 +- .../regional/united_arab_emirates/utils.py | 144 +++++++++++++++++- 2 files changed, 148 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 463ad6c94b..72be2dcc75 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -246,7 +246,10 @@ doc_events = { "on_trash": "erpnext.regional.check_deletion_permission" }, "Purchase Invoice": { - "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm" + "validate": [ + "erpnext.regional.india.utils.update_grand_total_for_rcm", + "erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm", + ] }, "Payment Entry": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"], @@ -379,7 +382,8 @@ regional_overrides = { 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries' }, 'United Arab Emirates': { - 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' + 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data', + 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.united_arab_emirates.utils.make_regional_gl_entries', }, 'Saudi Arabia': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index a0425f6b1c..05a99d190a 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt +from frappe import _ +import erpnext +from frappe.utils import flt, round_based_on_smallest_currency_fraction, money_in_words from erpnext.controllers.taxes_and_totals import get_itemised_tax from six import iteritems @@ -26,4 +28,142 @@ def update_itemised_tax_data(doc): row.tax_rate = flt(tax_rate, row.precision("tax_rate")) row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount")) - row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) \ No newline at end of file + row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) + +def get_account_currency(account): + """Helper function to get account currency""" + if not account: + return + def generator(): + account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"]) + if not account_currency: + account_currency = frappe.get_cached_value('Company', company, "default_currency") + + return account_currency + + return frappe.local_cache("account_currency", account, generator) + +def get_tax_accounts(company): + """Get the list of tax accounts for a specific company + + Args: + company (String): Current Company set as default + + Returns: + tax_accounts: List of Tax Accounts for the company + """ + tax_accounts_dict = frappe._dict() + tax_accounts_list = frappe.get_all("Account", + filters={"account_type": "Tax", "company": company}, + fields=["name"]) + + if not tax_accounts_list and not frappe.flags.in_test: + frappe.throw(_("Please create at least one Account of type Tax")) + for d in tax_accounts_list: + for key, name in d.items(): + tax_accounts_dict[name] = name + + return tax_accounts_dict + +def update_grand_total_for_rcm(doc, method): + """If the Reverse Charge is Applicable subtract the tax amount from the grand total and update in the form + + Args: + doc (Document): The document for the current Purchase Invoice + """ + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'United Arab Emirates': + return + + if not doc.total_taxes_and_charges: + return + + if doc.reverse_charge == 'Y': + tax_accounts = get_tax_accounts(doc.company) + + base_vat_tax = 0 + vat_tax = 0 + + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts: + base_vat_tax += tax.base_tax_amount_after_discount_amount + vat_tax += tax.tax_amount_after_discount_amount + + doc.taxes_and_charges_added -= vat_tax + doc.total_taxes_and_charges -= vat_tax + doc.base_taxes_and_charges_added -= base_vat_tax + doc.base_total_taxes_and_charges -= base_vat_tax + + update_totals(vat_tax, base_vat_tax, doc) + +def update_totals(vat_tax, base_vat_tax, doc): + """Update the grand total values in the form + + Args: + vat_tax (float): Vat Tax to be subtracted + base_vat_tax (float): Base Vat Tax to be subtracted + doc (Document): The document for the current Purchase Invoice + """ + + doc.base_grand_total -= base_vat_tax + doc.grand_total -= vat_tax + + if doc.meta.get_field("rounded_total"): + + if doc.is_rounded_total_disabled(): + doc.outstanding_amount = doc.grand_total + + else: + doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total, + doc.currency, doc.precision("rounded_total")) + doc.rounding_adjustment = flt(doc.rounded_total - doc.grand_total, + doc.precision("rounding_adjustment")) + doc.outstanding_amount = doc.rounded_total or doc.grand_total + + doc.in_words = money_in_words(doc.grand_total, doc.currency) + doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company)) + doc.set_payment_schedule() + +def make_regional_gl_entries(gl_entries, doc): + """This method is hooked to the make_regional_gl_entries in Purchase Invoice. + It appends the region specific general ledger entries to the list of GL Entries. + + Args: + gl_entries (List): List of GL entries to be made + doc (Document): The document for the current Purchase Invoice + + Returns: + List: Updates list of GL Entries + """ + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'United Arab Emirates': + return gl_entries + + if doc.reverse_charge == 'Y': + tax_accounts = get_tax_accounts(doc.company) + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts: + account_currency = get_account_currency(tax.account_head) + + gl_entries.append(doc.get_gl_dict( + { + "account": tax.account_head, + "cost_center": tax.cost_center, + "posting_date": doc.posting_date, + "against": doc.supplier, + dr_or_cr: tax.base_tax_amount_after_discount_amount, + dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==doc.company_currency \ + else tax.tax_amount_after_discount_amount + }, account_currency, item=tax) + ) + return gl_entries \ No newline at end of file From 91f1e266726c20abd2afc797c0c12f0e8793a3c5 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 10 Sep 2020 13:55:14 +0530 Subject: [PATCH 003/154] feat: Add rated supplies rows in UAE VAT report --- erpnext/regional/report/uae_vat/__init__.py | 0 erpnext/regional/report/uae_vat/uae_vat.js | 31 ++++++ erpnext/regional/report/uae_vat/uae_vat.json | 22 ++++ erpnext/regional/report/uae_vat/uae_vat.py | 108 +++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 erpnext/regional/report/uae_vat/__init__.py create mode 100644 erpnext/regional/report/uae_vat/uae_vat.js create mode 100644 erpnext/regional/report/uae_vat/uae_vat.json create mode 100644 erpnext/regional/report/uae_vat/uae_vat.py diff --git a/erpnext/regional/report/uae_vat/__init__.py b/erpnext/regional/report/uae_vat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/uae_vat/uae_vat.js b/erpnext/regional/report/uae_vat/uae_vat.js new file mode 100644 index 0000000000..45df167823 --- /dev/null +++ b/erpnext/regional/report/uae_vat/uae_vat.js @@ -0,0 +1,31 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["UAE VAT"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -3), + "width": "80" + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + }, + ] +}; diff --git a/erpnext/regional/report/uae_vat/uae_vat.json b/erpnext/regional/report/uae_vat/uae_vat.json new file mode 100644 index 0000000000..8807405a98 --- /dev/null +++ b/erpnext/regional/report/uae_vat/uae_vat.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2020-09-10 08:51:02.298482", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2020-09-10 08:51:02.298482", + "modified_by": "Administrator", + "module": "Regional", + "name": "UAE VAT", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "UAE VAT", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat/uae_vat.py new file mode 100644 index 0000000000..6e9565b1e5 --- /dev/null +++ b/erpnext/regional/report/uae_vat/uae_vat.py @@ -0,0 +1,108 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + + return columns, data + +def get_columns(): + return [ + { + "fieldname": "no", + "label": "No", + "fieldtype": "Data", + "width": 50 + }, + { + "fieldname": "legend", + "label": "Legend", + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": "Amount (AED)", + "fieldtype": "Currency", + "width": 100 + }, + { + "fieldname": "vat_amount", + "label": "VAT Amount (AED)", + "fieldtype": "Currency", + "width": 100 + }, + { + "fieldname": "adjustment", + "label": "Adjustment (AED)", + "fieldtype": "Currency", + "width": 100 + } + ] + +def get_data(filters = None): + data = [] + total_emiratewise = get_total_emiratewise(filters) + emirates = get_emirates() + amounts_by_emirate = {} + for d in total_emiratewise: + emirate, amount, vat= d + amounts_by_emirate[emirate] = { + "legend": emirate, + "amount": amount, + "vat_amount": vat + } + for d, emirate in enumerate(emirates, 97): + if emirate in amounts_by_emirate: + amounts_by_emirate[emirate]["no"] = f'1{chr(d)}' + amounts_by_emirate[emirate]["legend"] = f'Standard rated supplies in {emirate}' + data.append(amounts_by_emirate[emirate]) + else: + data.append( + { + "no": f'1{chr(d)}', + "legend": f'Standard rated supplies in {emirate}', + "amount": 0, + "vat_amount": 0 + } + ) + return data + + +def get_total_emiratewise(filters): + conditions = get_conditions(filters) + print(f""" + select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` + where docstatus = 1 {conditions} + group by `tabSales Invoice`.emirate; + """) + return frappe.db.sql(f""" + select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` + where docstatus = 1 {conditions} + group by `tabSales Invoice`.emirate; + """, filters) + +def get_emirates(): + return [ + 'Abu Dhabi', + 'Dubai', + 'Sharjah', + 'Ajman', + 'Umm Al Quwain', + 'Ras Al Khaimah', + 'Fujairah' + ] + +def get_conditions(filters): + conditions = "" + + for opts in (("company", " and company=%(company)s"), + ("from_date", " and posting_date>=%(from_date)s"), + ("to_date", " and posting_date<=%(to_date)s")): + if filters.get(opts[0]): + conditions += opts[1] + return conditions \ No newline at end of file From 031d77be2e58a033786a81354f08607ea07e4910 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 10 Sep 2020 17:40:01 +0530 Subject: [PATCH 004/154] feat: add reverse charge to uae vat report --- erpnext/regional/report/uae_vat/uae_vat.py | 53 +++++++++++++++---- .../regional/united_arab_emirates/setup.py | 8 +-- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat/uae_vat.py index 6e9565b1e5..50d1ddd351 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.py +++ b/erpnext/regional/report/uae_vat/uae_vat.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from erpnext.regional.united_arab_emirates.utils import get_tax_accounts def execute(filters=None): columns = get_columns() @@ -70,19 +71,21 @@ def get_data(filters = None): "vat_amount": 0 } ) + data.append( + { + "no": '3', + "legend": f'Supplies subject to the reverse charge provision', + "amount": get_reverse_charge_total(filters), + "vat_amount": get_reverse_charge_tax(filters) + } + ) return data def get_total_emiratewise(filters): - conditions = get_conditions(filters) - print(f""" - select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` - where docstatus = 1 {conditions} - group by `tabSales Invoice`.emirate; - """) return frappe.db.sql(f""" select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` - where docstatus = 1 {conditions} + where docstatus = 1 {get_conditions(filters)} group by `tabSales Invoice`.emirate; """, filters) @@ -99,10 +102,40 @@ def get_emirates(): def get_conditions(filters): conditions = "" + for opts in (("company", f' and company="{filters.get("company")}"'), + ("from_date", f' and posting_date>="{filters.get("from_date")}"'), + ("to_date", f' and posting_date<="{filters.get("to_date")}"')): + if filters.get(opts[0]): + conditions += opts[1] + return conditions - for opts in (("company", " and company=%(company)s"), - ("from_date", " and posting_date>=%(from_date)s"), - ("to_date", " and posting_date<=%(to_date)s")): +def get_reverse_charge_tax(filters): + return frappe.db.sql(f""" + select sum(debit) from + `tabPurchase Invoice` inner join `tabGL Entry` + on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name + where + `tabPurchase Invoice`.reverse_charge = "Y" + and `tabPurchase Invoice`.docstatus = 1 + and `tabGL Entry`.docstatus = 1 {get_conditions_join(filters)} + and account in ("{'", "'.join(get_tax_accounts(filters['company']))}"); + """)[0][0] + + +def get_reverse_charge_total(filters): + return frappe.db.sql(f""" + select sum(total) from + `tabPurchase Invoice` + where + reverse_charge = "Y" + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] + +def get_conditions_join(filters): + conditions = "" + for opts in (("company", f' and `tabPurchase Invoice`.company="{filters.get("company")}"'), + ("from_date", f' and `tabPurchase Invoice`.posting_date>="{filters.get("from_date")}"'), + ("to_date", f' and `tabPurchase Invoice`.posting_date<="{filters.get("to_date")}"')): if filters.get(opts[0]): conditions += opts[1] return conditions \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index a2938bb300..d38d647277 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -20,7 +20,7 @@ def make_custom_fields(): insert_after='group_same_items', print_hide=1, collapsible=1), dict(fieldname='permit_no', label='Permit Number', fieldtype='Data', insert_after='vat_section', print_hide=1), - dict(fieldname='reverse_charge_applicable', label='Reverse Charge Applicable', + dict(fieldname='reverse_charge', label='Reverse Charge Applicable', fieldtype='Select', insert_after='permit_no', print_hide=1, options='Y\nN', default='N') ] @@ -42,7 +42,7 @@ def make_custom_fields(): fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), dict(fieldname='emirate', label='Emirate', insert_after='customer_address', - fetch_from='customer_address.emirates'), + fieldtype='Read Only', fetch_from='customer_address.emirates'), ] invoice_item_fields = [ @@ -79,8 +79,8 @@ def make_custom_fields(): fieldtype='Data', insert_after='supplier_name'), ], 'Address': [ - dict(fieldname='emirates', label='Emirates', - fieldtype='Data', insert_after='state'), + dict(fieldname='emirates', label='Emirates', fieldtype='Select', insert_after='state', + options='Abu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, 'Purchase Order': purchase_invoice_fields + invoice_fields, From 323791d123e63e9a7b61109d7a727137e7fe81d0 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 10 Sep 2020 17:53:29 +0530 Subject: [PATCH 005/154] refactor: added docstrings --- erpnext/regional/report/uae_vat/uae_vat.py | 75 +++++++++++++++++++--- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat/uae_vat.py index 50d1ddd351..4c4c6bc2ab 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.py +++ b/erpnext/regional/report/uae_vat/uae_vat.py @@ -12,6 +12,11 @@ def execute(filters=None): return columns, data def get_columns(): + """Creates a list of dictionaries that are used to generate column headers of the data table + + Returns: + List(Dict): list of dictionaries that are used to generate column headers of the data table + """ return [ { "fieldname": "no", @@ -46,6 +51,14 @@ def get_columns(): ] def get_data(filters = None): + """Returns the list of dictionaries. Each dictionary is a row in the datatable + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + List(Dict): Each dictionary is a row in the datatable + """ data = [] total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() @@ -90,6 +103,11 @@ def get_total_emiratewise(filters): """, filters) def get_emirates(): + """Returns a List of emirates in the order that they are to be displayed + + Returns: + List(String): List of emirates in the order that they are to be displayed + """ return [ 'Abu Dhabi', 'Dubai', @@ -101,6 +119,14 @@ def get_emirates(): ] def get_conditions(filters): + """The conditions to be used to filter data to calculate the total sale + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + String: Concatenated list of conditions to be applied to calculate the total sale + """ conditions = "" for opts in (("company", f' and company="{filters.get("company")}"'), ("from_date", f' and posting_date>="{filters.get("from_date")}"'), @@ -109,7 +135,40 @@ def get_conditions(filters): conditions += opts[1] return conditions +def get_reverse_charge_total(filters): + """Returns the sum of the total of each Purchase invoice made + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the total of each Purchase invoice made + """ + conditions = """ + for opts in (("company", f' and company="{filters.get("company")}"'), + ("from_date", f' and posting_date>="{filters.get("from_date")}"'), + ("to_date", f' and posting_date<="{filters.get("to_date")}"')): + if filters.get(opts[0]): + conditions += opts[1] + return conditions + """ + return frappe.db.sql(f""" + select sum(total) from + `tabPurchase Invoice` + where + reverse_charge = "Y" + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] + def get_reverse_charge_tax(filters): + """Returns the sum of the tax of each Purchase invoice made + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the tax of each Purchase invoice made + """ return frappe.db.sql(f""" select sum(debit) from `tabPurchase Invoice` inner join `tabGL Entry` @@ -122,16 +181,16 @@ def get_reverse_charge_tax(filters): """)[0][0] -def get_reverse_charge_total(filters): - return frappe.db.sql(f""" - select sum(total) from - `tabPurchase Invoice` - where - reverse_charge = "Y" - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] def get_conditions_join(filters): + """The conditions to be used to filter data to calculate the total vat + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + String: Concatenated list of conditions to be applied to calculate the total vat + """ conditions = "" for opts in (("company", f' and `tabPurchase Invoice`.company="{filters.get("company")}"'), ("from_date", f' and `tabPurchase Invoice`.posting_date>="{filters.get("from_date")}"'), From f44524dd3e3ed87ed112d99ea7fef8cf450c9dcd Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 10 Sep 2020 18:35:04 +0530 Subject: [PATCH 006/154] feat: Added a Chart to compare Vat and sales between emirates --- erpnext/regional/report/uae_vat/uae_vat.py | 48 +++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat/uae_vat.py index 4c4c6bc2ab..8eeff51723 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.py +++ b/erpnext/regional/report/uae_vat/uae_vat.py @@ -4,12 +4,12 @@ from __future__ import unicode_literals import frappe from erpnext.regional.united_arab_emirates.utils import get_tax_accounts +from frappe import _ def execute(filters=None): columns = get_columns() - data = get_data(filters) - - return columns, data + data, chart = get_data(filters) + return columns, data, None, chart def get_columns(): """Creates a list of dictionaries that are used to generate column headers of the data table @@ -51,13 +51,14 @@ def get_columns(): ] def get_data(filters = None): - """Returns the list of dictionaries. Each dictionary is a row in the datatable + """Returns the list of dictionaries. Each dictionary is a row in the datatable and chart data Args: filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. Returns: List(Dict): Each dictionary is a row in the datatable + Dict: Dictionary containing chart data """ data = [] total_emiratewise = get_total_emiratewise(filters) @@ -70,6 +71,9 @@ def get_data(filters = None): "amount": amount, "vat_amount": vat } + + chart = get_chart_data(emirates, amounts_by_emirate) + for d, emirate in enumerate(emirates, 97): if emirate in amounts_by_emirate: amounts_by_emirate[emirate]["no"] = f'1{chr(d)}' @@ -92,9 +96,43 @@ def get_data(filters = None): "vat_amount": get_reverse_charge_tax(filters) } ) - return data + return data, chart +def get_chart_data(emirates, amounts_by_emirate): + """Returns chart data + + Args: + emirates (List): List of Emirates + amounts_by_emirate (Dict): Vat and Tax amount by emirates with emirates as key + + Returns: + [Dict]: Chart Data + """ + labels = [] + amount = [] + vat_amount = [] + for d in emirates: + if d in amounts_by_emirate: + amount.append(amounts_by_emirate[d]["amount"]) + vat_amount.append(amounts_by_emirate[d]["vat_amount"]) + labels.append(d) + + datasets = [] + datasets.append({'name': _('Amount (AED)'), 'values': amount}) + datasets.append({'name': _('Vat Amount (AED)'), 'values': vat_amount}) + + chart = { + "data": { + 'labels': labels, + 'datasets': datasets + } + } + + chart["type"] = "bar" + chart["fieldtype"] = "Currency" + return chart + def get_total_emiratewise(filters): return frappe.db.sql(f""" select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` From 3ef11b1d647666b2eed836fd7f449e1b1bb88f8f Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 24 Sep 2020 13:21:23 +0530 Subject: [PATCH 007/154] feat(UAE VAT 21): Add region fields for UAE VAT 21 --- erpnext/hooks.py | 3 +++ .../regional/united_arab_emirates/setup.py | 20 +++++++++++++++---- .../regional/united_arab_emirates/utils.py | 12 ++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 72be2dcc75..ec34c4e126 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -243,6 +243,9 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], + "validate": [ + "erpnext.regional.united_arab_emirates.utils.validate_returns", + ], "on_trash": "erpnext.regional.check_deletion_permission" }, "Purchase Invoice": { diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index d38d647277..f116303d80 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -20,9 +20,6 @@ def make_custom_fields(): insert_after='group_same_items', print_hide=1, collapsible=1), dict(fieldname='permit_no', label='Permit Number', fieldtype='Data', insert_after='vat_section', print_hide=1), - dict(fieldname='reverse_charge', label='Reverse Charge Applicable', - fieldtype='Select', insert_after='permit_no', print_hide=1, - options='Y\nN', default='N') ] purchase_invoice_fields = [ @@ -31,7 +28,12 @@ def make_custom_fields(): fetch_from='company.tax_id', print_hide=1), dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Read Only', insert_after='supplier_name', - fetch_from='supplier.supplier_name_in_arabic', print_hide=1) + fetch_from='supplier.supplier_name_in_arabic', print_hide=1), + dict(fieldname='reverse_charge', label='Reverse Charge Applicable', + fieldtype='Select', insert_after='permit_no', print_hide=1, + options='Y\nN', default='N'), + dict(fieldname='claimable_reverse_charge', label='Claimable Reverse Charge (Percentage)', + insert_after='reverse_charge', fieldtype='Percent'), ] sales_invoice_fields = [ @@ -43,6 +45,12 @@ def make_custom_fields(): fetch_from='customer.customer_name_in_arabic', print_hide=1), dict(fieldname='emirate', label='Emirate', insert_after='customer_address', fieldtype='Read Only', fetch_from='customer_address.emirates'), + dict(fieldname='returns_column_break', fieldtype='Column Break', + insert_after='select_print_heading'), + dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', + insert_after='returns_column_break', fieldtype='Currency',), + dict(fieldname='standard_rated_expenses', label='Standard Rated Expenses (AED)', + insert_after='tourist_tax_return', fieldtype='Currency',), ] invoice_item_fields = [ @@ -69,6 +77,10 @@ def make_custom_fields(): 'Item': [ dict(fieldname='tax_code', label='Tax Code', fieldtype='Data', insert_after='item_group'), + # dict(fieldname='is_zero_rated', label='Is Zero Rated', + # fieldtype='Check', insert_after='tax_code'), + # dict(fieldname='is_exempt', label='Is Exempt ', + # fieldtype='Check', insert_after='is_zero_rated') ], 'Customer': [ dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 05a99d190a..5242c63e63 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -166,4 +166,14 @@ def make_regional_gl_entries(gl_entries, doc): else tax.tax_amount_after_discount_amount }, account_currency, item=tax) ) - return gl_entries \ No newline at end of file + return gl_entries + +def validate_returns(doc, method): + print("validate_returns") + country = frappe.get_cached_value('Company', doc.company, 'country') + + if country != 'United Arab Emirates': + return + + if flt(doc.tourist_tax_return) + flt(doc.standard_rated_expenses) > flt(doc.total_taxes_and_charges): + frappe.throw(_("The Total Returns(Tax Refund provided to Tourists (AED) + Standard Rated Expenses (AED)) should be less than the Total Taxes and Charges (Company Currency)")) \ No newline at end of file From 5225215d3116cefd45f5c1ed7a7fce2fddab9522 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 24 Sep 2020 18:41:43 +0530 Subject: [PATCH 008/154] feat(UAE VAT 21): Add rows for inputs and tourists --- erpnext/regional/report/uae_vat/uae_vat.py | 168 +++++++++++++++++- .../regional/united_arab_emirates/setup.py | 11 +- 2 files changed, 173 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat/uae_vat.py index 8eeff51723..b3e52be3e5 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.py +++ b/erpnext/regional/report/uae_vat/uae_vat.py @@ -61,6 +61,7 @@ def get_data(filters = None): Dict: Dictionary containing chart data """ data = [] + data.append({"legend": f'VAT on Sales and All Other Outputs',}) total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() amounts_by_emirate = {} @@ -88,6 +89,16 @@ def get_data(filters = None): "vat_amount": 0 } ) + + data.append( + { + "no": '2', + "legend": f'Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme', + "amount": (-1) * get_tourist_tax_return_total(filters), + "vat_amount": (-1) * get_tourist_tax_return_tax(filters) + } + ) + data.append( { "no": '3', @@ -96,6 +107,25 @@ def get_data(filters = None): "vat_amount": get_reverse_charge_tax(filters) } ) + + data.append({"legend": f'VAT on Expenses and All Other Inputs'}) + data.append( + { + "no": '9', + "legend": f'Standard Rated Expenses', + "amount": get_standard_rated_expenses_total(filters), + "vat_amount": get_standard_rated_expenses_tax(filters) + } + ) + data.append( + { + "no": '10', + "legend": f'Supplies subject to the reverse charge provision', + "amount": get_reverse_charge_recoverable_total(filters), + "vat_amount": get_reverse_charge_recoverable_tax(filters) + } + ) + return data, chart @@ -235,4 +265,140 @@ def get_conditions_join(filters): ("to_date", f' and `tabPurchase Invoice`.posting_date<="{filters.get("to_date")}"')): if filters.get(opts[0]): conditions += opts[1] - return conditions \ No newline at end of file + return conditions + + +def get_reverse_charge_recoverable_total(filters): + """Returns the sum of the total of each Purchase invoice made with claimable reverse charge + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the total of each Purchase invoice made with claimable reverse charge + """ + conditions = """ + for opts in (("company", f' and company="{filters.get("company")}"'), + ("from_date", f' and posting_date>="{filters.get("from_date")}"'), + ("to_date", f' and posting_date<="{filters.get("to_date")}"')): + if filters.get(opts[0]): + conditions += opts[1] + return conditions + """ + return frappe.db.sql(f""" + select sum(total) from + `tabPurchase Invoice` + where + reverse_charge = "Y" + and claimable_reverse_charge > 0 + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] + + +def get_reverse_charge_recoverable_tax(filters): + """Returns the sum of the tax of each Purchase invoice made + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the tax of each Purchase invoice made + """ + return frappe.db.sql(f""" + select sum(debit * `tabPurchase Invoice`.claimable_reverse_charge / 100) from + `tabPurchase Invoice` inner join `tabGL Entry` + on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name + where + `tabPurchase Invoice`.reverse_charge = "Y" + and `tabPurchase Invoice`.docstatus = 1 + and `tabPurchase Invoice`.claimable_reverse_charge > 0 + and `tabGL Entry`.docstatus = 1 {get_conditions_join(filters)} + and account in ("{'", "'.join(get_tax_accounts(filters['company']))}"); + """)[0][0] + + +def get_standard_rated_expenses_total(filters): + """Returns the sum of the total of each Purchase invoice made with claimable reverse charge + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the total of each Purchase invoice made with claimable reverse charge + """ + conditions = """ + for opts in (("company", f' and company="{filters.get("company")}"'), + ("from_date", f' and posting_date>="{filters.get("from_date")}"'), + ("to_date", f' and posting_date<="{filters.get("to_date")}"')): + if filters.get(opts[0]): + conditions += opts[1] + return conditions + """ + return frappe.db.sql(f""" + select sum(total) from + `tabSales Invoice` + where + standard_rated_expenses > 0 + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] + + +def get_standard_rated_expenses_tax(filters): + """Returns the sum of the tax of each Purchase invoice made + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the tax of each Purchase invoice made + """ + return frappe.db.sql(f""" + select sum(standard_rated_expenses) from + `tabSales Invoice` + where + standard_rated_expenses > 0 + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] + +def get_tourist_tax_return_total(filters): + """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the total of each Sales invoice with non zero tourist_tax_return + """ + conditions = """ + for opts in (("company", f' and company="{filters.get("company")}"'), + ("from_date", f' and posting_date>="{filters.get("from_date")}"'), + ("to_date", f' and posting_date<="{filters.get("to_date")}"')): + if filters.get(opts[0]): + conditions += opts[1] + return conditions + """ + return frappe.db.sql(f""" + select sum(total) from + `tabSales Invoice` + where + tourist_tax_return > 0 + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] + + +def get_tourist_tax_return_tax(filters): + """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of the tax of each Sales invoice with non zero tourist_tax_return + """ + return frappe.db.sql(f""" + select sum(tourist_tax_return) from + `tabSales Invoice` + where + tourist_tax_return > 0 + and docstatus = 1 {get_conditions(filters)} ; + """)[0][0] \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index f116303d80..01763402b1 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -33,7 +33,8 @@ def make_custom_fields(): fieldtype='Select', insert_after='permit_no', print_hide=1, options='Y\nN', default='N'), dict(fieldname='claimable_reverse_charge', label='Claimable Reverse Charge (Percentage)', - insert_after='reverse_charge', fieldtype='Percent'), + insert_after='reverse_charge', fieldtype='Percent', print_hide=1, + depends_on="eval:doc.reverse_charge=='Y'", default='100.000'), ] sales_invoice_fields = [ @@ -45,12 +46,12 @@ def make_custom_fields(): fetch_from='customer.customer_name_in_arabic', print_hide=1), dict(fieldname='emirate', label='Emirate', insert_after='customer_address', fieldtype='Read Only', fetch_from='customer_address.emirates'), - dict(fieldname='returns_column_break', fieldtype='Column Break', - insert_after='select_print_heading'), + # dict(fieldname='returns_column_break', fieldtype='Column Break', + # insert_after='select_print_heading'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', - insert_after='returns_column_break', fieldtype='Currency',), + insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), dict(fieldname='standard_rated_expenses', label='Standard Rated Expenses (AED)', - insert_after='tourist_tax_return', fieldtype='Currency',), + insert_after='tourist_tax_return', fieldtype='Currency', print_hide=1, default='0'), ] invoice_item_fields = [ From 72cad2a8abd173b2c84029bfdff6df38d6e87a6f Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Fri, 25 Sep 2020 13:13:38 +0530 Subject: [PATCH 009/154] feat(UAE VAT 21): Add zero rated and vat exempt --- erpnext/regional/report/uae_vat/uae_vat.js | 12 ++- erpnext/regional/report/uae_vat/uae_vat.py | 87 +++++++++++++++---- .../regional/united_arab_emirates/setup.py | 21 +++-- 3 files changed, 96 insertions(+), 24 deletions(-) diff --git a/erpnext/regional/report/uae_vat/uae_vat.js b/erpnext/regional/report/uae_vat/uae_vat.js index 45df167823..0213956d44 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.js +++ b/erpnext/regional/report/uae_vat/uae_vat.js @@ -27,5 +27,15 @@ frappe.query_reports["UAE VAT"] = { "reqd": 1, "default": frappe.datetime.get_today() }, - ] + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.legend=='VAT on Sales and All Other Outputs' || data.legend=='VAT on Expenses and All Other Inputs') + && data.legend==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + } + return value; + }, }; diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat/uae_vat.py index b3e52be3e5..1a3537c268 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.py +++ b/erpnext/regional/report/uae_vat/uae_vat.py @@ -41,12 +41,6 @@ def get_columns(): "label": "VAT Amount (AED)", "fieldtype": "Currency", "width": 100 - }, - { - "fieldname": "adjustment", - "label": "Adjustment (AED)", - "fieldtype": "Currency", - "width": 100 } ] @@ -61,7 +55,12 @@ def get_data(filters = None): Dict: Dictionary containing chart data """ data = [] - data.append({"legend": f'VAT on Sales and All Other Outputs',}) + data.append({ + "no": '', + "legend": f'VAT on Sales and All Other Outputs', + "amount": '', + "vat_amount": '' + }) total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() amounts_by_emirate = {} @@ -108,7 +107,30 @@ def get_data(filters = None): } ) - data.append({"legend": f'VAT on Expenses and All Other Inputs'}) + data.append( + { + "no": '4', + "legend": f'Zero Rated', + "amount": get_zero_rated_total(filters), + "vat_amount": "-" + } + ) + + data.append( + { + "no": '5', + "legend": f'Exempt Supplies', + "amount": get_exempt_total(filters), + "vat_amount": "-" + } + ) + + data.append({ + "no": '', + "legend": f'VAT on Expenses and All Other Inputs', + "amount": '', + "vat_amount": '' + }) data.append( { "no": '9', @@ -226,7 +248,7 @@ def get_reverse_charge_total(filters): where reverse_charge = "Y" and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] + """)[0][0] or 0 def get_reverse_charge_tax(filters): """Returns the sum of the tax of each Purchase invoice made @@ -246,7 +268,7 @@ def get_reverse_charge_tax(filters): and `tabPurchase Invoice`.docstatus = 1 and `tabGL Entry`.docstatus = 1 {get_conditions_join(filters)} and account in ("{'", "'.join(get_tax_accounts(filters['company']))}"); - """)[0][0] + """)[0][0] or 0 @@ -292,7 +314,7 @@ def get_reverse_charge_recoverable_total(filters): reverse_charge = "Y" and claimable_reverse_charge > 0 and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] + """)[0][0] or 0 def get_reverse_charge_recoverable_tax(filters): @@ -314,7 +336,7 @@ def get_reverse_charge_recoverable_tax(filters): and `tabPurchase Invoice`.claimable_reverse_charge > 0 and `tabGL Entry`.docstatus = 1 {get_conditions_join(filters)} and account in ("{'", "'.join(get_tax_accounts(filters['company']))}"); - """)[0][0] + """)[0][0] or 0 def get_standard_rated_expenses_total(filters): @@ -340,7 +362,7 @@ def get_standard_rated_expenses_total(filters): where standard_rated_expenses > 0 and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] + """)[0][0] or 0 def get_standard_rated_expenses_tax(filters): @@ -358,7 +380,7 @@ def get_standard_rated_expenses_tax(filters): where standard_rated_expenses > 0 and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] + """)[0][0] or 0 def get_tourist_tax_return_total(filters): """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return @@ -383,7 +405,7 @@ def get_tourist_tax_return_total(filters): where tourist_tax_return > 0 and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] + """)[0][0] or 0 def get_tourist_tax_return_tax(filters): @@ -401,4 +423,37 @@ def get_tourist_tax_return_tax(filters): where tourist_tax_return > 0 and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] \ No newline at end of file + """)[0][0] or 0 + +def get_zero_rated_total(filters): + """Returns the sum of each Sales Invoice Item Amount which is zero rated + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of each Sales Invoice Item Amount which is zero rated + """ + return frappe.db.sql(f""" + select sum(i.base_amount) as total from + `tabSales Invoice Item` i, `tabSales Invoice` s + where s.docstatus = 1 and i.parent = s.name and i.is_zero_rated = 1 + {get_conditions(filters)} ; + """)[0][0] or 0 + + +def get_exempt_total(filters): + """Returns the sum of each Sales Invoice Item Amount which is Vat Exempt + + Args: + filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. + + Returns: + Float: sum of each Sales Invoice Item Amount which is Vat Exempt + """ + return frappe.db.sql(f""" + select sum(i.base_amount) as total from + `tabSales Invoice Item` i, `tabSales Invoice` s + where s.docstatus = 1 and i.parent = s.name and i.is_exempt = 1 + {get_conditions(filters)} ; + """)[0][0] or 0 \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 01763402b1..29ad41c9af 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -15,6 +15,13 @@ def setup(company=None, patch=True): create_sales_tax(company) def make_custom_fields(): + is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated', + fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description', + print_hide=1) + is_exempt = dict(fieldname='is_exempt', label='Is Exempt', + fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated', + print_hide=1) + invoice_fields = [ dict(fieldname='vat_section', label='VAT Details', fieldtype='Section Break', insert_after='group_same_items', print_hide=1, collapsible=1), @@ -46,8 +53,6 @@ def make_custom_fields(): fetch_from='customer.customer_name_in_arabic', print_hide=1), dict(fieldname='emirate', label='Emirate', insert_after='customer_address', fieldtype='Read Only', fetch_from='customer_address.emirates'), - # dict(fieldname='returns_column_break', fieldtype='Column Break', - # insert_after='select_print_heading'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), dict(fieldname='standard_rated_expenses', label='Standard Rated Expenses (AED)', @@ -78,10 +83,12 @@ def make_custom_fields(): 'Item': [ dict(fieldname='tax_code', label='Tax Code', fieldtype='Data', insert_after='item_group'), - # dict(fieldname='is_zero_rated', label='Is Zero Rated', - # fieldtype='Check', insert_after='tax_code'), - # dict(fieldname='is_exempt', label='Is Exempt ', - # fieldtype='Check', insert_after='is_zero_rated') + dict(fieldname='is_zero_rated', label='Is Zero Rated', + fieldtype='Check', insert_after='tax_code', + print_hide=1), + dict(fieldname='is_exempt', label='Is Exempt ', + fieldtype='Check', insert_after='is_zero_rated', + print_hide=1) ], 'Customer': [ dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', @@ -101,7 +108,7 @@ def make_custom_fields(): 'Sales Invoice': sales_invoice_fields + invoice_fields, 'Sales Order': sales_invoice_fields + invoice_fields, 'Delivery Note': sales_invoice_fields + invoice_fields, - 'Sales Invoice Item': invoice_item_fields + delivery_date_field, + 'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt], 'Purchase Invoice Item': invoice_item_fields, 'Sales Order Item': invoice_item_fields, 'Delivery Note Item': invoice_item_fields, From 321b5f54aef19c66460657552695af65b4a977a6 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Fri, 25 Sep 2020 14:24:57 +0530 Subject: [PATCH 010/154] feat(UAE VAT 21): Add Roles for UAE VAT 21 Report --- erpnext/regional/united_arab_emirates/setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 29ad41c9af..95fde3ab9f 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -10,6 +10,7 @@ from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax def setup(company=None, patch=True): make_custom_fields() add_print_formats() + add_custom_roles_for_reports() if company: create_sales_tax(company) @@ -127,3 +128,15 @@ def add_print_formats(): frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """) + +def add_custom_roles_for_reports(): + if not frappe.db.get_value('Custom Role', dict(report='UAE VAT')): + frappe.get_doc(dict( + doctype='Custom Role', + report='UAE VAT', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager'), + dict(role='Auditor') + ] + )).insert() \ No newline at end of file From 127fbfcb931f4a7ed215a6102dfa3e8ff18155f7 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Fri, 25 Sep 2020 20:42:54 +0530 Subject: [PATCH 011/154] feat(UAE VAT 21): Added Print Format --- erpnext/regional/report/uae_vat/uae_vat.html | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 erpnext/regional/report/uae_vat/uae_vat.html diff --git a/erpnext/regional/report/uae_vat/uae_vat.html b/erpnext/regional/report/uae_vat/uae_vat.html new file mode 100644 index 0000000000..861c0c818d --- /dev/null +++ b/erpnext/regional/report/uae_vat/uae_vat.html @@ -0,0 +1,68 @@ +{% + var report_columns = report.get_columns_for_print(); + report_columns = report_columns.filter(col => !col.hidden); + + if (report_columns.length > 8) { + frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); + } +%} + +

{%= __(report.report_name) %}

+ +

{%= __("VAT on Sales and All Other Outputs") %}

+ + + + + {% for (let i=0; i{%= report_columns[i].label %} + {% } %} + + + + {% for (let j=1; j<12; j++) { %} + {% + var row = data[j]; + %} + + {% for (let i=0; i + {% const fieldname = report_columns[i].fieldname; %} + {% if (!is_null(row[fieldname])) { %} + {%= frappe.format(row[fieldname], report_columns[i], {}, row) %} + {% } %} + + {% } %} + + {% } %} + +
+ +

{%= __("VAT on Expenses and All Other Inputs") %}

+ + + + {% for (let i=0; i{%= report_columns[i].label %} + {% } %} + + + + {% for (let j=12; j + {% for (let i=0; i + {% const fieldname = report_columns[i].fieldname; %} + {% if (!is_null(row[fieldname])) { %} + {%= frappe.format(row[fieldname], report_columns[i], {}, row) %} + {% } %} + + {% } %} + + {% } %} + + +
\ No newline at end of file From 79aca28453c2eb5a7d8c917459269c2efcb0a96d Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 12:27:41 +0530 Subject: [PATCH 012/154] refactor(UAE VAT 21): rename UAE VAT - UAE VAT 21 --- erpnext/regional/report/{uae_vat => uae_vat_21}/__init__.py | 0 .../{uae_vat/uae_vat.html => uae_vat_21/uae_vat_21.html} | 0 .../report/{uae_vat/uae_vat.js => uae_vat_21/uae_vat_21.js} | 2 +- .../{uae_vat/uae_vat.json => uae_vat_21/uae_vat_21.json} | 4 ++-- .../report/{uae_vat/uae_vat.py => uae_vat_21/uae_vat_21.py} | 0 erpnext/regional/united_arab_emirates/setup.py | 4 ++-- 6 files changed, 5 insertions(+), 5 deletions(-) rename erpnext/regional/report/{uae_vat => uae_vat_21}/__init__.py (100%) rename erpnext/regional/report/{uae_vat/uae_vat.html => uae_vat_21/uae_vat_21.html} (100%) rename erpnext/regional/report/{uae_vat/uae_vat.js => uae_vat_21/uae_vat_21.js} (96%) rename erpnext/regional/report/{uae_vat/uae_vat.json => uae_vat_21/uae_vat_21.json} (89%) rename erpnext/regional/report/{uae_vat/uae_vat.py => uae_vat_21/uae_vat_21.py} (100%) diff --git a/erpnext/regional/report/uae_vat/__init__.py b/erpnext/regional/report/uae_vat_21/__init__.py similarity index 100% rename from erpnext/regional/report/uae_vat/__init__.py rename to erpnext/regional/report/uae_vat_21/__init__.py diff --git a/erpnext/regional/report/uae_vat/uae_vat.html b/erpnext/regional/report/uae_vat_21/uae_vat_21.html similarity index 100% rename from erpnext/regional/report/uae_vat/uae_vat.html rename to erpnext/regional/report/uae_vat_21/uae_vat_21.html diff --git a/erpnext/regional/report/uae_vat/uae_vat.js b/erpnext/regional/report/uae_vat_21/uae_vat_21.js similarity index 96% rename from erpnext/regional/report/uae_vat/uae_vat.js rename to erpnext/regional/report/uae_vat_21/uae_vat_21.js index 0213956d44..3e6cdd85e5 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.js +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["UAE VAT"] = { +frappe.query_reports["UAE VAT 21"] = { "filters": [ { "fieldname": "company", diff --git a/erpnext/regional/report/uae_vat/uae_vat.json b/erpnext/regional/report/uae_vat_21/uae_vat_21.json similarity index 89% rename from erpnext/regional/report/uae_vat/uae_vat.json rename to erpnext/regional/report/uae_vat_21/uae_vat_21.json index 8807405a98..421990cc4a 100644 --- a/erpnext/regional/report/uae_vat/uae_vat.json +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.json @@ -12,11 +12,11 @@ "modified": "2020-09-10 08:51:02.298482", "modified_by": "Administrator", "module": "Regional", - "name": "UAE VAT", + "name": "UAE VAT 21", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "GL Entry", - "report_name": "UAE VAT", + "report_name": "UAE VAT 21", "report_type": "Script Report", "roles": [] } \ No newline at end of file diff --git a/erpnext/regional/report/uae_vat/uae_vat.py b/erpnext/regional/report/uae_vat_21/uae_vat_21.py similarity index 100% rename from erpnext/regional/report/uae_vat/uae_vat.py rename to erpnext/regional/report/uae_vat_21/uae_vat_21.py diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 95fde3ab9f..e7a0609f4a 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -130,10 +130,10 @@ def add_print_formats(): name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """) def add_custom_roles_for_reports(): - if not frappe.db.get_value('Custom Role', dict(report='UAE VAT')): + if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 21')): frappe.get_doc(dict( doctype='Custom Role', - report='UAE VAT', + report='UAE VAT 21', roles= [ dict(role='Accounts User'), dict(role='Accounts Manager'), From 815b7584dddbe4c29cc453c98d80d697035103c7 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 13:24:40 +0530 Subject: [PATCH 013/154] refactor(UAE VAT 21): seperate get_chart, get_data --- erpnext/regional/report/uae_vat_21/uae_vat_21.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.py b/erpnext/regional/report/uae_vat_21/uae_vat_21.py index 1a3537c268..d21cecd826 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.py +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.py @@ -8,7 +8,9 @@ from frappe import _ def execute(filters=None): columns = get_columns() - data, chart = get_data(filters) + data, emirates, amounts_by_emirate = get_data(filters) + chart = get_chart(emirates, amounts_by_emirate) + return columns, data, None, chart def get_columns(): @@ -72,8 +74,6 @@ def get_data(filters = None): "vat_amount": vat } - chart = get_chart_data(emirates, amounts_by_emirate) - for d, emirate in enumerate(emirates, 97): if emirate in amounts_by_emirate: amounts_by_emirate[emirate]["no"] = f'1{chr(d)}' @@ -148,10 +148,10 @@ def get_data(filters = None): } ) - return data, chart + return data, emirates, amounts_by_emirate -def get_chart_data(emirates, amounts_by_emirate): +def get_chart(emirates, amounts_by_emirate): """Returns chart data Args: From bed1add349cf571a912f8956ba7d86b91f48cfbe Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 13:26:05 +0530 Subject: [PATCH 014/154] docs(UAE VAT 21): add docstrings --- erpnext/regional/united_arab_emirates/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index e7a0609f4a..07543b31ea 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -130,6 +130,8 @@ def add_print_formats(): name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """) def add_custom_roles_for_reports(): + """Add Access Control to UAE VAT 21 + """ if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 21')): frappe.get_doc(dict( doctype='Custom Role', From 2a601e59bfb78d74551fb02af82fb17d8acef725 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 13:26:43 +0530 Subject: [PATCH 015/154] fix(UAE VAT 21): remove extra legend --- erpnext/regional/report/uae_vat_21/uae_vat_21.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.html b/erpnext/regional/report/uae_vat_21/uae_vat_21.html index 861c0c818d..aa6a255932 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.html +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.html @@ -48,7 +48,7 @@ - {% for (let j=12; j Date: Mon, 28 Sep 2020 14:38:14 +0530 Subject: [PATCH 016/154] feat(UAE VAT 21): Add vat settings --- .../doctype/uae_vat_account/__init__.py | 0 .../uae_vat_account/uae_vat_account.json | 35 ++++++++++++ .../uae_vat_account/uae_vat_account.py | 10 ++++ .../doctype/uae_vat_setting/__init__.py | 0 .../uae_vat_setting/test_uae_vat_setting.py | 10 ++++ .../uae_vat_setting/uae_vat_setting.js | 8 +++ .../uae_vat_setting/uae_vat_setting.json | 55 +++++++++++++++++++ .../uae_vat_setting/uae_vat_setting.py | 10 ++++ .../regional/united_arab_emirates/utils.py | 9 +-- 9 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 erpnext/regional/doctype/uae_vat_account/__init__.py create mode 100644 erpnext/regional/doctype/uae_vat_account/uae_vat_account.json create mode 100644 erpnext/regional/doctype/uae_vat_account/uae_vat_account.py create mode 100644 erpnext/regional/doctype/uae_vat_setting/__init__.py create mode 100644 erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py create mode 100644 erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js create mode 100644 erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json create mode 100644 erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py diff --git a/erpnext/regional/doctype/uae_vat_account/__init__.py b/erpnext/regional/doctype/uae_vat_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/uae_vat_account/uae_vat_account.json b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.json new file mode 100644 index 0000000000..73a8169207 --- /dev/null +++ b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.json @@ -0,0 +1,35 @@ +{ + "actions": [], + "autoname": "account", + "creation": "2020-09-28 11:30:45.472053", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "in_preview": 1, + "label": "Account", + "options": "Account" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-09-28 12:02:56.444007", + "modified_by": "Administrator", + "module": "Regional", + "name": "UAE VAT Account", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py new file mode 100644 index 0000000000..80d6b3a5f1 --- /dev/null +++ b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UAEVATAccount(Document): + pass diff --git a/erpnext/regional/doctype/uae_vat_setting/__init__.py b/erpnext/regional/doctype/uae_vat_setting/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py b/erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py new file mode 100644 index 0000000000..a5f151dc73 --- /dev/null +++ b/erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUAEVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js b/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js new file mode 100644 index 0000000000..f910e1f662 --- /dev/null +++ b/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('UAE VAT Setting', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json b/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json new file mode 100644 index 0000000000..e2e2ab8245 --- /dev/null +++ b/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2020-09-25 12:48:51.463265", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "uae_vat_account" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "uae_vat_account", + "fieldtype": "Table", + "label": "UAE VAT Account", + "options": "UAE VAT Account", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-09-28 12:19:11.493138", + "modified_by": "Administrator", + "module": "Regional", + "name": "UAE VAT Setting", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py b/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py new file mode 100644 index 0000000000..9549de9466 --- /dev/null +++ b/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UAEVATSetting(Document): + pass diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 5242c63e63..4424d2efec 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -53,12 +53,13 @@ def get_tax_accounts(company): tax_accounts: List of Tax Accounts for the company """ tax_accounts_dict = frappe._dict() - tax_accounts_list = frappe.get_all("Account", - filters={"account_type": "Tax", "company": company}, - fields=["name"]) + tax_accounts_list = frappe.get_all("UAE VAT Account", + filters={"parent": company}, + fields=["Account"] + ) if not tax_accounts_list and not frappe.flags.in_test: - frappe.throw(_("Please create at least one Account of type Tax")) + frappe.throw(_(f'Please set Vat Accounts for Company: "{company}" in UAE VAT Setting')) for d in tax_accounts_list: for key, name in d.items(): tax_accounts_dict[name] = name From 0860c74c824e337a2e5e689d67614a2644e663b8 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 14:38:45 +0530 Subject: [PATCH 017/154] feat(UAE VAT 21): Add permissions for doctypes --- erpnext/regional/united_arab_emirates/setup.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 07543b31ea..1c947d2fb0 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -5,13 +5,14 @@ from __future__ import unicode_literals import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.permissions import add_permission, update_permission_property from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax def setup(company=None, patch=True): make_custom_fields() add_print_formats() add_custom_roles_for_reports() - + add_permissions() if company: create_sales_tax(company) @@ -141,4 +142,14 @@ def add_custom_roles_for_reports(): dict(role='Accounts Manager'), dict(role='Auditor') ] - )).insert() \ No newline at end of file + )).insert() + +def add_permissions(): + """Add Permissions for UAE VAT Settings and UAE VAT Account + """ + for doctype in ('UAE VAT Setting', 'UAE VAT Account'): + add_permission(doctype, 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission(doctype, role, 0) + update_permission_property(doctype, role, 0, 'write', 1) + update_permission_property(doctype, role, 0, 'create', 1) From 587bb05a37a068962620526c47450731ee09d83f Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 14:52:29 +0530 Subject: [PATCH 018/154] feat(UAE VAT 21): Add desk page for UAE VAT 21 --- erpnext/accounts/desk_page/accounting/accounting.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 2c5231491c..6621e8e0ce 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -79,6 +79,11 @@ "hidden": 0, "label": "Profitability", "links": "[\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Gross Profit\",\n \"name\": \"Gross Profit\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profitability Analysis\",\n \"name\": \"Profitability Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"type\": \"report\"\n }\n]" + }, + { + "hidden": 0, + "label": "Value-Added Tax (VAT UAE)", + "links": "[\n {\n \"label\": \"UAE VAT Setting\",\n \"name\": \"UAE VAT Setting\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"UAE VAT 21\",\n \"name\": \"UAE VAT 21\",\n \"type\": \"report\"\n }\n]" } ], "category": "Modules", @@ -98,7 +103,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-09-03 10:37:07.865801", + "modified": "2020-09-28 13:19:27.703992", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", From 80068e16e856ebd37c782132be1d68c0c9d2e923 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 18:58:27 +0530 Subject: [PATCH 019/154] Refactor(UAE VAT 21): Replace f-strings by format --- .../regional/report/uae_vat_21/uae_vat_21.py | 135 ++++++++---------- 1 file changed, 57 insertions(+), 78 deletions(-) diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.py b/erpnext/regional/report/uae_vat_21/uae_vat_21.py index d21cecd826..6cb39e0835 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.py +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.py @@ -218,9 +218,9 @@ def get_conditions(filters): String: Concatenated list of conditions to be applied to calculate the total sale """ conditions = "" - for opts in (("company", f' and company="{filters.get("company")}"'), - ("from_date", f' and posting_date>="{filters.get("from_date")}"'), - ("to_date", f' and posting_date<="{filters.get("to_date")}"')): + for opts in (("company", " and company=%(company)s"), + ("from_date", " and posting_date>=%(from_date)s"), + ("to_date", " and posting_date<=%(to_date)s")): if filters.get(opts[0]): conditions += opts[1] return conditions @@ -234,21 +234,21 @@ def get_reverse_charge_total(filters): Returns: Float: sum of the total of each Purchase invoice made """ - conditions = """ - for opts in (("company", f' and company="{filters.get("company")}"'), - ("from_date", f' and posting_date>="{filters.get("from_date")}"'), - ("to_date", f' and posting_date<="{filters.get("to_date")}"')): - if filters.get(opts[0]): - conditions += opts[1] - return conditions - """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + print(""" select sum(total) from `tabPurchase Invoice` where reverse_charge = "Y" - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] or 0 + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions)) + return frappe.db.sql(""" + select sum(total) from + `tabPurchase Invoice` + where + reverse_charge = "Y" + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_reverse_charge_tax(filters): """Returns the sum of the tax of each Purchase invoice made @@ -259,18 +259,18 @@ def get_reverse_charge_tax(filters): Returns: Float: sum of the tax of each Purchase invoice made """ - return frappe.db.sql(f""" + conditions = get_conditions_join(filters) + return frappe.db.sql(""" select sum(debit) from `tabPurchase Invoice` inner join `tabGL Entry` on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name where `tabPurchase Invoice`.reverse_charge = "Y" and `tabPurchase Invoice`.docstatus = 1 - and `tabGL Entry`.docstatus = 1 {get_conditions_join(filters)} - and account in ("{'", "'.join(get_tax_accounts(filters['company']))}"); - """)[0][0] or 0 - - + and `tabGL Entry`.docstatus = 1 + and account in (select account from `tabUAE VAT Account` where parent=%(company)s) + {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_conditions_join(filters): """The conditions to be used to filter data to calculate the total vat @@ -282,14 +282,13 @@ def get_conditions_join(filters): String: Concatenated list of conditions to be applied to calculate the total vat """ conditions = "" - for opts in (("company", f' and `tabPurchase Invoice`.company="{filters.get("company")}"'), - ("from_date", f' and `tabPurchase Invoice`.posting_date>="{filters.get("from_date")}"'), - ("to_date", f' and `tabPurchase Invoice`.posting_date<="{filters.get("to_date")}"')): + for opts in (("company", " and `tabPurchase Invoice`.company=%(company)s"), + ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), + ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s")): if filters.get(opts[0]): conditions += opts[1] return conditions - def get_reverse_charge_recoverable_total(filters): """Returns the sum of the total of each Purchase invoice made with claimable reverse charge @@ -299,23 +298,15 @@ def get_reverse_charge_recoverable_total(filters): Returns: Float: sum of the total of each Purchase invoice made with claimable reverse charge """ - conditions = """ - for opts in (("company", f' and company="{filters.get("company")}"'), - ("from_date", f' and posting_date>="{filters.get("from_date")}"'), - ("to_date", f' and posting_date<="{filters.get("to_date")}"')): - if filters.get(opts[0]): - conditions += opts[1] - return conditions - """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(total) from `tabPurchase Invoice` where reverse_charge = "Y" and claimable_reverse_charge > 0 - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] or 0 - + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_reverse_charge_recoverable_tax(filters): """Returns the sum of the tax of each Purchase invoice made @@ -326,7 +317,8 @@ def get_reverse_charge_recoverable_tax(filters): Returns: Float: sum of the tax of each Purchase invoice made """ - return frappe.db.sql(f""" + conditions = get_conditions_join(filters) + return frappe.db.sql(""" select sum(debit * `tabPurchase Invoice`.claimable_reverse_charge / 100) from `tabPurchase Invoice` inner join `tabGL Entry` on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name @@ -334,10 +326,10 @@ def get_reverse_charge_recoverable_tax(filters): `tabPurchase Invoice`.reverse_charge = "Y" and `tabPurchase Invoice`.docstatus = 1 and `tabPurchase Invoice`.claimable_reverse_charge > 0 - and `tabGL Entry`.docstatus = 1 {get_conditions_join(filters)} - and account in ("{'", "'.join(get_tax_accounts(filters['company']))}"); - """)[0][0] or 0 - + and `tabGL Entry`.docstatus = 1 + and account in (select account from `tabUAE VAT Account` where parent=%(company)s) + {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_standard_rated_expenses_total(filters): """Returns the sum of the total of each Purchase invoice made with claimable reverse charge @@ -348,22 +340,14 @@ def get_standard_rated_expenses_total(filters): Returns: Float: sum of the total of each Purchase invoice made with claimable reverse charge """ - conditions = """ - for opts in (("company", f' and company="{filters.get("company")}"'), - ("from_date", f' and posting_date>="{filters.get("from_date")}"'), - ("to_date", f' and posting_date<="{filters.get("to_date")}"')): - if filters.get(opts[0]): - conditions += opts[1] - return conditions - """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(total) from `tabSales Invoice` where standard_rated_expenses > 0 - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] or 0 - + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_standard_rated_expenses_tax(filters): """Returns the sum of the tax of each Purchase invoice made @@ -374,13 +358,14 @@ def get_standard_rated_expenses_tax(filters): Returns: Float: sum of the tax of each Purchase invoice made """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(standard_rated_expenses) from `tabSales Invoice` where standard_rated_expenses > 0 - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] or 0 + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_tourist_tax_return_total(filters): """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return @@ -391,22 +376,14 @@ def get_tourist_tax_return_total(filters): Returns: Float: sum of the total of each Sales invoice with non zero tourist_tax_return """ - conditions = """ - for opts in (("company", f' and company="{filters.get("company")}"'), - ("from_date", f' and posting_date>="{filters.get("from_date")}"'), - ("to_date", f' and posting_date<="{filters.get("to_date")}"')): - if filters.get(opts[0]): - conditions += opts[1] - return conditions - """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(total) from `tabSales Invoice` where tourist_tax_return > 0 - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] or 0 - + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_tourist_tax_return_tax(filters): """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return @@ -417,13 +394,14 @@ def get_tourist_tax_return_tax(filters): Returns: Float: sum of the tax of each Sales invoice with non zero tourist_tax_return """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(tourist_tax_return) from `tabSales Invoice` where tourist_tax_return > 0 - and docstatus = 1 {get_conditions(filters)} ; - """)[0][0] or 0 + and docstatus = 1 {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_zero_rated_total(filters): """Returns the sum of each Sales Invoice Item Amount which is zero rated @@ -434,13 +412,13 @@ def get_zero_rated_total(filters): Returns: Float: sum of each Sales Invoice Item Amount which is zero rated """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(i.base_amount) as total from `tabSales Invoice Item` i, `tabSales Invoice` s where s.docstatus = 1 and i.parent = s.name and i.is_zero_rated = 1 - {get_conditions(filters)} ; - """)[0][0] or 0 - + {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 def get_exempt_total(filters): """Returns the sum of each Sales Invoice Item Amount which is Vat Exempt @@ -451,9 +429,10 @@ def get_exempt_total(filters): Returns: Float: sum of each Sales Invoice Item Amount which is Vat Exempt """ - return frappe.db.sql(f""" + conditions = get_conditions(filters) + return frappe.db.sql(""" select sum(i.base_amount) as total from `tabSales Invoice Item` i, `tabSales Invoice` s where s.docstatus = 1 and i.parent = s.name and i.is_exempt = 1 - {get_conditions(filters)} ; - """)[0][0] or 0 \ No newline at end of file + {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 \ No newline at end of file From a5e8e449eea1266b225d1616b0b400cd51feeb01 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Mon, 28 Sep 2020 20:43:03 +0530 Subject: [PATCH 020/154] chore: Merge branch 'develop' into UAE-VAT-Format --- erpnext/accounts/desk_page/accounting/accounting.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 29d1331b5d..85635572b1 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -103,11 +103,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", -<<<<<<< HEAD "modified": "2020-09-28 13:19:27.703992", -======= - "modified": "2020-09-09 11:45:33.766400", ->>>>>>> develop "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", From 3294f86b9235d57f9f143e7cc33cf367816d4aa1 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Wed, 30 Sep 2020 12:15:07 +0530 Subject: [PATCH 021/154] refactor: fix linting --- .../regional/report/uae_vat_21/uae_vat_21.py | 168 +++--------------- .../regional/united_arab_emirates/setup.py | 6 +- .../regional/united_arab_emirates/utils.py | 39 +--- 3 files changed, 36 insertions(+), 177 deletions(-) diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.py b/erpnext/regional/report/uae_vat_21/uae_vat_21.py index 6cb39e0835..d400732f39 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.py +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.united_arab_emirates.utils import get_tax_accounts from frappe import _ def execute(filters=None): @@ -14,11 +13,7 @@ def execute(filters=None): return columns, data, None, chart def get_columns(): - """Creates a list of dictionaries that are used to generate column headers of the data table - - Returns: - List(Dict): list of dictionaries that are used to generate column headers of the data table - """ + """Creates a list of dictionaries that are used to generate column headers of the data table.""" return [ { "fieldname": "no", @@ -47,22 +42,15 @@ def get_columns(): ] def get_data(filters = None): - """Returns the list of dictionaries. Each dictionary is a row in the datatable and chart data - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - List(Dict): Each dictionary is a row in the datatable - Dict: Dictionary containing chart data - """ + """Returns the list of dictionaries. Each dictionary is a row in the datatable and chart data.""" data = [] data.append({ "no": '', - "legend": f'VAT on Sales and All Other Outputs', + "legend": 'VAT on Sales and All Other Outputs', "amount": '', "vat_amount": '' }) + total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() amounts_by_emirate = {} @@ -92,7 +80,7 @@ def get_data(filters = None): data.append( { "no": '2', - "legend": f'Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme', + "legend": 'Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme', "amount": (-1) * get_tourist_tax_return_total(filters), "vat_amount": (-1) * get_tourist_tax_return_tax(filters) } @@ -101,7 +89,7 @@ def get_data(filters = None): data.append( { "no": '3', - "legend": f'Supplies subject to the reverse charge provision', + "legend": 'Supplies subject to the reverse charge provision', "amount": get_reverse_charge_total(filters), "vat_amount": get_reverse_charge_tax(filters) } @@ -110,7 +98,7 @@ def get_data(filters = None): data.append( { "no": '4', - "legend": f'Zero Rated', + "legend": 'Zero Rated', "amount": get_zero_rated_total(filters), "vat_amount": "-" } @@ -119,7 +107,7 @@ def get_data(filters = None): data.append( { "no": '5', - "legend": f'Exempt Supplies', + "legend": 'Exempt Supplies', "amount": get_exempt_total(filters), "vat_amount": "-" } @@ -127,22 +115,24 @@ def get_data(filters = None): data.append({ "no": '', - "legend": f'VAT on Expenses and All Other Inputs', + "legend": 'VAT on Expenses and All Other Inputs', "amount": '', "vat_amount": '' }) + data.append( { "no": '9', - "legend": f'Standard Rated Expenses', + "legend": 'Standard Rated Expenses', "amount": get_standard_rated_expenses_total(filters), "vat_amount": get_standard_rated_expenses_tax(filters) } ) + data.append( { "no": '10', - "legend": f'Supplies subject to the reverse charge provision', + "legend": 'Supplies subject to the reverse charge provision', "amount": get_reverse_charge_recoverable_total(filters), "vat_amount": get_reverse_charge_recoverable_tax(filters) } @@ -152,15 +142,7 @@ def get_data(filters = None): def get_chart(emirates, amounts_by_emirate): - """Returns chart data - - Args: - emirates (List): List of Emirates - amounts_by_emirate (Dict): Vat and Tax amount by emirates with emirates as key - - Returns: - [Dict]: Chart Data - """ + """Returns chart data.""" labels = [] amount = [] vat_amount = [] @@ -186,6 +168,7 @@ def get_chart(emirates, amounts_by_emirate): return chart def get_total_emiratewise(filters): + """Returns Emiratewise Amount and Taxes.""" return frappe.db.sql(f""" select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` where docstatus = 1 {get_conditions(filters)} @@ -193,11 +176,7 @@ def get_total_emiratewise(filters): """, filters) def get_emirates(): - """Returns a List of emirates in the order that they are to be displayed - - Returns: - List(String): List of emirates in the order that they are to be displayed - """ + """Returns a List of emirates in the order that they are to be displayed.""" return [ 'Abu Dhabi', 'Dubai', @@ -209,14 +188,7 @@ def get_emirates(): ] def get_conditions(filters): - """The conditions to be used to filter data to calculate the total sale - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - String: Concatenated list of conditions to be applied to calculate the total sale - """ + """The conditions to be used to filter data to calculate the total sale.""" conditions = "" for opts in (("company", " and company=%(company)s"), ("from_date", " and posting_date>=%(from_date)s"), @@ -226,22 +198,8 @@ def get_conditions(filters): return conditions def get_reverse_charge_total(filters): - """Returns the sum of the total of each Purchase invoice made - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the total of each Purchase invoice made - """ + """Returns the sum of the total of each Purchase invoice made.""" conditions = get_conditions(filters) - print(""" - select sum(total) from - `tabPurchase Invoice` - where - reverse_charge = "Y" - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions)) return frappe.db.sql(""" select sum(total) from `tabPurchase Invoice` @@ -251,14 +209,7 @@ def get_reverse_charge_total(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_reverse_charge_tax(filters): - """Returns the sum of the tax of each Purchase invoice made - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the tax of each Purchase invoice made - """ + """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" select sum(debit) from @@ -273,14 +224,7 @@ def get_reverse_charge_tax(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_conditions_join(filters): - """The conditions to be used to filter data to calculate the total vat - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - String: Concatenated list of conditions to be applied to calculate the total vat - """ + """The conditions to be used to filter data to calculate the total vat.""" conditions = "" for opts in (("company", " and `tabPurchase Invoice`.company=%(company)s"), ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), @@ -290,14 +234,7 @@ def get_conditions_join(filters): return conditions def get_reverse_charge_recoverable_total(filters): - """Returns the sum of the total of each Purchase invoice made with claimable reverse charge - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the total of each Purchase invoice made with claimable reverse charge - """ + """Returns the sum of the total of each Purchase invoice made with claimable reverse charge.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(total) from @@ -309,14 +246,7 @@ def get_reverse_charge_recoverable_total(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_reverse_charge_recoverable_tax(filters): - """Returns the sum of the tax of each Purchase invoice made - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the tax of each Purchase invoice made - """ + """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" select sum(debit * `tabPurchase Invoice`.claimable_reverse_charge / 100) from @@ -332,14 +262,7 @@ def get_reverse_charge_recoverable_tax(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_standard_rated_expenses_total(filters): - """Returns the sum of the total of each Purchase invoice made with claimable reverse charge - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the total of each Purchase invoice made with claimable reverse charge - """ + """Returns the sum of the total of each Purchase invoice made with claimable reverse charge.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(total) from @@ -350,14 +273,7 @@ def get_standard_rated_expenses_total(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_standard_rated_expenses_tax(filters): - """Returns the sum of the tax of each Purchase invoice made - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the tax of each Purchase invoice made - """ + """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(standard_rated_expenses) from @@ -368,14 +284,7 @@ def get_standard_rated_expenses_tax(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_tourist_tax_return_total(filters): - """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the total of each Sales invoice with non zero tourist_tax_return - """ + """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(total) from @@ -386,14 +295,7 @@ def get_tourist_tax_return_total(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_tourist_tax_return_tax(filters): - """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of the tax of each Sales invoice with non zero tourist_tax_return - """ + """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(tourist_tax_return) from @@ -404,14 +306,7 @@ def get_tourist_tax_return_tax(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_zero_rated_total(filters): - """Returns the sum of each Sales Invoice Item Amount which is zero rated - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of each Sales Invoice Item Amount which is zero rated - """ + """Returns the sum of each Sales Invoice Item Amount which is zero rated.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(i.base_amount) as total from @@ -421,14 +316,7 @@ def get_zero_rated_total(filters): """.format(where_conditions=conditions), filters)[0][0] or 0 def get_exempt_total(filters): - """Returns the sum of each Sales Invoice Item Amount which is Vat Exempt - - Args: - filters (Dict, optional): Dictionary consisting of the filters selected by the user. Defaults to None. - - Returns: - Float: sum of each Sales Invoice Item Amount which is Vat Exempt - """ + """Returns the sum of each Sales Invoice Item Amount which is Vat Exempt.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(i.base_amount) as total from diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 1c947d2fb0..70030c736c 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -131,8 +131,7 @@ def add_print_formats(): name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """) def add_custom_roles_for_reports(): - """Add Access Control to UAE VAT 21 - """ + """Add Access Control to UAE VAT 21.""" if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 21')): frappe.get_doc(dict( doctype='Custom Role', @@ -145,8 +144,7 @@ def add_custom_roles_for_reports(): )).insert() def add_permissions(): - """Add Permissions for UAE VAT Settings and UAE VAT Account - """ + """Add Permissions for UAE VAT Settings and UAE VAT Account.""" for doctype in ('UAE VAT Setting', 'UAE VAT Account'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 4424d2efec..f75efd1aae 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -31,7 +31,7 @@ def update_itemised_tax_data(doc): row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) def get_account_currency(account): - """Helper function to get account currency""" + """Helper function to get account currency.""" if not account: return def generator(): @@ -44,14 +44,7 @@ def get_account_currency(account): return frappe.local_cache("account_currency", account, generator) def get_tax_accounts(company): - """Get the list of tax accounts for a specific company - - Args: - company (String): Current Company set as default - - Returns: - tax_accounts: List of Tax Accounts for the company - """ + """Get the list of tax accounts for a specific company.""" tax_accounts_dict = frappe._dict() tax_accounts_list = frappe.get_all("UAE VAT Account", filters={"parent": company}, @@ -67,11 +60,7 @@ def get_tax_accounts(company): return tax_accounts_dict def update_grand_total_for_rcm(doc, method): - """If the Reverse Charge is Applicable subtract the tax amount from the grand total and update in the form - - Args: - doc (Document): The document for the current Purchase Invoice - """ + """If the Reverse Charge is Applicable subtract the tax amount from the grand total and update in the form.""" country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'United Arab Emirates': @@ -102,14 +91,7 @@ def update_grand_total_for_rcm(doc, method): update_totals(vat_tax, base_vat_tax, doc) def update_totals(vat_tax, base_vat_tax, doc): - """Update the grand total values in the form - - Args: - vat_tax (float): Vat Tax to be subtracted - base_vat_tax (float): Base Vat Tax to be subtracted - doc (Document): The document for the current Purchase Invoice - """ - + """Update the grand total values in the form.""" doc.base_grand_total -= base_vat_tax doc.grand_total -= vat_tax @@ -130,16 +112,7 @@ def update_totals(vat_tax, base_vat_tax, doc): doc.set_payment_schedule() def make_regional_gl_entries(gl_entries, doc): - """This method is hooked to the make_regional_gl_entries in Purchase Invoice. - It appends the region specific general ledger entries to the list of GL Entries. - - Args: - gl_entries (List): List of GL entries to be made - doc (Document): The document for the current Purchase Invoice - - Returns: - List: Updates list of GL Entries - """ + """Hooked to make_regional_gl_entries in Purchase Invoice.It appends the region specific general ledger entries to the list of GL Entries.""" country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'United Arab Emirates': @@ -170,7 +143,7 @@ def make_regional_gl_entries(gl_entries, doc): return gl_entries def validate_returns(doc, method): - print("validate_returns") + """Sum of Tourist Returns and Standard Rated Expenses should be less than Total Tax.""" country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'United Arab Emirates': From 10a3a338b6a98d68e3d68d090801d08e7d240891 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Wed, 30 Sep 2020 21:51:00 +0530 Subject: [PATCH 022/154] refactor: change uae vat settingplural --- .../{uae_vat_setting => uae_vat_settings}/__init__.py | 0 .../test_uae_vat_settings.py} | 2 +- .../uae_vat_settings.js} | 2 +- .../uae_vat_settings.json} | 10 +++++----- .../uae_vat_settings.py} | 2 +- erpnext/regional/united_arab_emirates/setup.py | 2 +- erpnext/regional/united_arab_emirates/utils.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) rename erpnext/regional/doctype/{uae_vat_setting => uae_vat_settings}/__init__.py (100%) rename erpnext/regional/doctype/{uae_vat_setting/test_uae_vat_setting.py => uae_vat_settings/test_uae_vat_settings.py} (80%) rename erpnext/regional/doctype/{uae_vat_setting/uae_vat_setting.js => uae_vat_settings/uae_vat_settings.js} (80%) rename erpnext/regional/doctype/{uae_vat_setting/uae_vat_setting.json => uae_vat_settings/uae_vat_settings.json} (84%) rename erpnext/regional/doctype/{uae_vat_setting/uae_vat_setting.py => uae_vat_settings/uae_vat_settings.py} (88%) diff --git a/erpnext/regional/doctype/uae_vat_setting/__init__.py b/erpnext/regional/doctype/uae_vat_settings/__init__.py similarity index 100% rename from erpnext/regional/doctype/uae_vat_setting/__init__.py rename to erpnext/regional/doctype/uae_vat_settings/__init__.py diff --git a/erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py b/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py similarity index 80% rename from erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py rename to erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py index a5f151dc73..b88439f9b8 100644 --- a/erpnext/regional/doctype/uae_vat_setting/test_uae_vat_setting.py +++ b/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestUAEVATSetting(unittest.TestCase): +class TestUAEVATSettings(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js similarity index 80% rename from erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js rename to erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js index f910e1f662..07a93010b5 100644 --- a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.js +++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js @@ -1,7 +1,7 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('UAE VAT Setting', { +frappe.ui.form.on('UAE VAT Settings', { // refresh: function(frm) { // } diff --git a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json similarity index 84% rename from erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json rename to erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json index e2e2ab8245..ce2c1d4e14 100644 --- a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.json +++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json @@ -7,7 +7,7 @@ "engine": "InnoDB", "field_order": [ "company", - "uae_vat_account" + "uae_vat_accounts" ], "fields": [ { @@ -20,19 +20,19 @@ "unique": 1 }, { - "fieldname": "uae_vat_account", + "fieldname": "uae_vat_accounts", "fieldtype": "Table", - "label": "UAE VAT Account", + "label": "UAE VAT Accounts", "options": "UAE VAT Account", "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-28 12:19:11.493138", + "modified": "2020-09-30 20:08:18.764798", "modified_by": "Administrator", "module": "Regional", - "name": "UAE VAT Setting", + "name": "UAE VAT Settings", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py similarity index 88% rename from erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py rename to erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py index 9549de9466..20dc604510 100644 --- a/erpnext/regional/doctype/uae_vat_setting/uae_vat_setting.py +++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class UAEVATSetting(Document): +class UAEVATSettings(Document): pass diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 70030c736c..0de42120c4 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -145,7 +145,7 @@ def add_custom_roles_for_reports(): def add_permissions(): """Add Permissions for UAE VAT Settings and UAE VAT Account.""" - for doctype in ('UAE VAT Setting', 'UAE VAT Account'): + for doctype in ('UAE VAT Settings', 'UAE VAT Account'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index f75efd1aae..5a8c3c47fb 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -52,7 +52,7 @@ def get_tax_accounts(company): ) if not tax_accounts_list and not frappe.flags.in_test: - frappe.throw(_(f'Please set Vat Accounts for Company: "{company}" in UAE VAT Setting')) + frappe.throw(_(f'Please set Vat Accounts for Company: "{company}" in UAE VAT Settings')) for d in tax_accounts_list: for key, name in d.items(): tax_accounts_dict[name] = name From 8cc6f2b38881f1bcd53cf558a362486e564648f1 Mon Sep 17 00:00:00 2001 From: hasnain2808 Date: Thu, 1 Oct 2020 12:44:39 +0530 Subject: [PATCH 023/154] feat(UAE VAT 21): add fixed width to columns --- .../report/uae_vat_21/uae_vat_21.html | 31 ++++++---- .../regional/report/uae_vat_21/uae_vat_21.js | 1 - .../regional/report/uae_vat_21/uae_vat_21.py | 57 +++++++++++-------- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.html b/erpnext/regional/report/uae_vat_21/uae_vat_21.html index aa6a255932..d9b9968d90 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.html +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.html @@ -1,21 +1,27 @@ {% var report_columns = report.get_columns_for_print(); report_columns = report_columns.filter(col => !col.hidden); - - if (report_columns.length > 8) { - frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); - } %} +

{%= __(report.report_name) %}

-

{%= __("VAT on Sales and All Other Outputs") %}

+

{%= __("VAT on Sales and All Other Outputs") %}

- {% for (let i=0; i{%= report_columns[i].label %} + + + + {% for (let i=2; i{%= report_columns[i].label %} {% } %} @@ -38,17 +44,20 @@
{%= report_columns[0].label %}{%= report_columns[1].label %}
-

{%= __("VAT on Expenses and All Other Inputs") %}

+

{%= __("VAT on Expenses and All Other Inputs") %}

- {% for (let i=0; i{%= report_columns[i].label %} + + + + {% for (let i=2; i{%= report_columns[i].label %} {% } %} - {% for (let j=13; j Date: Thu, 1 Oct 2020 12:59:39 +0530 Subject: [PATCH 024/154] feat(UAE VAT 21): Add currency formatting --- .../regional/report/uae_vat_21/uae_vat_21.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.py b/erpnext/regional/report/uae_vat_21/uae_vat_21.py index 211bf16734..2bded71ec2 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.py +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.py @@ -60,8 +60,10 @@ def get_data(filters = None): emirate, amount, vat= d amounts_by_emirate[emirate] = { "legend": emirate, - "amount": amount, - "vat_amount": vat + "raw_amount": amount, + "raw_vat_amount": vat, + "amount": frappe.format(amount, 'Currency'), + "vat_amount": frappe.format(vat, 'Currency'), } for d, emirate in enumerate(emirates, 97): @@ -74,8 +76,8 @@ def get_data(filters = None): { "no": _(f'1{chr(d)}'), "legend": _(f'Standard rated supplies in {emirate}'), - "amount": 0, - "vat_amount": 0 + "amount": frappe.format(0, 'Currency'), + "vat_amount": frappe.format(0, 'Currency') } ) @@ -83,8 +85,8 @@ def get_data(filters = None): { "no": _('2'), "legend": _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'), - "amount": (-1) * get_tourist_tax_return_total(filters), - "vat_amount": (-1) * get_tourist_tax_return_tax(filters) + "amount": frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'), + "vat_amount": frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency') } ) @@ -92,8 +94,8 @@ def get_data(filters = None): { "no": _('3'), "legend": _('Supplies subject to the reverse charge provision'), - "amount": get_reverse_charge_total(filters), - "vat_amount": get_reverse_charge_tax(filters) + "amount": frappe.format(get_reverse_charge_total(filters), 'Currency'), + "vat_amount": frappe.format(get_reverse_charge_tax(filters), 'Currency') } ) @@ -101,7 +103,7 @@ def get_data(filters = None): { "no": _('4'), "legend": _('Zero Rated'), - "amount": get_zero_rated_total(filters), + "amount": frappe.format(get_zero_rated_total(filters), 'Currency'), "vat_amount": "-" } ) @@ -110,7 +112,7 @@ def get_data(filters = None): { "no": _('5'), "legend": _('Exempt Supplies'), - "amount": get_exempt_total(filters), + "amount": frappe.format(get_exempt_total(filters), 'Currency'), "vat_amount": "-" } ) @@ -133,8 +135,8 @@ def get_data(filters = None): { "no": '9', "legend": _('Standard Rated Expenses'), - "amount": get_standard_rated_expenses_total(filters), - "vat_amount": get_standard_rated_expenses_tax(filters) + "amount": frappe.format(get_standard_rated_expenses_total(filters), 'Currency'), + "vat_amount": frappe.format(get_standard_rated_expenses_tax(filters), 'Currency') } ) @@ -142,8 +144,8 @@ def get_data(filters = None): { "no": _('10'), "legend": _('Supplies subject to the reverse charge provision'), - "amount": get_reverse_charge_recoverable_total(filters), - "vat_amount": get_reverse_charge_recoverable_tax(filters) + "amount": frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'), + "vat_amount": frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency') } ) @@ -157,8 +159,8 @@ def get_chart(emirates, amounts_by_emirate): vat_amount = [] for d in emirates: if d in amounts_by_emirate: - amount.append(amounts_by_emirate[d]["amount"]) - vat_amount.append(amounts_by_emirate[d]["vat_amount"]) + amount.append(amounts_by_emirate[d]["raw_amount"]) + vat_amount.append(amounts_by_emirate[d]["raw_vat_amount"]) labels.append(d) datasets = [] From 51c6cf692ef4cc550fbc034142b25c819e7cc341 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Oct 2020 23:20:36 +0530 Subject: [PATCH 025/154] fix: Add method for loan closure --- erpnext/loan_management/doctype/loan/loan.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index d1b7589a17..1fb0805d38 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -9,6 +9,7 @@ from frappe import _ from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.controllers.accounts_controller import AccountsController +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts class Loan(AccountsController): def validate(self): @@ -182,6 +183,19 @@ def get_monthly_repayment_amount(repayment_method, loan_amount, rate_of_interest monthly_repayment_amount = math.ceil(flt(loan_amount) / repayment_periods) return monthly_repayment_amount +@frappe.whitelist() +def request_loan_closure(loan): + amounts = calculate_amounts(loan, getdate()) + + pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest'] + + # checking greater than 0 as there may be some minor precision error + if pending_amount > 0: + frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount)) + else: + # update status as loan closure requested + frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested') + @frappe.whitelist() def get_loan_application(loan_application): loan = frappe.get_doc("Loan Application", loan_application) From 6d27adccfce718c37b0a019fdf164c685c8271eb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Oct 2020 23:28:39 +0530 Subject: [PATCH 026/154] fix: Button to close loan --- erpnext/loan_management/doctype/loan/loan.js | 40 ++++++++++++++++---- erpnext/loan_management/loan_common.js | 2 +- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 9b4c21770e..682b574fea 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -21,6 +21,14 @@ frappe.ui.form.on('Loan', { }; }); + frm.set_query("loan_type", function () { + return { + "filters": { + "docstatus": 1 + } + }; + }); + $.each(["penalty_income_account", "interest_income_account"], function(i, field) { frm.set_query(field, function () { return { @@ -49,19 +57,21 @@ frappe.ui.form.on('Loan', { refresh: function (frm) { if (frm.doc.docstatus == 1) { + if (["Disbursed", "Partially Disbursed"].includes(frm.doc.status) && (!frm.doc.repay_from_salary)) { + frm.add_custom_button(__('Request Loan Closure'), function() { + frm.trigger("request_loan_closure"); + },__('Status')); + frm.add_custom_button(__('Loan Repayment'), function() { + frm.trigger("make_repayment_entry"); + },__('Create')); + } + if (frm.doc.status == "Sanctioned" || frm.doc.status == 'Partially Disbursed') { frm.add_custom_button(__('Loan Disbursement'), function() { frm.trigger("make_loan_disbursement"); },__('Create')); } - if (["Disbursed", "Partially Disbursed"].includes(frm.doc.status) && (!frm.doc.repay_from_salary)) { - frm.add_custom_button(__('Loan Repayment'), function() { - frm.trigger("make_repayment_entry"); - },__('Create')); - - } - if (frm.doc.status == "Loan Closure Requested") { frm.add_custom_button(__('Loan Security Unpledge'), function() { frm.trigger("create_loan_security_unpledge"); @@ -117,6 +127,22 @@ frappe.ui.form.on('Loan', { }) }, + request_loan_closure: function(frm) { + frappe.confirm(__("Do you really want to close this loan"), + function() { + frappe.call({ + args: { + 'loan': frm.doc.name + }, + method: "erpnext.loan_management.doctype.loan.loan.request_loan_closure", + callback: function() { + frm.reload_doc(); + } + }); + } + ); + }, + create_loan_security_unpledge: function(frm) { frappe.call({ method: "erpnext.loan_management.doctype.loan.loan.unpledge_security", diff --git a/erpnext/loan_management/loan_common.js b/erpnext/loan_management/loan_common.js index d9dd415296..33a5de0566 100644 --- a/erpnext/loan_management/loan_common.js +++ b/erpnext/loan_management/loan_common.js @@ -15,7 +15,7 @@ frappe.ui.form.on(cur_frm.doctype, { frappe.route_options = { voucher_no: frm.doc.name, company: frm.doc.company, - from_date: frm.doc.posting_date, + from_date: moment(frm.doc.posting_date).format('YYYY-MM-DD'), to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), show_cancelled_entries: frm.doc.docstatus === 2 }; From 6dccf79250ff1a380b712a7ff3625a648d7f71f3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Oct 2020 23:32:57 +0530 Subject: [PATCH 027/154] fix: Add accrual type and penalty field in interest accrual --- .../loan_disbursement/loan_disbursement.py | 2 +- .../loan_disbursement/test_loan_disbursement.py | 3 +-- .../loan_interest_accrual.json | 17 ++++++++++++++++- .../process_loan_interest_accrual.json | 10 +++++++++- .../process_loan_interest_accrual.py | 5 +++-- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index 260fada893..bfdc8b403e 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -74,7 +74,7 @@ class LoanDisbursement(AccountsController): if loan_details.status == "Disbursed" and not loan_details.is_term_loan: process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1), - loan=self.against_loan) + loan=self.against_loan, accrual_type="Disbursement") if disbursed_amount > loan_details.loan_amount: topup_amount = disbursed_amount - loan_details.loan_amount diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index 2cb2637612..3ade5c549b 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -60,8 +60,7 @@ class TestLoanDisbursement(unittest.TestCase): self.assertRaises(frappe.ValidationError, make_loan_disbursement_entry, loan.name, 500000, first_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant, add_days(get_last_day(nowdate()), 5), - "Regular Payment", 611095.89) + repayment_entry = create_repayment_entry(loan.name, self.applicant, add_days(get_last_day(nowdate()), 5), 611095.89) repayment_entry.submit() loan.reload() diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 5fc3e8f4b6..a0abc5b57e 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -14,6 +14,7 @@ "column_break_4", "company", "posting_date", + "accrual_type", "is_term_loan", "section_break_7", "pending_principal_amount", @@ -22,6 +23,7 @@ "column_break_14", "interest_amount", "paid_interest_amount", + "penalty_amount", "section_break_15", "process_loan_interest_accrual", "repayment_schedule_name", @@ -149,12 +151,25 @@ "fieldtype": "Currency", "label": "Paid Interest Amount", "options": "Company:company:default_currency" + }, + { + "fieldname": "accrual_type", + "fieldtype": "Select", + "label": "Accrual Type", + "options": "Regular\nRepayment\nDisbursement" + }, + { + "fieldname": "penalty_amount", + "fieldtype": "Currency", + "label": "Penalty Amount", + "options": "Company:company:default_currency" } ], "in_create": 1, + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-04-16 11:24:23.258404", + "modified": "2020-10-10 03:12:58.204501", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index 0ef098f278..1e05a42de7 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -10,6 +10,7 @@ "loan_type", "loan", "process_type", + "accrual_type", "amended_from" ], "fields": [ @@ -47,11 +48,18 @@ "hidden": 1, "label": "Process Type", "read_only": 1 + }, + { + "fieldname": "accrual_type", + "fieldtype": "Select", + "label": "Accrual Type", + "options": "Regular\nRepayment\nAccrual" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-04-09 22:52:53.911416", + "modified": "2020-10-08 12:20:11.124769", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 0fa96860d0..1eeb18b7ea 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -20,19 +20,20 @@ class ProcessLoanInterestAccrual(Document): if (not self.loan or not loan_doc.is_term_loan) and self.process_type != 'Term Loans': make_accrual_interest_entry_for_demand_loans(self.posting_date, self.name, - open_loans = open_loans, loan_type = self.loan_type) + open_loans = open_loans, loan_type = self.loan_type, accrual_type=self.accrual_type) if (not self.loan or loan_doc.is_term_loan) and self.process_type != 'Demand Loans': make_accrual_interest_entry_for_term_loans(self.posting_date, self.name, term_loan=self.loan, loan_type=self.loan_type) -def process_loan_interest_accrual_for_demand_loans(posting_date=None, loan_type=None, loan=None): +def process_loan_interest_accrual_for_demand_loans(posting_date=None, loan_type=None, loan=None, accrual_type="Regular"): loan_process = frappe.new_doc('Process Loan Interest Accrual') loan_process.posting_date = posting_date or nowdate() loan_process.loan_type = loan_type loan_process.process_type = 'Demand Loans' loan_process.loan = loan + loan_process.accrual_type = accrual_type loan_process.submit() From d86b7c55373c0a6e72d18ab0f9106649489575d5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 11 Oct 2020 00:37:34 +0530 Subject: [PATCH 028/154] fix: Remove repayment type --- .../loan_management/doctype/loan/test_loan.py | 136 ++++++++++++++---- .../loan_interest_accrual.py | 29 +++- .../loan_repayment/loan_repayment.json | 31 ++-- .../doctype/loan_repayment/loan_repayment.py | 116 +++++++++------ 4 files changed, 220 insertions(+), 92 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 5a4a19a0fb..7b653652ea 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -14,7 +14,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_ process_loan_interest_accrual_for_term_loans) from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall -from erpnext.loan_management.doctype.loan.loan import unpledge_security +from erpnext.loan_management.doctype.loan.loan import unpledge_security, request_loan_closure from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount @@ -132,7 +132,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -149,23 +149,23 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), 111118.68) repayment_entry.save() repayment_entry.submit() penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2)) - amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', - 'paid_principal_amount']) + amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount']) loan.load_from_db() - self.assertEquals(amounts[0], repayment_entry.interest_payable) + total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount'] + self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid - - penalty_amount - amounts[0], 2)) + penalty_amount - total_interest_paid, 2)) - def test_loan_closure_repayment(self): + def test_loan_closure(self): pledge = [{ "loan_security": "Test Security 1", "qty": 4000.00 @@ -174,7 +174,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -196,14 +196,16 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), - "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry.submit() amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 2),flt(accrued_interest_amount, 2)) + self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + request_loan_closure(loan.name) loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -230,8 +232,7 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(nowdate(), 5), - "Regular Payment", 89768.75) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(nowdate(), 5), 89768.75) repayment_entry.submit() @@ -281,7 +282,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -299,10 +300,10 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), - "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() + request_loan_closure(loan.name) loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -317,9 +318,9 @@ class TestLoan(unittest.TestCase): self.assertEqual(loan.status, 'Closed') self.assertEquals(sum(pledged_qty.values()), 0) - amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") - self.assertEqual(amounts['pending_principal_amount'], 0) - self.assertEqual(amounts['payable_principal_amount'], 0) + amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertTrue(amounts['pending_principal_amount'] < 0) + self.assertTrue(amounts['payable_principal_amount'] < 0) self.assertEqual(amounts['interest_amount'], 0) def test_disbursal_check_with_shortfall(self): @@ -381,7 +382,7 @@ class TestLoan(unittest.TestCase): loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) create_pledge(loan_application) - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate())) + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() self.assertEquals(loan.loan_amount, 1000000) @@ -399,20 +400,102 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), - "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', 'paid_principal_amount']) + request_loan_closure(loan.name) loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") - amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment") - self.assertEquals(amounts['pending_principal_amount'], 0.0) + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertTrue(amounts['pending_principal_amount'] < 0.0) + + def test_partial_unaccrued_interest_payment(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + no_of_days = date_diff(last_date, first_date) + 1 + + no_of_days += 5.5 + + # get partial unaccrued interest amount + paid_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + paid_amount) + + repayment_entry.submit() + repayment_entry.load_from_db() + + partial_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 5) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + interest_amount = flt(amounts['interest_amount'] + partial_accrued_interest_amount, 2) + self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0)) + + def test_penalty(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + amounts = calculate_amounts(loan.name, add_days(last_date, 1)) + paid_amount = amounts['interest_amount']/2 + + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + paid_amount) + + repayment_entry.submit() + + # 30 days - grace period + penalty_days = 30 - 5 + penalty_applicable_amount = flt(amounts['interest_amount']/2, 2) + penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days)/365, 2) + process = process_loan_interest_accrual_for_demand_loans(posting_date = '2019-11-30') + + calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', + {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount') + + self.assertEquals(calculated_penalty_amount, penalty_amount) + def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): @@ -582,12 +665,11 @@ def create_loan_security_price(loan_security, loan_security_price, uom, from_dat "valid_upto": to_date }).insert(ignore_permissions=True) -def create_repayment_entry(loan, applicant, posting_date, payment_type, paid_amount): +def create_repayment_entry(loan, applicant, posting_date, paid_amount): lr = frappe.get_doc({ "doctype": "Loan Repayment", "against_loan": loan, - "payment_type": payment_type, "company": "_Test Company", "posting_date": posting_date or nowdate(), "applicant": applicant, diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 2d959bf3be..4517de0c59 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -22,7 +22,6 @@ class LoanInterestAccrual(AccountsController): if not self.interest_amount and not self.payable_principal_amount: frappe.throw(_("Interest Amount or Principal Amount is mandatory")) - def on_submit(self): self.make_gl_entries() @@ -79,8 +78,11 @@ class LoanInterestAccrual(AccountsController): # For Eg: If Loan disbursement date is '01-09-2019' and disbursed amount is 1000000 and # rate of interest is 13.5 then first loan interest accural will be on '01-10-2019' # which means interest will be accrued for 30 days which should be equal to 11095.89 -def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest): +def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type): + from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts + no_of_days = get_no_of_days_for_interest_accural(loan, posting_date) + precision = cint(frappe.db.get_default("currency_precision")) or 2 if no_of_days <= 0: return @@ -91,7 +93,7 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i else: pending_principal_amount = loan.disbursed_amount - interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100) + interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date) payable_interest = interest_per_day * no_of_days args = frappe._dict({ @@ -102,13 +104,16 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i 'loan_account': loan.loan_account, 'pending_principal_amount': pending_principal_amount, 'interest_amount': payable_interest, + 'penalty_amount': calculate_amounts(loan.name, posting_date)['penalty_amount'], 'process_loan_interest': process_loan_interest, - 'posting_date': posting_date + 'posting_date': posting_date, + 'accrual_type': accrual_type }) - make_loan_interest_accrual_entry(args) + if flt(payable_interest, precision) > 0.0: + make_loan_interest_accrual_entry(args) -def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None): +def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"): query_filters = { "status": ('in', ['Disbursed', 'Partially Disbursed']), "docstatus": 1 @@ -127,7 +132,7 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte filters=query_filters) for loan in open_loans: - calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest) + calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type) def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_interest, term_loan=None, loan_type=None): curr_date = posting_date or add_days(nowdate(), 1) @@ -192,10 +197,12 @@ def make_loan_interest_accrual_entry(args): loan_interest_accrual.loan_account = args.loan_account loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision) loan_interest_accrual.interest_amount = flt(args.interest_amount, precision) + loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision) loan_interest_accrual.posting_date = args.posting_date or nowdate() loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name loan_interest_accrual.payable_principal_amount = args.payable_principal + loan_interest_accrual.accrual_type = args.accrual_type loan_interest_accrual.save() loan_interest_accrual.submit() @@ -226,3 +233,11 @@ def days_in_year(year): return days +def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None): + if not posting_date: + posting_date = getdate() + + precision = cint(frappe.db.get_default("currency_precision")) or 2 + + return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100), 2) + diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 5942455919..60b20369dc 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -10,11 +10,11 @@ "applicant_type", "applicant", "loan_type", - "payment_type", "column_break_3", "company", "posting_date", "is_term_loan", + "rate_of_interest", "payment_details_section", "due_date", "pending_principal_amount", @@ -31,6 +31,7 @@ "column_break_21", "reference_date", "principal_amount_paid", + "total_interest_paid", "repayment_details", "amended_from" ], @@ -95,15 +96,6 @@ "fieldname": "column_break_9", "fieldtype": "Column Break" }, - { - "default": "Regular Payment", - "fieldname": "payment_type", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Payment Type", - "options": "\nRegular Payment\nLoan Closure", - "reqd": 1 - }, { "fieldname": "payable_amount", "fieldtype": "Currency", @@ -195,6 +187,7 @@ "fieldtype": "Currency", "hidden": 1, "label": "Principal Amount Paid", + "options": "Company:company:default_currency", "read_only": 1 }, { @@ -217,11 +210,27 @@ "hidden": 1, "label": "Repayment Details", "options": "Loan Repayment Detail" + }, + { + "fieldname": "total_interest_paid", + "fieldtype": "Currency", + "hidden": 1, + "label": "Total Interest Paid", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fetch_from": "loan_type.rate_of_interest", + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "label": "Rate Of Interest", + "read_only": 1 } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-05-16 09:40:15.581165", + "modified": "2020-10-10 03:49:01.827593", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 97dbc44bf1..c8344d4667 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -14,19 +14,18 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest class LoanRepayment(AccountsController): def validate(self): - amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type) + amounts = calculate_amounts(self.against_loan, self.posting_date) self.set_missing_values(amounts) self.validate_amount() - self.allocate_amounts(amounts['pending_accrual_entries']) - - def before_submit(self): - self.book_unaccrued_interest() + self.allocate_amounts(amounts) def on_submit(self): + self.book_unaccrued_interest() self.update_paid_amount() self.make_gl_entries() @@ -72,29 +71,35 @@ class LoanRepayment(AccountsController): msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount) frappe.throw(msg) - if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision): - msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount) - frappe.throw(msg) - def book_unaccrued_interest(self): - if self.payment_type == 'Loan Closure': - total_interest_paid = 0 - for payment in self.repayment_details: - total_interest_paid += payment.paid_interest_amount + precision = cint(frappe.db.get_default("currency_precision")) or 2 + if self.total_interest_paid > self.interest_payable: + if not self.is_term_loan: + # get last loan interest accrual date + last_accrual_date = frappe.get_value('Loan Interest Accrual', {'loan': self.against_loan}, 'MAX(posting_date)') - if total_interest_paid < self.interest_payable: - if not self.is_term_loan: - process = process_loan_interest_accrual_for_demand_loans(posting_date=self.posting_date, - loan=self.against_loan) + # get posting date upto which interest has to be accrued + per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date), 2) - lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': - process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1) + no_of_days = flt(flt(self.total_interest_paid - self.interest_payable, + precision)/per_day_interest, 0) - self.append('repayment_details', { - 'loan_interest_accrual': lia.name, - 'paid_interest_amount': lia.interest_amount, - 'paid_principal_amount': lia.payable_principal_amount - }) + posting_date = add_days(last_accrual_date, no_of_days) + + # book excess interest paid + process = process_loan_interest_accrual_for_demand_loans(posting_date=posting_date, + loan=self.against_loan, accrual_type="Repayment") + + # get loan interest accrual to update paid amount + lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': + process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1) + + self.append('repayment_details', { + 'loan_interest_accrual': lia.name, + 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), + 'paid_principal_amount': 0.0 + }) def update_paid_amount(self): precision = cint(frappe.db.get_default("currency_precision")) or 2 @@ -108,12 +113,6 @@ class LoanRepayment(AccountsController): WHERE name = %s""", (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual)) - if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision): - if loan.is_secured_loan: - frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") - else: - frappe.db.set_value("Loan", self.against_loan, "status", "Closed") - frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid + self.amount_paid, loan.total_principal_paid + self.principal_amount_paid, self.against_loan)) @@ -137,15 +136,17 @@ class LoanRepayment(AccountsController): if loan.status == "Loan Closure Requested": frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed") - def allocate_amounts(self, paid_entries): + def allocate_amounts(self, repayment_details): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + self.set('repayment_details', []) self.principal_amount_paid = 0 total_interest_paid = 0 interest_paid = self.amount_paid - self.penalty_amount - if self.amount_paid - self.penalty_amount > 0 and paid_entries: + if self.amount_paid - self.penalty_amount > 0: interest_paid = self.amount_paid - self.penalty_amount - for lia, amounts in iteritems(paid_entries): + for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])): if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid: interest_amount = amounts['interest_amount'] paid_principal = amounts['payable_principal_amount'] @@ -169,9 +170,24 @@ class LoanRepayment(AccountsController): 'paid_principal_amount': paid_principal }) - if self.payment_type == 'Loan Closure' and total_interest_paid < self.interest_payable: - unaccrued_interest = self.interest_payable - total_interest_paid - interest_paid -= unaccrued_interest + if repayment_details['unaccrued_interest'] and interest_paid: + # no of days for which to accrue interest + # Interest can only be accrued for an entire day and not partial + if interest_paid > repayment_details['unaccrued_interest']: + per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date), precision) + interest_paid -= repayment_details['unaccrued_interest'] + total_interest_paid += repayment_details['unaccrued_interest'] + else: + # get no of days for which interest can be paid + per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date), precision) + + no_of_days = cint(interest_paid/per_day_interest) + total_interest_paid += no_of_days * per_day_interest + interest_paid -= no_of_days * per_day_interest + + self.total_interest_paid = total_interest_paid if interest_paid: self.principal_amount_paid += interest_paid @@ -289,7 +305,7 @@ def get_accrued_interest_entries(against_loan): # This function returns the amounts that are payable at the time of loan repayment based on posting date # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable -def get_amounts(amounts, against_loan, posting_date, payment_type): +def get_amounts(amounts, against_loan, posting_date): precision = cint(frappe.db.get_default("currency_precision")) or 2 against_loan_doc = frappe.get_doc("Loan", against_loan) @@ -332,15 +348,16 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): else: pending_principal_amount = against_loan_doc.disbursed_amount - if payment_type == "Loan Closure": - if due_date: - pending_days = date_diff(posting_date, due_date) + 1 - else: - pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 + unaccrued_interest = 0 + if due_date: + pending_days = date_diff(posting_date, due_date) + 1 + else: + pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 - payable_principal_amount = pending_principal_amount - per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365 - total_pending_interest += (pending_days * per_day_interest) + if pending_days > 0: + payable_principal_amount = flt(pending_principal_amount, precision) + per_day_interest = get_per_day_interest(payable_principal_amount, loan_type_details.rate_of_interest, posting_date) + unaccrued_interest += (pending_days * flt(per_day_interest, precision)) amounts["pending_principal_amount"] = flt(pending_principal_amount, precision) amounts["payable_principal_amount"] = flt(payable_principal_amount, precision) @@ -348,6 +365,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): amounts["penalty_amount"] = flt(penalty_amount, precision) amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision) amounts["pending_accrual_entries"] = pending_accrual_entries + amounts["unaccrued_interest"] = unaccrued_interest if final_due_date: amounts["due_date"] = final_due_date @@ -355,7 +373,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): return amounts @frappe.whitelist() -def calculate_amounts(against_loan, posting_date, payment_type): +def calculate_amounts(against_loan, posting_date, payment_type=''): amounts = { 'penalty_amount': 0.0, @@ -363,10 +381,14 @@ def calculate_amounts(against_loan, posting_date, payment_type): 'pending_principal_amount': 0.0, 'payable_principal_amount': 0.0, 'payable_amount': 0.0, + 'unaccrued_interest': 0.0, 'due_date': '' } - amounts = get_amounts(amounts, against_loan, posting_date, payment_type) + amounts = get_amounts(amounts, against_loan, posting_date) + + if payment_type == 'Loan Closure': + amounts['payable_amount'] += amounts['unaccrued_interest'] return amounts From ac451b3a5435b2927f1a60f3e0901a45c3fceb07 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Sun, 11 Oct 2020 01:40:22 +0530 Subject: [PATCH 029/154] feat(UAE VAT 21): Move standard rated expense from Sales invoice to Purchase invoice --- erpnext/hooks.py | 4 +--- erpnext/regional/report/uae_vat_21/uae_vat_21.py | 4 ++-- erpnext/regional/united_arab_emirates/setup.py | 7 ++++--- erpnext/regional/united_arab_emirates/utils.py | 10 ++++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a0242c175c..8d08c90047 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -242,15 +242,13 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], - "validate": [ - "erpnext.regional.united_arab_emirates.utils.validate_returns", - ], "on_trash": "erpnext.regional.check_deletion_permission" }, "Purchase Invoice": { "validate": [ "erpnext.regional.india.utils.update_grand_total_for_rcm", "erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm", + "erpnext.regional.united_arab_emirates.utils.validate_returns" ] }, "Payment Entry": { diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.py b/erpnext/regional/report/uae_vat_21/uae_vat_21.py index 2bded71ec2..227c2fb5a1 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.py +++ b/erpnext/regional/report/uae_vat_21/uae_vat_21.py @@ -277,7 +277,7 @@ def get_standard_rated_expenses_total(filters): conditions = get_conditions(filters) return frappe.db.sql(""" select sum(total) from - `tabSales Invoice` + `tabPurchase Invoice` where standard_rated_expenses > 0 and docstatus = 1 {where_conditions} ; @@ -288,7 +288,7 @@ def get_standard_rated_expenses_tax(filters): conditions = get_conditions(filters) return frappe.db.sql(""" select sum(standard_rated_expenses) from - `tabSales Invoice` + `tabPurchase Invoice` where standard_rated_expenses > 0 and docstatus = 1 {where_conditions} ; diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 0de42120c4..e48b9a9ce1 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -38,8 +38,11 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Read Only', insert_after='supplier_name', fetch_from='supplier.supplier_name_in_arabic', print_hide=1), + dict(fieldname='standard_rated_expenses', label='Standard Rated Expenses (AED)', + insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0', + depends_on="eval:doc.reverse_charge=='N'",), dict(fieldname='reverse_charge', label='Reverse Charge Applicable', - fieldtype='Select', insert_after='permit_no', print_hide=1, + fieldtype='Select', insert_after='standard_rated_expenses', print_hide=1, options='Y\nN', default='N'), dict(fieldname='claimable_reverse_charge', label='Claimable Reverse Charge (Percentage)', insert_after='reverse_charge', fieldtype='Percent', print_hide=1, @@ -57,8 +60,6 @@ def make_custom_fields(): fieldtype='Read Only', fetch_from='customer_address.emirates'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), - dict(fieldname='standard_rated_expenses', label='Standard Rated Expenses (AED)', - insert_after='tourist_tax_return', fieldtype='Currency', print_hide=1, default='0'), ] invoice_item_fields = [ diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 5a8c3c47fb..dbe283f226 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -143,11 +143,13 @@ def make_regional_gl_entries(gl_entries, doc): return gl_entries def validate_returns(doc, method): - """Sum of Tourist Returns and Standard Rated Expenses should be less than Total Tax.""" + """Standard Rated expenses should not be set when Reverse Charge Applicable is set.""" country = frappe.get_cached_value('Company', doc.company, 'country') - + print("-"*50) + print(doc.reverse_charge) + print(flt(doc.standard_rated_expenses)) if country != 'United Arab Emirates': return - if flt(doc.tourist_tax_return) + flt(doc.standard_rated_expenses) > flt(doc.total_taxes_and_charges): - frappe.throw(_("The Total Returns(Tax Refund provided to Tourists (AED) + Standard Rated Expenses (AED)) should be less than the Total Taxes and Charges (Company Currency)")) \ No newline at end of file + if doc.reverse_charge == 'Y' and flt(doc.standard_rated_expenses) != 0: + frappe.throw(_("Standard Rated expenses should not be set when Reverse Charge Applicable is Y")) \ No newline at end of file From a034311b1b397b26217c47b16e20ae8581ac1b0d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 11 Oct 2020 20:52:02 +0530 Subject: [PATCH 030/154] fix: Acrual type --- .../doctype/loan_interest_accrual/loan_interest_accrual.json | 2 +- .../process_loan_interest_accrual.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index a0abc5b57e..893609e0c7 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -169,7 +169,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-10 03:12:58.204501", + "modified": "2020-10-11 11:17:44.704694", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index 1e05a42de7..bb781b1d56 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -53,13 +53,13 @@ "fieldname": "accrual_type", "fieldtype": "Select", "label": "Accrual Type", - "options": "Regular\nRepayment\nAccrual" + "options": "Regular\nRepayment\nDisbursement" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-08 12:20:11.124769", + "modified": "2020-10-11 11:19:00.531046", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", From a322f0d0b3163192b372a1b407468f12c9474334 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Mon, 12 Oct 2020 11:38:55 +0800 Subject: [PATCH 031/154] chore: Fix error message grammar --- erpnext/stock/doctype/item_price/item_price.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 8e39eb5037..56efa09486 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -23,7 +23,7 @@ class ItemPrice(Document): def validate_item(self): if not frappe.db.exists("Item", self.item_code): - frappe.throw(_("Item {0} not found").format(self.item_code)) + frappe.throw(_("Item {0} not found.").format(self.item_code)) def validate_dates(self): if self.valid_from and self.valid_upto: @@ -38,8 +38,7 @@ class ItemPrice(Document): if not price_list_details: link = frappe.utils.get_link_to_form('Price List', self.price_list) - frappe.throw("The price list {0} does not exists or disabled". - format(link)) + frappe.throw("The price list {0} does not exist or is disabled".format(link)) self.buying, self.selling, self.currency = price_list_details @@ -69,7 +68,7 @@ class ItemPrice(Document): self.reference = self.customer if self.buying: self.reference = self.supplier - + if self.selling and not self.buying: # if only selling then remove supplier self.supplier = None From 39bed3a65e2557d15d2275178120de941e66c536 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Mon, 12 Oct 2020 11:42:05 +0800 Subject: [PATCH 032/154] fix: Add null or empty checking to validation This commit adds null or empty checking in the **Item Price** DocType's `check_duplicates()`. This was done to fix a bug where some prices are shown to be duplciates even though they aren't because they don't have values in some fields. --- .../stock/doctype/item_price/item_price.py | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 56efa09486..f88b05cd20 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -48,20 +48,52 @@ class ItemPrice(Document): self.item_code,["item_name", "description"]) def check_duplicates(self): - conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s" + conditions = """ + where + item_code = %(item_code)s + and price_list = %(price_list)s + and name != %(name)s + """ - for field in ['uom', 'valid_from', - 'valid_upto', 'packing_unit', 'customer', 'supplier']: + for field in [ + "uom", + "valid_from", + "valid_upto", + "packing_unit", + "customer", + "supplier", + ]: if self.get(field): - conditions += " and {0} = %({1})s".format(field, field) + conditions += " and {0} = %({0})s ".format(field) + else: + conditions += """ + and ( + isnull({0}) + or {0} = '' + ) + """.format( + field + ) - price_list_rate = frappe.db.sql(""" - SELECT price_list_rate - FROM `tabItem Price` - {conditions} """.format(conditions=conditions), self.as_dict()) + price_list_rate = frappe.db.sql( + """ + select + price_list_rate + from + `tabItem Price` + {conditions} + """.format( + conditions=conditions + ), + self.as_dict(), + ) - if price_list_rate : - frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates."), ItemPriceDuplicateItem) + if price_list_rate: + frappe.throw(_(""" + Item Price appears multiple times based on + Price List, Supplier/Customer, Currency, Item, UOM, Qty, + and Dates. + """), ItemPriceDuplicateItem,) def before_save(self): if self.selling: From 58389ebd402d461903fccb522127bdc1adbbb40e Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Mon, 12 Oct 2020 15:20:11 +0800 Subject: [PATCH 033/154] style: Collapse whitespace --- .../stock/doctype/item_price/item_price.py | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index f88b05cd20..f05de22d01 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -66,21 +66,12 @@ class ItemPrice(Document): if self.get(field): conditions += " and {0} = %({0})s ".format(field) else: - conditions += """ - and ( - isnull({0}) - or {0} = '' - ) - """.format( - field - ) + conditions += "and (isnull({0}) or {0} = '')".format(field) price_list_rate = frappe.db.sql( """ - select - price_list_rate - from - `tabItem Price` + select price_list_rate + from `tabItem Price` {conditions} """.format( conditions=conditions @@ -89,11 +80,7 @@ class ItemPrice(Document): ) if price_list_rate: - frappe.throw(_(""" - Item Price appears multiple times based on - Price List, Supplier/Customer, Currency, Item, UOM, Qty, - and Dates. - """), ItemPriceDuplicateItem,) + frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,) def before_save(self): if self.selling: From f6bcd5c2b230375f4014e8270a41027da9b9c6b7 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Mon, 12 Oct 2020 16:57:33 +0800 Subject: [PATCH 034/154] test: Add test case for duplicate checking --- .../doctype/item_price/test_item_price.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 702acc38fe..f3d406eeca 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -138,4 +138,23 @@ class TestItemPrice(unittest.TestCase): # Valid price list must already exist self.assertRaises(frappe.ValidationError, doc.save) + def test_empty_duplicate_validation(self): + # Check if none/empty values are not compared during insert validation + doc = frappe.copy_doc(test_records[2]) + doc.customer = None + doc.price_list_rate = 21 + doc.insert() + + args = { + "price_list": doc.price_list, + "uom": "_Test UOM", + "transaction_date": '2017-04-18', + "qty": 7 + } + + price = get_price_list_rate_for(args, doc.item_code) + frappe.db.rollback() + + self.assertEqual(price, 21) + test_records = frappe.get_test_records('Item Price') From 8c13fded17c6a52c09ac5a37bf636b91b2076e8d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Oct 2020 09:23:04 +0530 Subject: [PATCH 035/154] fix: Add unaccrued interest in interest amount for loan closure --- erpnext/loan_management/doctype/loan_repayment/loan_repayment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index c8344d4667..940f82ee34 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -389,6 +389,7 @@ def calculate_amounts(against_loan, posting_date, payment_type=''): if payment_type == 'Loan Closure': amounts['payable_amount'] += amounts['unaccrued_interest'] + amounts['interest_amount'] += amounts['unaccrued_interest'] return amounts From 66967c6d450f5d83cf6d999ecb4b5516fd71a050 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Oct 2020 18:10:51 +0530 Subject: [PATCH 036/154] fix: Loan Security unpledge on loan cancel --- erpnext/loan_management/doctype/loan/loan.js | 3 +++ erpnext/loan_management/doctype/loan/loan.py | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 682b574fea..0dc3bf8563 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -11,6 +11,9 @@ frappe.ui.form.on('Loan', { } }, onload: function (frm) { + // Ignore loan security pledge on cancel of loan + frm.ignore_doctypes_on_cancel_all = ["Loan Security Pledge"]; + frm.set_query("loan_application", function () { return { "filters": { diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 1fb0805d38..2d705fc296 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -138,9 +138,12 @@ class Loan(AccountsController): }) def unlink_loan_security_pledge(self): - frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET - loan = '', status = 'Unpledged' - where name = %s """, (self.loan_security_pledge)) + pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name}) + pledge_list = [d.name for d in pledges] + if pledge_list: + frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET + loan = '', status = 'Unpledged' + where name in (%s) """ % (', '.join(['%s']*len(pledge_list))), tuple(pledge_list)) def update_total_amount_paid(doc): total_amount_paid = 0 From dfc3993cecdd7f0e7d9cbd80734fbf8c6b8021f2 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 14 Oct 2020 12:21:36 +0530 Subject: [PATCH 037/154] chore(UAE VAT 21): rename report --- erpnext/accounts/desk_page/accounting/accounting.json | 2 +- .../report/{uae_vat_21 => uae_vat_201}/__init__.py | 0 .../uae_vat_21.html => uae_vat_201/uae_vat_201.html} | 0 .../uae_vat_21.js => uae_vat_201/uae_vat_201.js} | 2 +- .../uae_vat_21.json => uae_vat_201/uae_vat_201.json} | 4 ++-- .../uae_vat_21.py => uae_vat_201/uae_vat_201.py} | 6 +++--- erpnext/regional/united_arab_emirates/setup.py | 10 +++++----- erpnext/regional/united_arab_emirates/utils.py | 8 ++------ 8 files changed, 14 insertions(+), 18 deletions(-) rename erpnext/regional/report/{uae_vat_21 => uae_vat_201}/__init__.py (100%) rename erpnext/regional/report/{uae_vat_21/uae_vat_21.html => uae_vat_201/uae_vat_201.html} (100%) rename erpnext/regional/report/{uae_vat_21/uae_vat_21.js => uae_vat_201/uae_vat_201.js} (96%) rename erpnext/regional/report/{uae_vat_21/uae_vat_21.json => uae_vat_201/uae_vat_201.json} (88%) rename erpnext/regional/report/{uae_vat_21/uae_vat_21.py => uae_vat_201/uae_vat_201.py} (98%) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 214e31f21f..7d25b27e77 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -83,7 +83,7 @@ { "hidden": 0, "label": "Value-Added Tax (VAT UAE)", - "links": "[\n {\n \"label\": \"UAE VAT Setting\",\n \"name\": \"UAE VAT Setting\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"UAE VAT 21\",\n \"name\": \"UAE VAT 21\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"label\": \"UAE VAT Setting\",\n \"name\": \"UAE VAT Setting\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n]" } ], "category": "Modules", diff --git a/erpnext/regional/report/uae_vat_21/__init__.py b/erpnext/regional/report/uae_vat_201/__init__.py similarity index 100% rename from erpnext/regional/report/uae_vat_21/__init__.py rename to erpnext/regional/report/uae_vat_201/__init__.py diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.html b/erpnext/regional/report/uae_vat_201/uae_vat_201.html similarity index 100% rename from erpnext/regional/report/uae_vat_21/uae_vat_21.html rename to erpnext/regional/report/uae_vat_201/uae_vat_201.html diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.js b/erpnext/regional/report/uae_vat_201/uae_vat_201.js similarity index 96% rename from erpnext/regional/report/uae_vat_21/uae_vat_21.js rename to erpnext/regional/report/uae_vat_201/uae_vat_201.js index 5fc5449655..5957424770 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.js +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["UAE VAT 21"] = { +frappe.query_reports["UAE VAT 201"] = { "filters": [ { "fieldname": "company", diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.json b/erpnext/regional/report/uae_vat_201/uae_vat_201.json similarity index 88% rename from erpnext/regional/report/uae_vat_21/uae_vat_21.json rename to erpnext/regional/report/uae_vat_201/uae_vat_201.json index 421990cc4a..8a88bcd3e2 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.json +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.json @@ -12,11 +12,11 @@ "modified": "2020-09-10 08:51:02.298482", "modified_by": "Administrator", "module": "Regional", - "name": "UAE VAT 21", + "name": "UAE VAT 201", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "GL Entry", - "report_name": "UAE VAT 21", + "report_name": "UAE VAT 201", "report_type": "Script Report", "roles": [] } \ No newline at end of file diff --git a/erpnext/regional/report/uae_vat_21/uae_vat_21.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py similarity index 98% rename from erpnext/regional/report/uae_vat_21/uae_vat_21.py rename to erpnext/regional/report/uae_vat_201/uae_vat_201.py index 227c2fb5a1..bcd31351ea 100644 --- a/erpnext/regional/report/uae_vat_21/uae_vat_21.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -279,7 +279,7 @@ def get_standard_rated_expenses_total(filters): select sum(total) from `tabPurchase Invoice` where - standard_rated_expenses > 0 + claimable_standard_rated_expenses > 0 and docstatus = 1 {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 @@ -287,10 +287,10 @@ def get_standard_rated_expenses_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions(filters) return frappe.db.sql(""" - select sum(standard_rated_expenses) from + select sum(claimable_standard_rated_expenses) from `tabPurchase Invoice` where - standard_rated_expenses > 0 + claimable_standard_rated_expenses > 0 and docstatus = 1 {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index e48b9a9ce1..a0e7620919 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -38,11 +38,11 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Read Only', insert_after='supplier_name', fetch_from='supplier.supplier_name_in_arabic', print_hide=1), - dict(fieldname='standard_rated_expenses', label='Standard Rated Expenses (AED)', + dict(fieldname='claimable_standard_rated_expenses', label='Claimable Standard Rated Expenses (AED)', insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0', depends_on="eval:doc.reverse_charge=='N'",), dict(fieldname='reverse_charge', label='Reverse Charge Applicable', - fieldtype='Select', insert_after='standard_rated_expenses', print_hide=1, + fieldtype='Select', insert_after='claimable_standard_rated_expenses', print_hide=1, options='Y\nN', default='N'), dict(fieldname='claimable_reverse_charge', label='Claimable Reverse Charge (Percentage)', insert_after='reverse_charge', fieldtype='Percent', print_hide=1, @@ -132,11 +132,11 @@ def add_print_formats(): name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """) def add_custom_roles_for_reports(): - """Add Access Control to UAE VAT 21.""" - if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 21')): + """Add Access Control to UAE VAT 201.""" + if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 201')): frappe.get_doc(dict( doctype='Custom Role', - report='UAE VAT 21', + report='UAE VAT 201', roles= [ dict(role='Accounts User'), dict(role='Accounts Manager'), diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index dbe283f226..37501a2638 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -145,11 +145,7 @@ def make_regional_gl_entries(gl_entries, doc): def validate_returns(doc, method): """Standard Rated expenses should not be set when Reverse Charge Applicable is set.""" country = frappe.get_cached_value('Company', doc.company, 'country') - print("-"*50) - print(doc.reverse_charge) - print(flt(doc.standard_rated_expenses)) if country != 'United Arab Emirates': return - - if doc.reverse_charge == 'Y' and flt(doc.standard_rated_expenses) != 0: - frappe.throw(_("Standard Rated expenses should not be set when Reverse Charge Applicable is Y")) \ No newline at end of file + if doc.reverse_charge == 'Y' and flt(doc.claimable_standard_rated_expenses) != 0: + frappe.throw(_("Claimable Standard Rated expenses should not be set when Reverse Charge Applicable is Y")) \ No newline at end of file From b5d16a82eb73ab5a287a982d105bfeef65b3f3c6 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 14 Oct 2020 14:05:54 +0530 Subject: [PATCH 038/154] fix: Dont overrule Item Price via Pricing Rule Rate if 0 --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 454776e4e7..540a9b2c91 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -342,8 +342,14 @@ def apply_price_discount_rule(pricing_rule, item_details, args): pricing_rule_rate = 0.0 if pricing_rule.currency == args.currency: pricing_rule_rate = pricing_rule.rate + + if pricing_rule_rate: + # Override already set price list rate (from item price) + # if pricing_rule_rate > 0 + item_details.update({ + "price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1), + }) item_details.update({ - "price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1), "discount_percentage": 0.0 }) From b9dc13294d7233eebe37783394550a3bdbe0a334 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 14 Oct 2020 16:56:26 +0530 Subject: [PATCH 039/154] chore(UAE VAT 201) solve translations issues --- .../report/uae_vat_201/uae_vat_201.py | 24 +++++++++---------- .../regional/united_arab_emirates/utils.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index bcd31351ea..a2518c38c6 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -68,14 +68,14 @@ def get_data(filters = None): for d, emirate in enumerate(emirates, 97): if emirate in amounts_by_emirate: - amounts_by_emirate[emirate]["no"] = _(f'1{chr(d)}') - amounts_by_emirate[emirate]["legend"] = _(f'Standard rated supplies in {emirate}') + amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(d)) + amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate) data.append(amounts_by_emirate[emirate]) else: data.append( { - "no": _(f'1{chr(d)}'), - "legend": _(f'Standard rated supplies in {emirate}'), + "no": _('1{0}').format(chr(d)), + "legend": _('Standard rated supplies in {0}').format(emirate), "amount": frappe.format(0, 'Currency'), "vat_amount": frappe.format(0, 'Currency') } @@ -83,7 +83,7 @@ def get_data(filters = None): data.append( { - "no": _('2'), + "no": '2', "legend": _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'), "amount": frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'), "vat_amount": frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency') @@ -92,7 +92,7 @@ def get_data(filters = None): data.append( { - "no": _('3'), + "no": '3', "legend": _('Supplies subject to the reverse charge provision'), "amount": frappe.format(get_reverse_charge_total(filters), 'Currency'), "vat_amount": frappe.format(get_reverse_charge_tax(filters), 'Currency') @@ -101,7 +101,7 @@ def get_data(filters = None): data.append( { - "no": _('4'), + "no": '4', "legend": _('Zero Rated'), "amount": frappe.format(get_zero_rated_total(filters), 'Currency'), "vat_amount": "-" @@ -110,7 +110,7 @@ def get_data(filters = None): data.append( { - "no": _('5'), + "no": '5', "legend": _('Exempt Supplies'), "amount": frappe.format(get_exempt_total(filters), 'Currency'), "vat_amount": "-" @@ -118,14 +118,14 @@ def get_data(filters = None): ) data.append({ - "no": _(''), - "legend": _(''), + "no": '', + "legend": '', "amount": '', "vat_amount": '' }) data.append({ - "no": _(''), + "no": '', "legend": _('VAT on Expenses and All Other Inputs'), "amount": '', "vat_amount": '' @@ -142,7 +142,7 @@ def get_data(filters = None): data.append( { - "no": _('10'), + "no": '10', "legend": _('Supplies subject to the reverse charge provision'), "amount": frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'), "vat_amount": frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency') diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 37501a2638..847bda8271 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -52,7 +52,7 @@ def get_tax_accounts(company): ) if not tax_accounts_list and not frappe.flags.in_test: - frappe.throw(_(f'Please set Vat Accounts for Company: "{company}" in UAE VAT Settings')) + frappe.throw(_('Please set Vat Accounts for Company: "{0}" in UAE VAT Settings').format(company)) for d in tax_accounts_list: for key, name in d.items(): tax_accounts_dict[name] = name From c70cc0d95080d3337b4613c56983e5fbaad47426 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 29 Sep 2020 21:37:08 +0530 Subject: [PATCH 040/154] fix: tds calculation, skip invoices with "Apply Tax Withholding Amount" has disabled --- .../tax_withholding_category.py | 6 ++--- .../test_tax_withholding_category.py | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 8b5e68b359..32ad4cb03a 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -140,9 +140,9 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai else: tds_amount = _get_tds(net_total, tax_details.rate) else: - supplier_credit_amount = frappe.get_all('Purchase Invoice Item', - fields = ['sum(net_amount)'], - filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1) + supplier_credit_amount = frappe.get_all('Purchase Invoice', + fields = ['sum(net_total)'], + filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1) supplier_credit_amount = (supplier_credit_amount[0][0] if supplier_credit_amount and supplier_credit_amount[0][0] else 0) 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 b1468999fc..a0b0cbb995 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 @@ -101,6 +101,29 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self): + invoices = [] + frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS") + pi = create_purchase_invoice(supplier="Test TDS Supplier2") + pi.submit() + invoices.append(pi) + + # TDS not applied + pi = create_purchase_invoice(supplier="Test TDS Supplier2", do_not_apply_tds=True) + pi.submit() + invoices.append(pi) + + pi = create_purchase_invoice(supplier="Test TDS Supplier2") + pi.submit() + invoices.append(pi) + + self.assertEqual(pi.taxes_and_charges_deducted, 2000) + self.assertEqual(pi.grand_total, 8000) + + # delete invoices to avoid clashing + for d in invoices: + d.cancel() + def create_purchase_invoice(**args): # return sales invoice doc object item = frappe.get_doc('Item', {'item_name': 'TDS Item'}) @@ -109,7 +132,7 @@ def create_purchase_invoice(**args): pi = frappe.get_doc({ "doctype": "Purchase Invoice", "posting_date": today(), - "apply_tds": 1, + "apply_tds": 0 if args.do_not_apply_tds else 1, "supplier": args.supplier, "company": '_Test Company', "taxes_and_charges": "", From c0e24735e362a64228c5e75421dc04ad78a2ef4d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 17 Oct 2020 22:31:36 +0530 Subject: [PATCH 041/154] feat: Add loan write off doctype --- .../doctype/loan_write_off/__init__.py | 0 .../doctype/loan_write_off/loan_write_off.js | 18 +++ .../loan_write_off/loan_write_off.json | 141 ++++++++++++++++++ .../doctype/loan_write_off/loan_write_off.py | 79 ++++++++++ .../loan_write_off/test_loan_write_off.py | 10 ++ 5 files changed, 248 insertions(+) create mode 100644 erpnext/loan_management/doctype/loan_write_off/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_write_off/loan_write_off.js create mode 100644 erpnext/loan_management/doctype/loan_write_off/loan_write_off.json create mode 100644 erpnext/loan_management/doctype/loan_write_off/loan_write_off.py create mode 100644 erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py diff --git a/erpnext/loan_management/doctype/loan_write_off/__init__.py b/erpnext/loan_management/doctype/loan_write_off/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js new file mode 100644 index 0000000000..cc5cd0d3a0 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js @@ -0,0 +1,18 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +{% include 'erpnext/loan_management/loan_common.js' %}; + +frappe.ui.form.on('Loan Write Off', { + refresh: function(frm) { + frm.set_query('write_off_account', function(){ + return { + filters: { + 'company': frm.doc.company, + 'root_type': 'Expense', + 'is_group': 0 + } + } + }); + } +}); diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json new file mode 100644 index 0000000000..64623c4b3a --- /dev/null +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json @@ -0,0 +1,141 @@ +{ + "actions": [], + "autoname": "LM-WO-.#####", + "creation": "2020-10-16 11:09:14.495066", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "write_off_account", + "column_break_11", + "write_off_amount", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Write Off Details" + }, + { + "fieldname": "write_off_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Write Off Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "write_off_amount", + "fieldtype": "Currency", + "label": "Write Off Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Write Off", + "print_hide": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2020-10-17 08:30:54.859362", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Write Off", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py new file mode 100644 index 0000000000..22fbe1ac57 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, erpnext +from frappe import _ +from frappe.utils import getdate +from frappe.model.document import Document +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.accounts.general_ledger import make_gl_entries + +class LoanWriteOff(AccountsController): + def validate(self): + self.set_missing_values() + + def set_missing_values(self): + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + def on_submit(self): + self.update_outstanding_amount() + self.make_gl_entries() + + def on_cancel(self): + self.update_outstanding_amount(cancel=1) + self.ignore_linked_doctypes = ['GL Entry'] + self.make_gl_entries(cancel=1) + + def update_outstanding_amount(self, cancel=0): + written_off_amount = frappe.db.get_value('Loan', self.loan, 'written_off_amount') + + if cancel: + written_off_amount -= self.write_off_amount + else: + written_off_amount += self.write_off_amount + + frappe.db.set_value('Loan', self.loan, 'written_off_amount', written_off_amount) + + + def make_gl_entries(self, cancel=0): + gl_entries = [] + loan_details = frappe.get_doc("Loan", self.loan) + + gl_entries.append( + self.get_gl_dict({ + "account": self.write_off_account, + "against": loan_details.loan_account, + "debit": self.write_off_amount, + "debit_in_account_currency": self.write_off_amount, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("Against Loan:") + self.loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": getdate(self.posting_date) + }) + ) + + gl_entries.append( + self.get_gl_dict({ + "account": loan_details.loan_account, + "party_type": loan_details.applicant_type, + "party": loan_details.applicant, + "against": self.write_off_account, + "credit": self.write_off_amount, + "credit_in_account_currency": self.write_off_amount, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("Against Loan:") + self.loan, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date) + }) + ) + + make_gl_entries(gl_entries, cancel=cancel, merge_entries=False) + + diff --git a/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py new file mode 100644 index 0000000000..9f6700e274 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_write_off/test_loan_write_off.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestLoanWriteOff(unittest.TestCase): + pass From 9945ccc0cc76b08fe4f9e09fae134f066010a10b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 18 Oct 2020 22:25:24 +0530 Subject: [PATCH 042/154] fix: Write Off amount handling in Loan accrual and closure --- .../loan_management/desk_page/loan/loan.json | 7 +-- erpnext/loan_management/doctype/loan/loan.js | 25 +++++++++- .../loan_management/doctype/loan/loan.json | 10 +++- erpnext/loan_management/doctype/loan/loan.py | 50 ++++++++++++++++--- .../doctype/loan/loan_dashboard.py | 2 +- .../loan_disbursement/loan_disbursement.json | 25 +++++++--- .../loan_interest_accrual.py | 9 ++-- .../doctype/loan_repayment/loan_repayment.py | 10 ++-- .../loan_security_unpledge.py | 6 +-- .../doctype/loan_type/loan_type.json | 17 ++++--- erpnext/loan_management/loan_common.js | 2 +- 11 files changed, 126 insertions(+), 37 deletions(-) diff --git a/erpnext/loan_management/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json index 3bdd1ce56e..fc59c19325 100644 --- a/erpnext/loan_management/desk_page/loan/loan.json +++ b/erpnext/loan_management/desk_page/loan/loan.json @@ -3,7 +3,7 @@ { "hidden": 0, "label": "Loan", - "links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n { \"dependencies\": [\n \"Loan Type\"\n ],\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -13,7 +13,7 @@ { "hidden": 0, "label": "Disbursement and Repayment", - "links": "[\n {\n \"label\": \"Loan Disbursement\",\n \"name\": \"Loan Disbursement\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Repayment\",\n \"name\": \"Loan Repayment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Interest Accrual\",\n \"name\": \"Loan Interest Accrual\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"label\": \"Loan Disbursement\",\n \"name\": \"Loan Disbursement\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Repayment\",\n \"name\": \"Loan Repayment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Write Off\",\n \"name\": \"Loan Write Off\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Interest Accrual\",\n \"name\": \"Loan Interest Accrual\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -34,10 +34,11 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Loan", - "modified": "2020-06-07 19:42:14.947902", + "modified": "2020-10-17 12:59:50.336085", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 0dc3bf8563..8d101b862a 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -64,12 +64,13 @@ frappe.ui.form.on('Loan', { frm.add_custom_button(__('Request Loan Closure'), function() { frm.trigger("request_loan_closure"); },__('Status')); + frm.add_custom_button(__('Loan Repayment'), function() { frm.trigger("make_repayment_entry"); },__('Create')); } - if (frm.doc.status == "Sanctioned" || frm.doc.status == 'Partially Disbursed') { + if (["Sanctioned", "Partially Disbursed"].includes(frm.doc.status)) { frm.add_custom_button(__('Loan Disbursement'), function() { frm.trigger("make_loan_disbursement"); },__('Create')); @@ -80,6 +81,12 @@ frappe.ui.form.on('Loan', { frm.trigger("create_loan_security_unpledge"); },__('Create')); } + + if (["Loan Closure Requested", "Disbursed", "Partially Disbursed"].includes(frm.doc.status)) { + frm.add_custom_button(__('Loan Write Off'), function() { + frm.trigger("make_loan_write_off_entry"); + },__('Create')); + } } frm.trigger("toggle_fields"); }, @@ -130,6 +137,22 @@ frappe.ui.form.on('Loan', { }) }, + make_loan_write_off_entry: function(frm) { + frappe.call({ + args: { + "loan": frm.doc.name, + "company": frm.doc.company, + "as_dict": 1 + }, + method: "erpnext.loan_management.doctype.loan.loan.make_loan_write_off", + callback: function (r) { + if (r.message) + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + }) + }, + request_loan_closure: function(frm) { frappe.confirm(__("Do you really want to close this loan"), function() { diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index aa5e21b426..312e9affb9 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -43,6 +43,7 @@ "section_break_17", "total_payment", "total_principal_paid", + "written_off_amount", "column_break_19", "total_interest_payable", "total_amount_paid", @@ -330,11 +331,18 @@ "label": "Maximum Loan Amount", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "written_off_amount", + "fieldtype": "Currency", + "label": "Written Off Amount", + "options": "Company:company:default_currency" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-08-01 12:36:11.255233", + "modified": "2020-10-17 10:35:44.361836", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 2d705fc296..8405d6ec62 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -143,7 +143,7 @@ class Loan(AccountsController): if pledge_list: frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET loan = '', status = 'Unpledged' - where name in (%s) """ % (', '.join(['%s']*len(pledge_list))), tuple(pledge_list)) + where name in (%s) """ % (', '.join(['%s']*len(pledge_list))), tuple(pledge_list)) #nosec def update_total_amount_paid(doc): total_amount_paid = 0 @@ -187,17 +187,22 @@ def get_monthly_repayment_amount(repayment_method, loan_amount, rate_of_interest return monthly_repayment_amount @frappe.whitelist() -def request_loan_closure(loan): - amounts = calculate_amounts(loan, getdate()) +def request_loan_closure(loan, posting_date=None): + if not posting_date: + posting_date = getdate() + amounts = calculate_amounts(loan, posting_date) pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest'] + loan_type = frappe.get_value('Loan', loan, 'loan_type') + write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount') + # checking greater than 0 as there may be some minor precision error - if pending_amount > 0: - frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount)) - else: + if pending_amount < write_off_limit: # update status as loan closure requested frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested') + else: + frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount)) @frappe.whitelist() def get_loan_application(loan_application): @@ -217,6 +222,7 @@ def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amo disbursement_entry.applicant = applicant disbursement_entry.company = company disbursement_entry.disbursement_date = nowdate() + disbursement_entry.posting_date = nowdate() disbursement_entry.disbursed_amount = pending_amount if as_dict: @@ -239,6 +245,38 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as else: return repayment_entry +@frappe.whitelist() +def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict=0): + if not company: + company = frappe.get_value('Loan', loan, 'company') + + if not posting_date: + posting_date = getdate() + + amounts = calculate_amounts(loan, posting_date) + pending_amount = amounts['pending_principal_amount'] + + if amount and (amount > pending_amount): + frappe.throw('Write Off amount cannot be greater than pending loan amount') + + if not amount: + amount = pending_amount + + # get default write off account from company master + write_off_account = frappe.get_value('Company', company, 'write_off_account') + + write_off = frappe.new_doc('Loan Write Off') + write_off.loan = loan + write_off.posting_date = posting_date + write_off.write_off_account = write_off_account + write_off.write_off_amount = amount + write_off.save() + + if as_dict: + return write_off.as_dict() + else: + return write_off + @frappe.whitelist() def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0): # if loan is passed it will be considered as full unpledge diff --git a/erpnext/loan_management/doctype/loan/loan_dashboard.py b/erpnext/loan_management/doctype/loan/loan_dashboard.py index 90d5ae2650..7a8190f745 100644 --- a/erpnext/loan_management/doctype/loan/loan_dashboard.py +++ b/erpnext/loan_management/doctype/loan/loan_dashboard.py @@ -13,7 +13,7 @@ def get_data(): 'items': ['Loan Security Pledge', 'Loan Security Shortfall', 'Loan Disbursement'] }, { - 'items': ['Loan Repayment', 'Loan Interest Accrual', 'Loan Security Unpledge'] + 'items': ['Loan Repayment', 'Loan Interest Accrual', 'Loan Write Off', 'Loan Security Unpledge'] } ] } \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json index c437a987eb..89f671bcc0 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json @@ -26,19 +26,23 @@ { "fieldname": "against_loan", "fieldtype": "Link", + "in_list_view": 1, "label": "Against Loan ", - "options": "Loan" + "options": "Loan", + "reqd": 1 }, { "fieldname": "disbursement_date", "fieldtype": "Date", - "label": "Disbursement Date" + "label": "Disbursement Date", + "reqd": 1 }, { "fieldname": "disbursed_amount", "fieldtype": "Currency", "label": "Disbursed Amount", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "reqd": 1 }, { "fieldname": "amended_from", @@ -53,17 +57,21 @@ "fetch_from": "against_loan.company", "fieldname": "company", "fieldtype": "Link", + "in_list_view": 1, "label": "Company", "options": "Company", - "read_only": 1 + "read_only": 1, + "reqd": 1 }, { "fetch_from": "against_loan.applicant", "fieldname": "applicant", "fieldtype": "Dynamic Link", + "in_list_view": 1, "label": "Applicant", "options": "applicant_type", - "read_only": 1 + "read_only": 1, + "reqd": 1 }, { "collapsible": 1, @@ -102,9 +110,11 @@ "fetch_from": "against_loan.applicant_type", "fieldname": "applicant_type", "fieldtype": "Select", + "in_list_view": 1, "label": "Applicant Type", "options": "Employee\nMember\nCustomer", - "read_only": 1 + "read_only": 1, + "reqd": 1 }, { "fieldname": "bank_account", @@ -117,9 +127,10 @@ "fieldtype": "Column Break" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-04-29 05:20:41.629911", + "modified": "2020-10-16 10:04:26.229216", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Disbursement", diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 4517de0c59..e31b844953 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -89,9 +89,10 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i if loan.status == 'Disbursed': pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \ - - flt(loan.total_principal_paid) + - flt(loan.total_principal_paid) - flt(loan.written_off_amount) else: - pending_principal_amount = loan.disbursed_amount + pending_principal_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \ + - flt(loan.total_principal_paid) - flt(loan.written_off_amount) interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date) payable_interest = interest_per_day * no_of_days @@ -128,7 +129,7 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte open_loans = frappe.get_all("Loan", fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant", - "rate_of_interest", "total_interest_payable", "total_principal_paid", "repayment_start_date"], + "rate_of_interest", "total_interest_payable", "written_off_amount", "total_principal_paid", "repayment_start_date"], filters=query_filters) for loan in open_loans: @@ -239,5 +240,5 @@ def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None): precision = cint(frappe.db.get_default("currency_precision")) or 2 - return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100), 2) + return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100), precision) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 940f82ee34..de5ba8fcd5 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -31,8 +31,8 @@ class LoanRepayment(AccountsController): def on_cancel(self): self.mark_as_unpaid() - self.make_gl_entries(cancel=1) self.ignore_linked_doctypes = ['GL Entry'] + self.make_gl_entries(cancel=1) def set_missing_values(self, amounts): precision = cint(frappe.db.get_default("currency_precision")) or 2 @@ -235,7 +235,7 @@ class LoanRepayment(AccountsController): "against": loan_details.loan_account + ", " + loan_details.interest_income_account + ", " + loan_details.penalty_income_account, "debit": self.amount_paid, - "debit_in_account_currency": self.amount_paid , + "debit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, "remarks": _("Against Loan:") + self.against_loan, @@ -344,9 +344,11 @@ def get_amounts(amounts, against_loan, posting_date): final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'): - pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable + pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid \ + - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount else: - pending_principal_amount = against_loan_doc.disbursed_amount + pending_principal_amount = against_loan_doc.disbursed_amount - against_loan_doc.total_principal_paid \ + - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount unaccrued_interest = 0 if due_date: diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index b3eb6001e4..d0d25e8897 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -42,10 +42,10 @@ class LoanSecurityUnpledge(Document): "valid_upto": (">=", get_datetime()) }, as_list=1)) - total_payment, principal_paid, interest_payable = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid', - 'total_interest_payable']) + total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid', + 'total_interest_payable', 'written_off_amount']) - pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) + pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount) security_value = 0 for security in self.securities: diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index 669490a448..5d9232d711 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -11,6 +11,7 @@ "rate_of_interest", "penalty_interest_rate", "grace_period_in_days", + "write_off_amount", "column_break_2", "company", "is_term_loan", @@ -76,7 +77,6 @@ "reqd": 1 }, { - "description": "This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower", "fieldname": "payment_account", "fieldtype": "Link", "label": "Payment Account", @@ -84,7 +84,6 @@ "reqd": 1 }, { - "description": "This account is capital account which is used to allocate capital for loan disbursal account ", "fieldname": "loan_account", "fieldtype": "Link", "label": "Loan Account", @@ -96,7 +95,6 @@ "fieldtype": "Column Break" }, { - "description": "This account will be used for booking loan interest accruals", "fieldname": "interest_income_account", "fieldtype": "Link", "label": "Interest Income Account", @@ -104,7 +102,6 @@ "reqd": 1 }, { - "description": "This account will be used for booking penalties levied due to delayed repayments", "fieldname": "penalty_income_account", "fieldtype": "Link", "label": "Penalty Income Account", @@ -113,7 +110,6 @@ }, { "default": "0", - "description": "If this is not checked the loan by default will be considered as a Demand Loan", "fieldname": "is_term_loan", "fieldtype": "Check", "label": "Is Term Loan" @@ -145,11 +141,20 @@ "label": "Company", "options": "Company", "reqd": 1 + }, + { + "allow_on_submit": 1, + "description": "Pending amount that will be automatically ignored on loan closure request ", + "fieldname": "write_off_amount", + "fieldtype": "Currency", + "label": "Write Off Amount ", + "options": "Company:company:default_currency" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-06-07 18:55:59.346292", + "modified": "2020-10-17 11:41:17.907683", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", diff --git a/erpnext/loan_management/loan_common.js b/erpnext/loan_management/loan_common.js index 33a5de0566..50b68da30e 100644 --- a/erpnext/loan_management/loan_common.js +++ b/erpnext/loan_management/loan_common.js @@ -8,7 +8,7 @@ frappe.ui.form.on(cur_frm.doctype, { frm.refresh_field('applicant_type'); } - if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype) + if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual', 'Loan Write Off'].includes(frm.doc.doctype) && frm.doc.docstatus > 0) { frm.add_custom_button(__("Accounting Ledger"), function() { From 8cd8dbe15d736d33d04106def46c586bf39712b3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 18 Oct 2020 22:26:09 +0530 Subject: [PATCH 043/154] fix: Add write off test --- .../loan_management/doctype/loan/test_loan.py | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 7b653652ea..c38541196f 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -14,7 +14,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_ process_loan_interest_accrual_for_term_loans) from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall -from erpnext.loan_management.doctype.loan.loan import unpledge_security, request_loan_closure +from erpnext.loan_management.doctype.loan.loan import unpledge_security, request_loan_closure, make_loan_write_off from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount @@ -496,6 +496,96 @@ class TestLoan(unittest.TestCase): self.assertEquals(calculated_penalty_amount, penalty_amount) + def test_loan_write_off_limit(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + no_of_days = date_diff(last_date, first_date) + 1 + no_of_days += 6 + + accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + # repay 50 less so that it can be automatically written off + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + flt(loan.loan_amount + accrued_interest_amount - 50)) + + repayment_entry.submit() + + amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) + + self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50) + + request_loan_closure(loan.name) + loan.load_from_db() + self.assertEquals(loan.status, "Loan Closure Requested") + + def test_loan_amount_write_off(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + no_of_days = date_diff(last_date, first_date) + 1 + no_of_days += 6 + + accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + + # repay 100 less so that it can be automatically written off + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + flt(loan.loan_amount + accrued_interest_amount - 100)) + + repayment_entry.submit() + + amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) + + self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100) + + we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount']) + we.submit() + + amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0) + def create_loan_accounts(): if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"): @@ -579,7 +669,8 @@ def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest, penalty_i "interest_income_account": interest_income_account, "penalty_income_account": penalty_income_account, "repayment_method": repayment_method, - "repayment_periods": repayment_periods + "repayment_periods": repayment_periods, + "write_off_amount": 100 }).insert() loan_type.submit() From 32eb9a78930d2771f68ee25ad1930bda5929545f Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 19 Oct 2020 17:58:50 +0530 Subject: [PATCH 044/154] feat(UAE VAT 201): claimable to recoverable --- erpnext/regional/united_arab_emirates/setup.py | 10 +++++----- erpnext/regional/united_arab_emirates/utils.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index a0e7620919..efa39bd3c5 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -38,13 +38,13 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Read Only', insert_after='supplier_name', fetch_from='supplier.supplier_name_in_arabic', print_hide=1), - dict(fieldname='claimable_standard_rated_expenses', label='Claimable Standard Rated Expenses (AED)', + dict(fieldname='recoverable_standard_rated_expenses', label='Recoverable Standard Rated Expenses (AED)', insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0', depends_on="eval:doc.reverse_charge=='N'",), dict(fieldname='reverse_charge', label='Reverse Charge Applicable', - fieldtype='Select', insert_after='claimable_standard_rated_expenses', print_hide=1, + fieldtype='Select', insert_after='recoverable_standard_rated_expenses', print_hide=1, options='Y\nN', default='N'), - dict(fieldname='claimable_reverse_charge', label='Claimable Reverse Charge (Percentage)', + dict(fieldname='recoverable_reverse_charge', label='Recoverable Reverse Charge (Percentage)', insert_after='reverse_charge', fieldtype='Percent', print_hide=1, depends_on="eval:doc.reverse_charge=='Y'", default='100.000'), ] @@ -57,7 +57,7 @@ def make_custom_fields(): fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), dict(fieldname='emirate', label='Emirate', insert_after='customer_address', - fieldtype='Read Only', fetch_from='customer_address.emirates'), + fieldtype='Read Only', fetch_from='customer_address.emirate'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), ] @@ -102,7 +102,7 @@ def make_custom_fields(): fieldtype='Data', insert_after='supplier_name'), ], 'Address': [ - dict(fieldname='emirates', label='Emirates', fieldtype='Select', insert_after='state', + dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', options='Abu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 847bda8271..7edc2b6720 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -147,5 +147,5 @@ def validate_returns(doc, method): country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'United Arab Emirates': return - if doc.reverse_charge == 'Y' and flt(doc.claimable_standard_rated_expenses) != 0: - frappe.throw(_("Claimable Standard Rated expenses should not be set when Reverse Charge Applicable is Y")) \ No newline at end of file + if doc.reverse_charge == 'Y' and flt(doc.recoverable_standard_rated_expenses) != 0: + frappe.throw(_("Recoverable Standard Rated expenses should not be set when Reverse Charge Applicable is Y")) \ No newline at end of file From c1b80047571a579c34899ad4e458c28e89bf4c31 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 19 Oct 2020 20:47:56 +0530 Subject: [PATCH 045/154] feat(UAE VAT 21): claimable to recoverable --- .../regional/report/uae_vat_201/uae_vat_201.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index a2518c38c6..b5ff8389e3 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -245,14 +245,14 @@ def get_conditions_join(filters): return conditions def get_reverse_charge_recoverable_total(filters): - """Returns the sum of the total of each Purchase invoice made with claimable reverse charge.""" + """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(total) from `tabPurchase Invoice` where reverse_charge = "Y" - and claimable_reverse_charge > 0 + and recoverable_reverse_charge > 0 and docstatus = 1 {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 @@ -260,26 +260,26 @@ def get_reverse_charge_recoverable_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" - select sum(debit * `tabPurchase Invoice`.claimable_reverse_charge / 100) from + select sum(debit * `tabPurchase Invoice`.recoverable_reverse_charge / 100) from `tabPurchase Invoice` inner join `tabGL Entry` on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name where `tabPurchase Invoice`.reverse_charge = "Y" and `tabPurchase Invoice`.docstatus = 1 - and `tabPurchase Invoice`.claimable_reverse_charge > 0 + and `tabPurchase Invoice`.recoverable_reverse_charge > 0 and `tabGL Entry`.docstatus = 1 and account in (select account from `tabUAE VAT Account` where parent=%(company)s) {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 def get_standard_rated_expenses_total(filters): - """Returns the sum of the total of each Purchase invoice made with claimable reverse charge.""" + """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" conditions = get_conditions(filters) return frappe.db.sql(""" select sum(total) from `tabPurchase Invoice` where - claimable_standard_rated_expenses > 0 + recoverable_standard_rated_expenses > 0 and docstatus = 1 {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 @@ -287,10 +287,10 @@ def get_standard_rated_expenses_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions(filters) return frappe.db.sql(""" - select sum(claimable_standard_rated_expenses) from + select sum(recoverable_standard_rated_expenses) from `tabPurchase Invoice` where - claimable_standard_rated_expenses > 0 + recoverable_standard_rated_expenses > 0 and docstatus = 1 {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 From 33cdfdfb7f21661997cef6fa70095f436b348054 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 19 Oct 2020 20:48:43 +0530 Subject: [PATCH 046/154] feat(UAE VAT 201): Add tests for report --- .../purchase_invoice/test_purchase_invoice.py | 3 +- .../report/uae_vat_201/test_uae_vat_201.py | 370 ++++++++++++++++++ 2 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 erpnext/regional/report/uae_vat_201/test_uae_vat_201.py diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2e5a7142a3..8276c1fc03 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1040,7 +1040,8 @@ def make_purchase_invoice_against_cost_center(**args): pi.is_return = args.is_return pi.credit_to = args.return_against or "Creditors - _TC" pi.is_subcontracted = args.is_subcontracted or "No" - pi.supplier_warehouse = "_Test Warehouse 1 - _TC" + if args.supplier_warehouse: + pi.supplier_warehouse = "_Test Warehouse 1 - _TC" pi.append("items", { "item_code": args.item or args.item_code or "_Test Item", diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py new file mode 100644 index 0000000000..8e845d7432 --- /dev/null +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -0,0 +1,370 @@ +# coding=utf-8 +from __future__ import unicode_literals + +import erpnext +import frappe +from unittest import TestCase +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse_account +from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( + get_total_emiratewise, + get_tourist_tax_return_total, + get_tourist_tax_return_tax, + get_reverse_charge_total, + get_reverse_charge_tax, + get_zero_rated_total, + get_exempt_total, + get_standard_rated_expenses_total, + get_standard_rated_expenses_tax, + get_reverse_charge_recoverable_total, + get_reverse_charge_recoverable_tax + ) + + + +class TestUaeVat201(TestCase): + def setUp(self): + frappe.set_user("Administrator") + + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company UAE VAT'") + frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company UAE VAT'") + + + make_company("_Test Company UAE VAT", "_TCUV") + set_vat_accounts() + + make_customers() + + make_supplier() + + create_warehouse("_Test UAE VAT Supplier Warehouse", company="_Test Company UAE VAT") + + + make_item("_Test UAE VAT Item", properties = {"is_zero_rated": 0, "is_exempt": 0}) + make_item("_Test UAE VAT Zero Rated Item", properties = {"is_zero_rated": 1, "is_exempt": 0}) + make_item("_Test UAE VAT Exempt Item", properties = {"is_zero_rated": 0, "is_exempt": 1}) + + make_sales_invoices() + + create_purchase_invoices() + + def test_uae_vat_201_report(self): + filters = {"company": "_Test Company UAE VAT"} + total_emiratewise = get_total_emiratewise(filters) + amounts_by_emirate = {} + for d in total_emiratewise: + emirate, amount, vat= d + amounts_by_emirate[emirate] = { + "raw_amount": amount, + "raw_vat_amount": vat, + } + self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"],300) + self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"],5) + self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200) + self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"],10) + + self.assertEqual(get_tourist_tax_return_tax(filters),2) + self.assertEqual(get_reverse_charge_total(filters),250) + self.assertEqual(get_reverse_charge_tax(filters),12.5) + self.assertEqual(get_zero_rated_total(filters),100) + self.assertEqual(get_exempt_total(filters),100) + self.assertEqual(get_standard_rated_expenses_total(filters),250) + self.assertEqual(get_standard_rated_expenses_tax(filters),1) + self.assertEqual(get_reverse_charge_recoverable_total(filters),250) + self.assertEqual(get_reverse_charge_recoverable_tax(filters),12.5) + +def make_company(company_name, abbr): + if not frappe.db.exists("Company", company_name): + company = frappe.get_doc({ + "doctype": "Company", + "company_name": company_name, + "abbr": abbr, + "default_currency": "AED", + "country": "United Arab Emirates", + "create_chart_of_accounts_based_on": "Standard Template", + }) + company.insert() + else: + company = frappe.get_doc("Company", company_name) + + # indempotent + company.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): + company.create_default_cost_center() + + company.save() + return company + +def set_vat_accounts(): + if not frappe.db.exists("UAE VAT Settings", "_Test Company UAE VAT"): + vat_accounts = frappe.get_all( + "Account", + fields=["name"], + filters = { + "company": "_Test Company UAE VAT", + "is_group":0, + "account_type": "Tax"}) + + uae_vat_accounts = [] + for d in vat_accounts: + uae_vat_accounts.append( + { + "doctype": "UAE VAT Account", + "account":d.name + }) + + frappe.get_doc({ + "company": "_Test Company UAE VAT", + "uae_vat_accounts": uae_vat_accounts, + "doctype": "UAE VAT Settings", + }).insert() + +def make_customers(): + if not frappe.db.exists("Customer", "_Test Dubai Customer"): + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_name": "_Test Dubai Customer", + "customer_type": "Company", + }) + customer.insert() + else: + customer = frappe.get_doc("Customer", "_Test Dubai Customer") + + if not frappe.db.exists("Customer", "_Test Sharjah Customer"): + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_name": "_Test Sharjah Customer", + "customer_type": "Company", + }) + customer.insert() + else: + customer = frappe.get_doc("Customer", "_Test Sharjah Customer") + + if not frappe.db.exists('Address', '_Test Dubai Address'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Dubai Address", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "United Arab Emirates", + "doctype": "Address", + "emirate": "Dubai" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Dubai Customer" + }) + + address.save() + + if not frappe.db.exists('Address', '_Test Sharjah Address'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Sharjah Address", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "United Arab Emirates", + "doctype": "Address", + "emirate": "Sharjah" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Sharjah Customer" + }) + + address.save() + +def make_supplier(): + + if not frappe.db.exists("Supplier", "_Test UAE Supplier"): + frappe.get_doc({ + "supplier_group": "Local", + "supplier_name": "_Test UAE Supplier", + "supplier_type": "Individual", + "doctype": "Supplier", + }).insert() + +def create_warehouse(warehouse_name, properties=None, company=None): + if not company: + company = "_Test Company" + + warehouse_id = erpnext.encode_company_abbr(warehouse_name, company) + if not frappe.db.exists("Warehouse", warehouse_id): + w = frappe.new_doc("Warehouse") + w.warehouse_name = warehouse_name + w.parent_warehouse = "All Warehouses - _TCUV" + w.company = company + w.account = get_warehouse_account(warehouse_name, company) + if properties: + w.update(properties) + w.save() + return w.name + else: + return warehouse_id + +def make_item(item_code, properties=None): + if frappe.db.exists("Item", item_code): + return frappe.get_doc("Item", item_code) + + item = frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "description": item_code, + "item_group": "Products" + }) + + if properties: + item.update(properties) + + item.insert() + + return item + +def make_sales_invoices(): + si = create_sales_invoice(company="_Test Company UAE VAT", + customer = '_Test Dubai Customer', + currency = 'AED', + warehouse = 'Finished Goods - _TCUV', + debit_to = 'Debtors - _TCUV', + income_account = 'Sales - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + cost_center = 'Main - _TCUV', + sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", + item = "_Test UAE VAT Item", + do_not_save=1 + ) + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + }) + si.submit() + + si = create_sales_invoice(company="_Test Company UAE VAT", + customer = '_Test Sharjah Customer', + currency = 'AED', + warehouse = 'Finished Goods - _TCUV', + debit_to = 'Debtors - _TCUV', + income_account = 'Sales - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + cost_center = 'Main - _TCUV', + sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", + item = "_Test UAE VAT Item", + do_not_save=1 + ) + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + }) + si.submit() + + si = create_sales_invoice(company="_Test Company UAE VAT", + customer = '_Test Dubai Customer', + currency = 'AED', + warehouse = 'Finished Goods - _TCUV', + debit_to = 'Debtors - _TCUV', + income_account = 'Sales - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + cost_center = 'Main - _TCUV', + sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", + item = "_Test UAE VAT Item", + do_not_save=1 + ) + + si.tourist_tax_return = 2 + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + }) + si.submit() + + si = create_sales_invoice(company="_Test Company UAE VAT", + customer = '_Test Sharjah Customer', + currency = 'AED', + warehouse = 'Finished Goods - _TCUV', + debit_to = 'Debtors - _TCUV', + income_account = 'Sales - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + cost_center = 'Main - _TCUV', + sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", + item = "_Test UAE VAT Zero Rated Item", + ) + + si = create_sales_invoice(company="_Test Company UAE VAT", + customer = '_Test Sharjah Customer', + currency = 'AED', + warehouse = 'Finished Goods - _TCUV', + debit_to = 'Debtors - _TCUV', + income_account = 'Sales - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + cost_center = 'Main - _TCUV', + sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", + item = "_Test UAE VAT Exempt Item", + ) + +def create_purchase_invoices(): + + pi = make_purchase_invoice( + company="_Test Company UAE VAT", + supplier = '_Test UAE Supplier', + warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', + currency = 'AED', + cost_center = 'Main - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + item = "_Test UAE VAT Item", + do_not_save=1, + ) + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + }) + + pi.recoverable_standard_rated_expenses = 1 + + pi.submit() + + pi = make_purchase_invoice( + company="_Test Company UAE VAT", + supplier = '_Test UAE Supplier', + warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', + currency = 'AED', + cost_center = 'Main - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + item = "_Test UAE VAT Item", + do_not_save=1, + ) + + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + }) + + pi.reverse_charge = "Y" + + pi.recoverable_reverse_charge = 100 + + pi.submit() + + From 853fd03f4910c9bd8641085ba876152e11f7728b Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 19 Oct 2020 22:35:16 +0530 Subject: [PATCH 047/154] feat(uae vat 201): fix linter issues --- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 8e845d7432..6a255b8a0b 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -64,6 +64,7 @@ class TestUaeVat201(TestCase): self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200) self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"],10) + self.assertEqual(get_tourist_tax_return_total(filters),100) self.assertEqual(get_tourist_tax_return_tax(filters),2) self.assertEqual(get_reverse_charge_total(filters),250) self.assertEqual(get_reverse_charge_tax(filters),12.5) From 234036df32fc6db164cf930dbc1e37dfabf1fee6 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 20 Oct 2020 07:42:10 +0530 Subject: [PATCH 048/154] chore(uae vat 201): solve travis issues --- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 6a255b8a0b..648d98e204 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -341,6 +341,7 @@ def create_purchase_invoices(): pi.recoverable_standard_rated_expenses = 1 + pi.insert() pi.submit() pi = make_purchase_invoice( @@ -366,6 +367,7 @@ def create_purchase_invoices(): pi.recoverable_reverse_charge = 100 + pi.insert() pi.submit() From c08591124c172da56a2e917644d864f4c6dc0ef5 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 20 Oct 2020 10:17:48 +0530 Subject: [PATCH 049/154] chore(UAE VAT 21): solve merge conflicts --- .../doctype/purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 8276c1fc03..f2499d24b5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -998,7 +998,7 @@ def make_purchase_invoice(**args): 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC', "conversion_factor": 1.0, "serial_no": args.serial_no, - "stock_uom": "_Test UOM", + "stock_uom": args.uom or "_Test UOM", "cost_center": args.cost_center or "_Test Cost Center - _TC", "project": args.project, "rejected_warehouse": args.rejected_warehouse or "", diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 648d98e204..68c163d5bc 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -324,12 +324,14 @@ def create_purchase_invoices(): pi = make_purchase_invoice( company="_Test Company UAE VAT", supplier = '_Test UAE Supplier', + supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', currency = 'AED', cost_center = 'Main - _TCUV', expense_account = 'Cost of Goods Sold - _TCUV', item = "_Test UAE VAT Item", do_not_save=1, + uom = "Nos" ) pi.append("taxes", { "charge_type": "On Net Total", @@ -341,18 +343,19 @@ def create_purchase_invoices(): pi.recoverable_standard_rated_expenses = 1 - pi.insert() pi.submit() pi = make_purchase_invoice( company="_Test Company UAE VAT", supplier = '_Test UAE Supplier', + supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', currency = 'AED', cost_center = 'Main - _TCUV', expense_account = 'Cost of Goods Sold - _TCUV', item = "_Test UAE VAT Item", do_not_save=1, + uom = "Nos" ) pi.append("taxes", { @@ -367,7 +370,6 @@ def create_purchase_invoices(): pi.recoverable_reverse_charge = 100 - pi.insert() pi.submit() From 6a24da4efd860b9f32ea61bb3cfc7ab23d787909 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 20 Oct 2020 11:53:06 +0530 Subject: [PATCH 050/154] refactor(UAE VAT 201): break functions --- .../report/uae_vat_201/uae_vat_201.py | 172 +++++++----------- 1 file changed, 69 insertions(+), 103 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index b5ff8389e3..90e7501291 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -46,109 +46,8 @@ def get_columns(): def get_data(filters = None): """Returns the list of dictionaries. Each dictionary is a row in the datatable and chart data.""" data = [] - data.append({ - "no": '', - "legend": _('VAT on Sales and All Other Outputs'), - "amount": '', - "vat_amount": '' - }) - - total_emiratewise = get_total_emiratewise(filters) - emirates = get_emirates() - amounts_by_emirate = {} - for d in total_emiratewise: - emirate, amount, vat= d - amounts_by_emirate[emirate] = { - "legend": emirate, - "raw_amount": amount, - "raw_vat_amount": vat, - "amount": frappe.format(amount, 'Currency'), - "vat_amount": frappe.format(vat, 'Currency'), - } - - for d, emirate in enumerate(emirates, 97): - if emirate in amounts_by_emirate: - amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(d)) - amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate) - data.append(amounts_by_emirate[emirate]) - else: - data.append( - { - "no": _('1{0}').format(chr(d)), - "legend": _('Standard rated supplies in {0}').format(emirate), - "amount": frappe.format(0, 'Currency'), - "vat_amount": frappe.format(0, 'Currency') - } - ) - - data.append( - { - "no": '2', - "legend": _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'), - "amount": frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'), - "vat_amount": frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency') - } - ) - - data.append( - { - "no": '3', - "legend": _('Supplies subject to the reverse charge provision'), - "amount": frappe.format(get_reverse_charge_total(filters), 'Currency'), - "vat_amount": frappe.format(get_reverse_charge_tax(filters), 'Currency') - } - ) - - data.append( - { - "no": '4', - "legend": _('Zero Rated'), - "amount": frappe.format(get_zero_rated_total(filters), 'Currency'), - "vat_amount": "-" - } - ) - - data.append( - { - "no": '5', - "legend": _('Exempt Supplies'), - "amount": frappe.format(get_exempt_total(filters), 'Currency'), - "vat_amount": "-" - } - ) - - data.append({ - "no": '', - "legend": '', - "amount": '', - "vat_amount": '' - }) - - data.append({ - "no": '', - "legend": _('VAT on Expenses and All Other Inputs'), - "amount": '', - "vat_amount": '' - }) - - data.append( - { - "no": '9', - "legend": _('Standard Rated Expenses'), - "amount": frappe.format(get_standard_rated_expenses_total(filters), 'Currency'), - "vat_amount": frappe.format(get_standard_rated_expenses_tax(filters), 'Currency') - } - ) - - data.append( - { - "no": '10', - "legend": _('Supplies subject to the reverse charge provision'), - "amount": frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'), - "vat_amount": frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency') - } - ) - + emirates, amounts_by_emirate = append_vat_on_sales(data, filters) + append_vat_on_expenses(data, filters) return data, emirates, amounts_by_emirate @@ -178,6 +77,73 @@ def get_chart(emirates, amounts_by_emirate): chart["fieldtype"] = "Currency" return chart +def append_vat_on_sales(data, filters): + """Appends Sales and All Other Outputs""" + append_data(data, '', _('VAT on Sales and All Other Outputs'), '', '') + + emirates, amounts_by_emirate = standard_rated_expenses_emiratewise(data, filters) + + append_data(data, '2', _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'), + frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'), + frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency')) + + append_data(data, '3', _('Supplies subject to the reverse charge provision'), + frappe.format(get_reverse_charge_total(filters), 'Currency'), + frappe.format(get_reverse_charge_tax(filters), 'Currency')) + + append_data(data, '4', _('Zero Rated'), + frappe.format(get_zero_rated_total(filters), 'Currency'), "-") + + append_data(data, '5', _('Exempt Supplies'), + frappe.format(get_exempt_total(filters), 'Currency'),"-") + + append_data(data, '', '', '', '') + + return emirates, amounts_by_emirate + +def standard_rated_expenses_emiratewise(data, filters): + """"Append emiratewise standard rated expenses and vat""" + total_emiratewise = get_total_emiratewise(filters) + emirates = get_emirates() + amounts_by_emirate = {} + for d in total_emiratewise: + emirate, amount, vat= d + amounts_by_emirate[emirate] = { + "legend": emirate, + "raw_amount": amount, + "raw_vat_amount": vat, + "amount": frappe.format(amount, 'Currency'), + "vat_amount": frappe.format(vat, 'Currency'), + } + + for d, emirate in enumerate(emirates, 97): + if emirate in amounts_by_emirate: + amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(d)) + amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate) + data.append(amounts_by_emirate[emirate]) + else: + append_data(data, _('1{0}').format(chr(d)), + _('Standard rated supplies in {0}').format(emirate), + frappe.format(0, 'Currency'), frappe.format(0, 'Currency')) + return emirates, amounts_by_emirate + + +def append_vat_on_expenses(data, filters): + """Appends Expenses and All Other Inputs""" + append_data(data, '', _('VAT on Expenses and All Other Inputs'), '', '') + append_data(data, '9', _('Standard Rated Expenses'), + frappe.format(get_standard_rated_expenses_total(filters), 'Currency'), + frappe.format(get_standard_rated_expenses_tax(filters), 'Currency')) + + append_data(data, '10', _('Supplies subject to the reverse charge provision'), + frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'), + frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency') +) + +def append_data(data, no, legend, amount, vat_amount): + """Returns data with appended value.""" + data.append({"no": no, "legend":legend, "amount": amount, "vat_amount": vat_amount}) + def get_total_emiratewise(filters): """Returns Emiratewise Amount and Taxes.""" return frappe.db.sql(f""" From 33e9a1ab31f5d22c2a865f84bdc8aa931c071545 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 20 Oct 2020 13:04:38 +0530 Subject: [PATCH 051/154] refactor(UAE VAT 201): solve linting issues --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 90e7501291..82032461ce 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -78,7 +78,7 @@ def get_chart(emirates, amounts_by_emirate): return chart def append_vat_on_sales(data, filters): - """Appends Sales and All Other Outputs""" + """Appends Sales and All Other Outputs.""" append_data(data, '', _('VAT on Sales and All Other Outputs'), '', '') emirates, amounts_by_emirate = standard_rated_expenses_emiratewise(data, filters) @@ -102,7 +102,7 @@ def append_vat_on_sales(data, filters): return emirates, amounts_by_emirate def standard_rated_expenses_emiratewise(data, filters): - """"Append emiratewise standard rated expenses and vat""" + """Append emiratewise standard rated expenses and vat""" total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() amounts_by_emirate = {} @@ -129,7 +129,7 @@ def standard_rated_expenses_emiratewise(data, filters): def append_vat_on_expenses(data, filters): - """Appends Expenses and All Other Inputs""" + """Appends Expenses and All Other Inputs.""" append_data(data, '', _('VAT on Expenses and All Other Inputs'), '', '') append_data(data, '9', _('Standard Rated Expenses'), frappe.format(get_standard_rated_expenses_total(filters), 'Currency'), From 8796567b4207195a4b24353470595e39ce1e56fd Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 20 Oct 2020 16:35:28 +0530 Subject: [PATCH 052/154] chore(UAE VAT 201): add puctuation --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- erpnext/regional/united_arab_emirates/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 82032461ce..5bed0e2463 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -102,7 +102,7 @@ def append_vat_on_sales(data, filters): return emirates, amounts_by_emirate def standard_rated_expenses_emiratewise(data, filters): - """Append emiratewise standard rated expenses and vat""" + """Append emiratewise standard rated expenses and vat.""" total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() amounts_by_emirate = {} diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index efa39bd3c5..fac6e270db 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -89,7 +89,7 @@ def make_custom_fields(): dict(fieldname='is_zero_rated', label='Is Zero Rated', fieldtype='Check', insert_after='tax_code', print_hide=1), - dict(fieldname='is_exempt', label='Is Exempt ', + dict(fieldname='is_exempt', label='Is Exempt', fieldtype='Check', insert_after='is_zero_rated', print_hide=1) ], From 2f65ab5355a86e3422b9fe81142d7aa84d24ccc9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Oct 2020 10:40:59 +0530 Subject: [PATCH 053/154] fix: Validatiion for loan write off amountt --- .../doctype/loan_write_off/loan_write_off.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py index 22fbe1ac57..823e6a904f 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py @@ -5,19 +5,28 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import getdate -from frappe.model.document import Document +from frappe.utils import getdate, flt from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries class LoanWriteOff(AccountsController): def validate(self): self.set_missing_values() + self.validate_write_off_amount() def set_missing_values(self): if not self.cost_center: self.cost_center = erpnext.get_default_cost_center(self.company) + def validate_write_off_amount(self): + total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, + ['total_payment', 'total_principal_paid','total_interest_payable', 'written_off_amount']) + + pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount) + + if self.write_off_amount > pending_principal_amount: + frappe.throw(_("Write off amount cannot be greater than pending principal amount")) + def on_submit(self): self.update_outstanding_amount() self.make_gl_entries() From 13cbda110d46bb171dc0162dd8e3bafb8f754f66 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Oct 2020 16:11:52 +0530 Subject: [PATCH 054/154] fix: Test Case --- .../doctype/loan_interest_accrual/test_loan_interest_accrual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py index 4b85b21869..5495d1c70d 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py @@ -57,4 +57,4 @@ class TestLoanInterestAccrual(unittest.TestCase): loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accural.interest_amount, 2), flt(accrued_interest_amount, 2)) + self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) From 8f9bab79a7128671191111cb4dfea95c5e588c5e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Oct 2020 19:03:13 +0530 Subject: [PATCH 055/154] fix: Update no copy fields --- erpnext/loan_management/doctype/loan/loan.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 312e9affb9..8a3b2fa029 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -76,6 +76,7 @@ "fieldname": "loan_application", "fieldtype": "Link", "label": "Loan Application", + "no_copy": 1, "options": "Loan Application" }, { @@ -149,7 +150,8 @@ "depends_on": "eval:doc.status==\"Disbursed\"", "fieldname": "disbursement_date", "fieldtype": "Date", - "label": "Disbursement Date" + "label": "Disbursement Date", + "no_copy": 1 }, { "depends_on": "is_term_loan", @@ -253,6 +255,7 @@ "fieldname": "total_payment", "fieldtype": "Currency", "label": "Total Payable Amount", + "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 }, @@ -266,6 +269,7 @@ "fieldname": "total_interest_payable", "fieldtype": "Currency", "label": "Total Interest Payable", + "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 }, @@ -274,6 +278,7 @@ "fieldname": "total_amount_paid", "fieldtype": "Currency", "label": "Total Amount Paid", + "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 }, @@ -314,6 +319,7 @@ "fieldname": "total_principal_paid", "fieldtype": "Currency", "label": "Total Principal Paid", + "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 }, @@ -321,6 +327,7 @@ "fieldname": "disbursed_amount", "fieldtype": "Currency", "label": "Disbursed Amount", + "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 }, @@ -329,6 +336,7 @@ "fieldname": "maximum_loan_amount", "fieldtype": "Currency", "label": "Maximum Loan Amount", + "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 }, @@ -336,13 +344,14 @@ "fieldname": "written_off_amount", "fieldtype": "Currency", "label": "Written Off Amount", + "no_copy": 1, "options": "Company:company:default_currency" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-17 10:35:44.361836", + "modified": "2020-10-21 09:12:26.809228", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From 3f177bffac645cb9218e4799835806b41487cdb9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Oct 2020 22:15:18 +0530 Subject: [PATCH 056/154] fix: Add test for loan top up --- .../loan_management/doctype/loan/test_loan.py | 10 +++-- .../test_loan_disbursement.py | 44 ++++++++++++++++++- .../test_loan_interest_accrual.py | 2 +- .../doctype/loan_repayment/loan_repayment.py | 8 ++-- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index c38541196f..1634697939 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -320,7 +320,7 @@ class TestLoan(unittest.TestCase): amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6)) self.assertTrue(amounts['pending_principal_amount'] < 0) - self.assertTrue(amounts['payable_principal_amount'] < 0) + self.assertEquals(amounts['payable_principal_amount'], 0.0) self.assertEqual(amounts['interest_amount'], 0) def test_disbursal_check_with_shortfall(self): @@ -706,7 +706,7 @@ def create_loan_security(): "haircut": 50.00, }).insert(ignore_permissions=True) -def create_loan_security_pledge(applicant, pledges, loan_application): +def create_loan_security_pledge(applicant, pledges, loan_application=None, loan=None): lsp = frappe.new_doc("Loan Security Pledge") lsp.applicant_type = 'Customer' @@ -714,11 +714,13 @@ def create_loan_security_pledge(applicant, pledges, loan_application): lsp.company = "_Test Company" lsp.loan_application = loan_application + if loan: + lsp.loan = loan + for pledge in pledges: lsp.append('securities', { "loan_security": pledge['loan_security'], - "qty": pledge['qty'], - "haircut": pledge['haircut'] + "qty": pledge['qty'] }) lsp.save() diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index 3ade5c549b..aaaeea8c4e 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -8,9 +8,10 @@ from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_la from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_repayment_entry, create_loan_application, make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_security_price) from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year, get_per_day_interest from erpnext.selling.doctype.customer.test_customer import get_customer_dict from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts class TestLoanDisbursement(unittest.TestCase): @@ -68,3 +69,44 @@ class TestLoanDisbursement(unittest.TestCase): # After repayment loan disbursement entry should go through make_loan_disbursement_entry(loan.name, 500000, disbursement_date=add_days(last_date, 16)) + def test_loan_topup_with_additional_pledge(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + first_date = '2019-10-01' + last_date = '2019-10-30' + + # Disbursed 10,00,000 amount + make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) + process_loan_interest_accrual_for_demand_loans(posting_date = last_date) + amounts = calculate_amounts(loan.name, add_days(last_date, 1)) + + previous_interest = amounts['interest_amount'] + + pledge1 = [{ + "loan_security": "Test Security 1", + "qty": 2000.00 + }] + + create_loan_security_pledge(self.applicant, pledge1, loan=loan.name) + + # Topup 500000 + make_loan_disbursement_entry(loan.name, 500000, disbursement_date=add_days(last_date, 1)) + process_loan_interest_accrual_for_demand_loans(posting_date = add_days(last_date, 15)) + amounts = calculate_amounts(loan.name, add_days(last_date, 15)) + + per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30') + interest = per_day_interest * 15 + + self.assertEquals(amounts['pending_principal_amount'], 1500000) + self.assertEquals(amounts['interest_amount'], flt(interest + previous_interest, 2)) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py index 5495d1c70d..46a6440553 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date) -from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_loan_security_price, +from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_price, make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_application) from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index de5ba8fcd5..6b3fba41c8 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -357,8 +357,8 @@ def get_amounts(amounts, against_loan, posting_date): pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 if pending_days > 0: - payable_principal_amount = flt(pending_principal_amount, precision) - per_day_interest = get_per_day_interest(payable_principal_amount, loan_type_details.rate_of_interest, posting_date) + principal_amount = flt(pending_principal_amount, precision) + per_day_interest = get_per_day_interest(principal_amount, loan_type_details.rate_of_interest, posting_date) unaccrued_interest += (pending_days * flt(per_day_interest, precision)) amounts["pending_principal_amount"] = flt(pending_principal_amount, precision) @@ -389,9 +389,11 @@ def calculate_amounts(against_loan, posting_date, payment_type=''): amounts = get_amounts(amounts, against_loan, posting_date) + # update values for closure if payment_type == 'Loan Closure': - amounts['payable_amount'] += amounts['unaccrued_interest'] + amounts['payable_principal_amount'] = amounts['pending_principal_amount'] amounts['interest_amount'] += amounts['unaccrued_interest'] + amounts['payable_amount'] = amounts['payable_principal_amount'] + amounts['interest_amount'] return amounts From 2c97244bada37ce793c466ed9bfef75910f5fda7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Oct 2020 09:34:27 +0530 Subject: [PATCH 057/154] fix: Test Cases --- .../doctype/loan_security_pledge/loan_security_pledge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index 2bb6fd84e5..cbc8376aa5 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -78,7 +78,7 @@ class LoanSecurityPledge(Document): self.maximum_loan_value = maximum_loan_value def update_loan(loan, maximum_value_against_pledge): - maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_value']) + maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_amount']) - frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_value=%s, is_secured_loan=1 + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) From cd2c90451e38716dce9db3e802c60c971a001381 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Oct 2020 21:50:06 +0530 Subject: [PATCH 058/154] fix: Unaccrued interest after disbursal --- erpnext/loan_management/doctype/loan/loan.json | 5 ++--- .../loan_interest_accrual/loan_interest_accrual.py | 8 ++++---- .../doctype/loan_repayment/loan_repayment.py | 7 ++++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 8a3b2fa029..b613d22827 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -295,8 +295,7 @@ "default": "0", "fieldname": "is_secured_loan", "fieldtype": "Check", - "label": "Is Secured Loan", - "read_only": 1 + "label": "Is Secured Loan" }, { "default": "0", @@ -351,7 +350,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-21 09:12:26.809228", + "modified": "2020-10-22 11:03:43.697394", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index e31b844953..1fc41f9ea5 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -210,21 +210,21 @@ def make_loan_interest_accrual_entry(args): def get_no_of_days_for_interest_accural(loan, posting_date): - last_interest_accrual_date = get_last_accural_date_in_current_month(loan) + last_interest_accrual_date = get_last_accural_date(loan.name) no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1 return no_of_days -def get_last_accural_date_in_current_month(loan): +def get_last_accural_date(loan): last_posting_date = frappe.db.sql(""" SELECT MAX(posting_date) from `tabLoan Interest Accrual` - WHERE loan = %s""", (loan.name)) + WHERE loan = %s""", (loan)) if last_posting_date[0][0]: # interest for last interest accrual date is already booked, so add 1 day return add_days(last_posting_date[0][0], 1) else: - return loan.disbursement_date + return frappe.db.get_value('Loan', loan, 'disbursement_date') def days_in_year(year): days = 365 diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 6b3fba41c8..12d81d3a24 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -14,7 +14,7 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest, get_last_accural_date class LoanRepayment(AccountsController): @@ -76,14 +76,15 @@ class LoanRepayment(AccountsController): if self.total_interest_paid > self.interest_payable: if not self.is_term_loan: # get last loan interest accrual date - last_accrual_date = frappe.get_value('Loan Interest Accrual', {'loan': self.against_loan}, 'MAX(posting_date)') + last_accrual_date = get_last_accural_date(self.against_loan) # get posting date upto which interest has to be accrued per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, self.rate_of_interest, self.posting_date), 2) no_of_days = flt(flt(self.total_interest_paid - self.interest_payable, - precision)/per_day_interest, 0) + precision)/per_day_interest, 0) - 1 + posting_date = add_days(last_accrual_date, no_of_days) From 2b3f8e0c3b5bab732ec452675a5b21fc72f5404a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 23 Oct 2020 19:02:24 +0530 Subject: [PATCH 059/154] fix: Cancel repayment accrual interest entry on payment cancellation --- .../doctype/loan_repayment/loan_repayment.py | 15 +++++++++++++-- .../loan_repayment_detail.json | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 12d81d3a24..b973cd69e6 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -24,8 +24,10 @@ class LoanRepayment(AccountsController): self.validate_amount() self.allocate_amounts(amounts) - def on_submit(self): + def before_submit(self): self.book_unaccrued_interest() + + def on_submit(self): self.update_paid_amount() self.make_gl_entries() @@ -99,7 +101,8 @@ class LoanRepayment(AccountsController): self.append('repayment_details', { 'loan_interest_accrual': lia.name, 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), - 'paid_principal_amount': 0.0 + 'paid_principal_amount': 0.0, + 'accrual_type': 'Repayment' }) def update_paid_amount(self): @@ -123,6 +126,8 @@ class LoanRepayment(AccountsController): def mark_as_unpaid(self): loan = frappe.get_doc("Loan", self.against_loan) + no_of_repayments = len(self.repayment_details) + for payment in self.repayment_details: frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` SET paid_principal_amount = `paid_principal_amount` - %s, @@ -130,6 +135,12 @@ class LoanRepayment(AccountsController): WHERE name = %s""", (payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual)) + # Cancel repayment interest accrual + # checking idx as a preventive measure, repayment accrual will always be the last entry + if payment.accrual_type == 'Repayment' and payment.idx == no_of_repayments: + lia_doc = frappe.get_doc('Loan Interest Accrual', payment.loan_interest_accrual) + lia_doc.cancel() + frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid - self.amount_paid, loan.total_principal_paid - self.principal_amount_paid, self.against_loan)) diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json index cff1dbb1d2..4b9b191e26 100644 --- a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json +++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json @@ -7,7 +7,8 @@ "field_order": [ "loan_interest_accrual", "paid_principal_amount", - "paid_interest_amount" + "paid_interest_amount", + "accrual_type" ], "fields": [ { @@ -27,11 +28,20 @@ "fieldtype": "Currency", "label": "Paid Interest Amount", "options": "Company:company:default_currency" + }, + { + "fetch_from": "loan_interest_accrual.accrual_type", + "fetch_if_empty": 1, + "fieldname": "accrual_type", + "fieldtype": "Select", + "label": "Accrual Type", + "options": "Regular\nRepayment\nDisbursement" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-04-15 21:50:03.837019", + "modified": "2020-10-23 08:09:18.267030", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment Detail", From 0ac40c75aabbbe2ee31dfd0b4f4881e9846dcf6d Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Sun, 25 Oct 2020 01:16:06 +0530 Subject: [PATCH 060/154] chore(UAE VAT): update tesr dependencies --- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 68c163d5bc..05bb701dc2 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -21,7 +21,7 @@ from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( get_reverse_charge_recoverable_tax ) - +test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] class TestUaeVat201(TestCase): def setUp(self): From 0ceae0bd66e7bbd1f8af8f4b20bc50450fbc2202 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Sun, 25 Oct 2020 13:06:42 +0530 Subject: [PATCH 061/154] feat(UAE VAT 201): emirate customizable --- .../report/uae_vat_201/test_uae_vat_201.py | 79 +++++-------------- .../regional/united_arab_emirates/setup.py | 10 +-- 2 files changed, 23 insertions(+), 66 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 05bb701dc2..ceaa3b3dcb 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -34,13 +34,12 @@ class TestUaeVat201(TestCase): make_company("_Test Company UAE VAT", "_TCUV") set_vat_accounts() - make_customers() + make_customer() make_supplier() create_warehouse("_Test UAE VAT Supplier Warehouse", company="_Test Company UAE VAT") - make_item("_Test UAE VAT Item", properties = {"is_zero_rated": 0, "is_exempt": 0}) make_item("_Test UAE VAT Zero Rated Item", properties = {"is_zero_rated": 1, "is_exempt": 0}) make_item("_Test UAE VAT Exempt Item", properties = {"is_zero_rated": 0, "is_exempt": 1}) @@ -122,64 +121,16 @@ def set_vat_accounts(): "doctype": "UAE VAT Settings", }).insert() -def make_customers(): - if not frappe.db.exists("Customer", "_Test Dubai Customer"): +def make_customer(): + if not frappe.db.exists("Customer", "_Test UAE Customer"): customer = frappe.get_doc({ "doctype": "Customer", - "customer_name": "_Test Dubai Customer", + "customer_name": "_Test UAE Customer", "customer_type": "Company", }) customer.insert() else: - customer = frappe.get_doc("Customer", "_Test Dubai Customer") - - if not frappe.db.exists("Customer", "_Test Sharjah Customer"): - customer = frappe.get_doc({ - "doctype": "Customer", - "customer_name": "_Test Sharjah Customer", - "customer_type": "Company", - }) - customer.insert() - else: - customer = frappe.get_doc("Customer", "_Test Sharjah Customer") - - if not frappe.db.exists('Address', '_Test Dubai Address'): - address = frappe.get_doc({ - "address_line1": "_Test Address Line 1", - "address_title": "_Test Dubai Address", - "address_type": "Billing", - "city": "_Test City", - "state": "Test State", - "country": "United Arab Emirates", - "doctype": "Address", - "emirate": "Dubai" - }).insert() - - address.append("links", { - "link_doctype": "Customer", - "link_name": "_Test Dubai Customer" - }) - - address.save() - - if not frappe.db.exists('Address', '_Test Sharjah Address'): - address = frappe.get_doc({ - "address_line1": "_Test Address Line 1", - "address_title": "_Test Sharjah Address", - "address_type": "Billing", - "city": "_Test City", - "state": "Test State", - "country": "United Arab Emirates", - "doctype": "Address", - "emirate": "Sharjah" - }).insert() - - address.append("links", { - "link_doctype": "Customer", - "link_name": "_Test Sharjah Customer" - }) - - address.save() + customer = frappe.get_doc("Customer", "_Test UAE Customer") def make_supplier(): @@ -230,7 +181,7 @@ def make_item(item_code, properties=None): def make_sales_invoices(): si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test Dubai Customer', + customer = '_Test UAE Customer', currency = 'AED', warehouse = 'Finished Goods - _TCUV', debit_to = 'Debtors - _TCUV', @@ -248,10 +199,11 @@ def make_sales_invoices(): "description": "VAT 5% @ 5.0", "rate": 5.0 }) + si.emirate = 'Dubai' si.submit() si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test Sharjah Customer', + customer = '_Test UAE Customer', currency = 'AED', warehouse = 'Finished Goods - _TCUV', debit_to = 'Debtors - _TCUV', @@ -262,6 +214,7 @@ def make_sales_invoices(): item = "_Test UAE VAT Item", do_not_save=1 ) + si.emirate = 'Sharjah' si.append("taxes", { "charge_type": "On Net Total", "account_head": "VAT 5% - _TCUV", @@ -272,7 +225,7 @@ def make_sales_invoices(): si.submit() si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test Dubai Customer', + customer = '_Test UAE Customer', currency = 'AED', warehouse = 'Finished Goods - _TCUV', debit_to = 'Debtors - _TCUV', @@ -286,6 +239,8 @@ def make_sales_invoices(): si.tourist_tax_return = 2 + si.emirate = 'Dubai' + si.append("taxes", { "charge_type": "On Net Total", "account_head": "VAT 5% - _TCUV", @@ -296,7 +251,7 @@ def make_sales_invoices(): si.submit() si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test Sharjah Customer', + customer = '_Test UAE Customer', currency = 'AED', warehouse = 'Finished Goods - _TCUV', debit_to = 'Debtors - _TCUV', @@ -305,10 +260,13 @@ def make_sales_invoices(): cost_center = 'Main - _TCUV', sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", item = "_Test UAE VAT Zero Rated Item", + do_not_save=1 ) + si.emirate = 'Sharjah' + si.submit() si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test Sharjah Customer', + customer = '_Test UAE Customer', currency = 'AED', warehouse = 'Finished Goods - _TCUV', debit_to = 'Debtors - _TCUV', @@ -317,7 +275,10 @@ def make_sales_invoices(): cost_center = 'Main - _TCUV', sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", item = "_Test UAE VAT Exempt Item", + do_not_save=1 ) + si.emirate = 'Sharjah' + si.submit() def create_purchase_invoices(): diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index fac6e270db..7f890d6867 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -56,10 +56,10 @@ def make_custom_fields(): dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), - dict(fieldname='emirate', label='Emirate', insert_after='customer_address', - fieldtype='Read Only', fetch_from='customer_address.emirate'), + dict(fieldname='emirate', label='Emirate', insert_after='permit_no', fieldtype='Select', + options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', - insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), + insert_after='emirate', fieldtype='Currency', print_hide=1, default='0'), ] invoice_item_fields = [ @@ -101,10 +101,6 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Data', insert_after='supplier_name'), ], - 'Address': [ - dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', - options='Abu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') - ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, 'Purchase Order': purchase_invoice_fields + invoice_fields, 'Purchase Receipt': purchase_invoice_fields + invoice_fields, From 5953acab8bb4a9efbbca7649db62d3fce38779f0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Sun, 25 Oct 2020 15:43:21 +0530 Subject: [PATCH 062/154] chore(UAE VAT 201): cleanup --- .../report/uae_vat_201/test_uae_vat_201.py | 40 +------------------ .../regional/united_arab_emirates/setup.py | 3 +- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index ceaa3b3dcb..3b5d388e25 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -11,14 +11,10 @@ from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( get_total_emiratewise, get_tourist_tax_return_total, get_tourist_tax_return_tax, - get_reverse_charge_total, - get_reverse_charge_tax, get_zero_rated_total, get_exempt_total, get_standard_rated_expenses_total, get_standard_rated_expenses_tax, - get_reverse_charge_recoverable_total, - get_reverse_charge_recoverable_tax ) test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] @@ -62,17 +58,12 @@ class TestUaeVat201(TestCase): self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"],5) self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200) self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"],10) - self.assertEqual(get_tourist_tax_return_total(filters),100) self.assertEqual(get_tourist_tax_return_tax(filters),2) - self.assertEqual(get_reverse_charge_total(filters),250) - self.assertEqual(get_reverse_charge_tax(filters),12.5) self.assertEqual(get_zero_rated_total(filters),100) self.assertEqual(get_exempt_total(filters),100) self.assertEqual(get_standard_rated_expenses_total(filters),250) self.assertEqual(get_standard_rated_expenses_tax(filters),1) - self.assertEqual(get_reverse_charge_recoverable_total(filters),250) - self.assertEqual(get_reverse_charge_recoverable_tax(filters),12.5) def make_company(company_name, abbr): if not frappe.db.exists("Company", company_name): @@ -304,33 +295,4 @@ def create_purchase_invoices(): pi.recoverable_standard_rated_expenses = 1 - pi.submit() - - pi = make_purchase_invoice( - company="_Test Company UAE VAT", - supplier = '_Test UAE Supplier', - supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', - warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', - currency = 'AED', - cost_center = 'Main - _TCUV', - expense_account = 'Cost of Goods Sold - _TCUV', - item = "_Test UAE VAT Item", - do_not_save=1, - uom = "Nos" - ) - - pi.append("taxes", { - "charge_type": "On Net Total", - "account_head": "VAT 5% - _TCUV", - "cost_center": "Main - _TCUV", - "description": "VAT 5% @ 5.0", - "rate": 5.0 - }) - - pi.reverse_charge = "Y" - - pi.recoverable_reverse_charge = 100 - - pi.submit() - - + pi.submit() \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 7f890d6867..c1188d6ccd 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -39,8 +39,7 @@ def make_custom_fields(): fieldtype='Read Only', insert_after='supplier_name', fetch_from='supplier.supplier_name_in_arabic', print_hide=1), dict(fieldname='recoverable_standard_rated_expenses', label='Recoverable Standard Rated Expenses (AED)', - insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0', - depends_on="eval:doc.reverse_charge=='N'",), + insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), dict(fieldname='reverse_charge', label='Reverse Charge Applicable', fieldtype='Select', insert_after='recoverable_standard_rated_expenses', print_hide=1, options='Y\nN', default='N'), From d63fbd79f4f434e3a6d925832d114c9f2b930442 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Oct 2020 14:20:55 +0530 Subject: [PATCH 063/154] fix: Unaccrued interest from last accrual date instead of disbursement date --- .../loan_management/doctype/loan_repayment/loan_repayment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index b973cd69e6..c1e83d9305 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -310,6 +310,7 @@ def get_accrued_interest_entries(against_loan): payable_principal_amount - paid_principal_amount > 0) AND docstatus = 1 + ORDER BY posting_date """, (against_loan), as_dict=1) return unpaid_accrued_entries @@ -366,7 +367,8 @@ def get_amounts(amounts, against_loan, posting_date): if due_date: pending_days = date_diff(posting_date, due_date) + 1 else: - pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 + last_accrual_date = get_last_accural_date(against_loan_doc.name) + pending_days = date_diff(posting_date, last_accrual_date) + 1 if pending_days > 0: principal_amount = flt(pending_principal_amount, precision) From 5b4742fd25043532cd9338ace81998c233c9a91d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Oct 2020 16:56:05 +0530 Subject: [PATCH 064/154] fix: Permission fixes for some doctypes --- .../doctype/loan_type/loan_type.json | 4 +++- .../doctype/loan_write_off/loan_write_off.json | 18 +++++++++++++++++- .../process_loan_interest_accrual.json | 6 +++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index 5d9232d711..18a97315f0 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -154,13 +154,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-17 11:41:17.907683", + "modified": "2020-10-26 07:13:55.029811", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", "owner": "Administrator", "permissions": [ { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -170,6 +171,7 @@ "report": 1, "role": "Loan Manager", "share": 1, + "submit": 1, "write": 1 }, { diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json index 64623c4b3a..4617a62f5b 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json @@ -116,13 +116,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-17 08:30:54.859362", + "modified": "2020-10-26 07:13:43.663924", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Write Off", "owner": "Administrator", "permissions": [ { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -132,6 +133,21 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index bb781b1d56..c1296f759f 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -59,13 +59,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-11 11:19:00.531046", + "modified": "2020-10-26 07:14:31.491249", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", "owner": "Administrator", "permissions": [ { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -75,9 +76,11 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 }, { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -87,6 +90,7 @@ "report": 1, "role": "Loan Manager", "share": 1, + "submit": 1, "write": 1 } ], From 3f971b23716205351ee45960ee448462ca392a6e Mon Sep 17 00:00:00 2001 From: Afshan Date: Mon, 26 Oct 2020 16:59:57 +0530 Subject: [PATCH 065/154] fix: copying po no when mapping doc --- .../doctype/sales_invoice/sales_invoice.py | 1 + erpnext/controllers/selling_controller.py | 40 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4b598877d9..af6c6968dc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1401,6 +1401,7 @@ def make_delivery_note(source_name, target_doc=None): def set_missing_values(source, target): target.ignore_pricing_rule = 1 target.run_method("set_missing_values") + target.run_method("set_po_nos") target.run_method("calculate_taxes_and_totals") def update_item(source_doc, target_doc, source_parent): diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 58861715c2..0fbd914fdf 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -371,13 +371,39 @@ class SellingController(StockController): self.make_sl_entries(sl_entries) def set_po_nos(self): - if self.doctype in ("Delivery Note", "Sales Invoice") and hasattr(self, "items"): - ref_fieldname = "against_sales_order" if self.doctype == "Delivery Note" else "sales_order" - sales_orders = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)])) - if sales_orders: - po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', sales_orders)}) - if po_nos and po_nos[0].get('po_no'): - self.po_no = ', '.join(list(set([d.po_no for d in po_nos if d.po_no]))) + self.po_no = '' + if self.doctype == 'Sales Invoice' and hasattr(self, "items"): + self.set_pos_for_sales_invoice() + if self.doctype == 'Delivery Note' and hasattr(self, "items"): + self.set_pos_for_delivery_note() + + def set_pos_for_sales_invoice(self): + ref_fieldname1 = "sales_order" + ref_fieldname2 = "delivery_note" + sales_orders = list(set([d.get(ref_fieldname1) for d in self.items if d.get(ref_fieldname1)])) + if sales_orders: + so_po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', sales_orders)}) + if so_po_nos and so_po_nos[0].get('po_no'): + self.po_no += ', '.join(list(set([d.po_no for d in so_po_nos if d.po_no]))) + delivery_notes = list(set([d.get(ref_fieldname2) for d in self.items if d.get(ref_fieldname2)])) + if delivery_notes: + dn_po_nos = frappe.get_all('Delivery Note', 'po_no', filters = {'name': ('in', delivery_notes)}) + if dn_po_nos and dn_po_nos[0].get('po_no'): + self.po_no += ', '.join(list(set([d.po_no for d in dn_po_nos if d.po_no]))) + + def set_pos_for_delivery_note(self): + ref_fieldname1 = "against_sales_order" + ref_fieldname2 = "against_sales_invoice" + sales_orders = list(set([d.get(ref_fieldname1) for d in self.items if d.get(ref_fieldname1)])) + sales_invoices = list(set([d.get(ref_fieldname2) for d in self.items if d.get(ref_fieldname2)])) + if sales_orders: + so_po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', sales_orders)}) + if so_po_nos and so_po_nos[0].get('po_no'): + self.po_no += ', '.join(list(set([d.po_no for d in so_po_nos if d.po_no]))) + if sales_invoices: + si_po_nos = frappe.get_all('Sales Invoice', 'po_no', filters = {'name': ('in', sales_invoices)}) + if si_po_nos and si_po_nos[0].get('po_no'): + self.po_no += ', '.join(list(set([d.po_no for d in si_po_nos if d.po_no]))) def set_gross_profit(self): if self.doctype in ["Sales Order", "Quotation"]: From a630de56e9d38aa5ed253fb349aa70957e8d8cd4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Oct 2020 18:53:56 +0530 Subject: [PATCH 066/154] fix: Loan disbursement amount validation check --- .../loan_disbursement/loan_disbursement.py | 107 ++++++++++-------- .../doctype/loan_security/loan_security.json | 4 +- 2 files changed, 64 insertions(+), 47 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index bfdc8b403e..f58b989a20 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -17,6 +17,7 @@ class LoanDisbursement(AccountsController): def validate(self): self.set_missing_values() + self.validate_disbursal_amount() def on_submit(self): self.set_status_and_amounts() @@ -40,57 +41,21 @@ class LoanDisbursement(AccountsController): if not self.bank_account and self.applicant_type == "Customer": self.bank_account = frappe.db.get_value("Customer", self.applicant, "default_bank_account") - def set_status_and_amounts(self, cancel=0): + def validate_disbursal_amount(self): + possible_disbursal_amount = get_disbursal_amount(self.against_loan) + if self.disbursed_amount > possible_disbursal_amount: + frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount)) + + def set_status_and_amounts(self, cancel=0): loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment", "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"], filters= { "name": self.against_loan })[0] if cancel: - disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount - total_payment = loan_details.total_payment - - if loan_details.disbursed_amount > loan_details.loan_amount: - topup_amount = loan_details.disbursed_amount - loan_details.loan_amount - if topup_amount > self.disbursed_amount: - topup_amount = self.disbursed_amount - - total_payment = total_payment - topup_amount - - if disbursed_amount == 0: - status = "Sanctioned" - elif disbursed_amount >= loan_details.loan_amount: - status = "Disbursed" - else: - status = "Partially Disbursed" + disbursed_amount, status, total_payment = self.get_values_on_cancel(loan_details) else: - disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount - total_payment = loan_details.total_payment - - possible_disbursal_amount = get_disbursal_amount(self.against_loan) - - if self.disbursed_amount > possible_disbursal_amount: - frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount)) - - if loan_details.status == "Disbursed" and not loan_details.is_term_loan: - process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1), - loan=self.against_loan, accrual_type="Disbursement") - - if disbursed_amount > loan_details.loan_amount: - topup_amount = disbursed_amount - loan_details.loan_amount - - if topup_amount < 0: - topup_amount = 0 - - if topup_amount > self.disbursed_amount: - topup_amount = self.disbursed_amount - - total_payment = total_payment + topup_amount - - if flt(disbursed_amount) >= loan_details.loan_amount: - status = "Disbursed" - else: - status = "Partially Disbursed" + disbursed_amount, status, total_payment = self.get_values_on_submit(loan_details) frappe.db.set_value("Loan", self.against_loan, { "disbursement_date": self.disbursement_date, @@ -99,6 +64,54 @@ class LoanDisbursement(AccountsController): "total_payment": total_payment }) + def get_values_on_cancel(self, loan_details): + disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount + total_payment = loan_details.total_payment + + if loan_details.disbursed_amount > loan_details.loan_amount: + topup_amount = loan_details.disbursed_amount - loan_details.loan_amount + if topup_amount > self.disbursed_amount: + topup_amount = self.disbursed_amount + + total_payment = total_payment - topup_amount + + if disbursed_amount == 0: + status = "Sanctioned" + total_payment = loan_details.loan_amount + elif disbursed_amount >= loan_details.loan_amount: + status = "Disbursed" + else: + status = "Partially Disbursed" + + return disbursed_amount, status, total_payment + + def get_values_on_submit(self, loan_details): + disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount + total_payment = loan_details.total_payment + + if loan_details.status == "Disbursed" and not loan_details.is_term_loan: + process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1), + loan=self.against_loan, accrual_type="Disbursement") + + if disbursed_amount > loan_details.loan_amount: + topup_amount = disbursed_amount - loan_details.loan_amount + + if topup_amount < 0: + topup_amount = 0 + + if topup_amount > self.disbursed_amount: + topup_amount = self.disbursed_amount + + total_payment = total_payment + topup_amount + + if flt(disbursed_amount) >= loan_details.loan_amount: + status = "Disbursed" + else: + status = "Partially Disbursed" + total_payment = disbursed_amount + + return disbursed_amount, status, total_payment + def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] loan_details = frappe.get_doc("Loan", self.against_loan) @@ -155,7 +168,8 @@ def get_total_pledged_security_value(loan): pledged_securities = get_pledged_security_qty(loan) for security, qty in pledged_securities.items(): - security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100 + after_haircut_percentage = 100 - hair_cut_map.get(security) + security_value += (loan_security_price_map.get(security) * qty * after_haircut_percentage)/100 return security_value @@ -173,7 +187,8 @@ def get_disbursal_amount(loan): pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \ - flt(loan_details.total_principal_paid) else: - pending_principal_amount = flt(loan_details.disbursed_amount) + pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \ + - flt(loan_details.total_principal_paid) security_value = 0.0 if loan_details.is_secured_loan: diff --git a/erpnext/loan_management/doctype/loan_security/loan_security.json b/erpnext/loan_management/doctype/loan_security/loan_security.json index 1d0bb30910..c698601ea4 100644 --- a/erpnext/loan_management/doctype/loan_security/loan_security.json +++ b/erpnext/loan_management/doctype/loan_security/loan_security.json @@ -25,6 +25,7 @@ }, { "fetch_from": "loan_security_type.haircut", + "fetch_if_empty": 1, "fieldname": "haircut", "fieldtype": "Percent", "label": "Haircut %" @@ -64,8 +65,9 @@ "reqd": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-29 13:21:26.043492", + "modified": "2020-10-26 07:34:48.601766", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security", From 13f4b3fc17f785176a006aea8592dcee588da5dd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Oct 2020 19:01:36 +0530 Subject: [PATCH 067/154] fix: Translation syntax --- .../loan_security_unpledge/loan_security_unpledge.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index d0d25e8897..5d4447bf2b 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -51,9 +51,11 @@ class LoanSecurityUnpledge(Document): for security in self.securities: pledged_qty = pledge_qty_map.get(security.loan_security, 0) if security.qty > pledged_qty: - frappe.throw(_("""Row {0}: {1} {2} of {3} is pledged against Loan {4}. - You are trying to unpledge more""").format(security.idx, pledged_qty, security.uom, - frappe.bold(security.loan_security), frappe.bold(self.loan))) + msg = _("Row {0}: {1} {2} of {3} is pledged against Loan {4}.").format(security.idx, pledged_qty, security.uom, + frappe.bold(security.loan_security), frappe.bold(self.loan)) + msg += "
" + msg += _("You are trying to unpledge more.") + frappe.throw(msg, title=_("Loan Security Unpledge Error")) qty_after_unpledge = pledged_qty - security.qty ltv_ratio = ltv_ratio_map.get(security.loan_security_type) From 0a6e1b35030e3fc3574d568531c02fbd6f36dd1c Mon Sep 17 00:00:00 2001 From: Afshan Date: Mon, 26 Oct 2020 21:23:31 +0530 Subject: [PATCH 068/154] fix: refactor --- erpnext/controllers/selling_controller.py | 40 +++++++++-------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 0fbd914fdf..e5405b2e43 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -371,39 +371,29 @@ class SellingController(StockController): self.make_sl_entries(sl_entries) def set_po_nos(self): - self.po_no = '' if self.doctype == 'Sales Invoice' and hasattr(self, "items"): self.set_pos_for_sales_invoice() if self.doctype == 'Delivery Note' and hasattr(self, "items"): self.set_pos_for_delivery_note() def set_pos_for_sales_invoice(self): - ref_fieldname1 = "sales_order" - ref_fieldname2 = "delivery_note" - sales_orders = list(set([d.get(ref_fieldname1) for d in self.items if d.get(ref_fieldname1)])) - if sales_orders: - so_po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', sales_orders)}) - if so_po_nos and so_po_nos[0].get('po_no'): - self.po_no += ', '.join(list(set([d.po_no for d in so_po_nos if d.po_no]))) - delivery_notes = list(set([d.get(ref_fieldname2) for d in self.items if d.get(ref_fieldname2)])) - if delivery_notes: - dn_po_nos = frappe.get_all('Delivery Note', 'po_no', filters = {'name': ('in', delivery_notes)}) - if dn_po_nos and dn_po_nos[0].get('po_no'): - self.po_no += ', '.join(list(set([d.po_no for d in dn_po_nos if d.po_no]))) + po_nos = [] + self.get_po_nos('Sales Order', 'sales_order', po_nos) + self.get_po_nos('Delivery Note', 'delivery_note', po_nos) + self.po_no = ', '.join(list(set(po_nos))) def set_pos_for_delivery_note(self): - ref_fieldname1 = "against_sales_order" - ref_fieldname2 = "against_sales_invoice" - sales_orders = list(set([d.get(ref_fieldname1) for d in self.items if d.get(ref_fieldname1)])) - sales_invoices = list(set([d.get(ref_fieldname2) for d in self.items if d.get(ref_fieldname2)])) - if sales_orders: - so_po_nos = frappe.get_all('Sales Order', 'po_no', filters = {'name': ('in', sales_orders)}) - if so_po_nos and so_po_nos[0].get('po_no'): - self.po_no += ', '.join(list(set([d.po_no for d in so_po_nos if d.po_no]))) - if sales_invoices: - si_po_nos = frappe.get_all('Sales Invoice', 'po_no', filters = {'name': ('in', sales_invoices)}) - if si_po_nos and si_po_nos[0].get('po_no'): - self.po_no += ', '.join(list(set([d.po_no for d in si_po_nos if d.po_no]))) + po_nos = [] + self.get_po_nos('Sales Order', 'against_sales_order', po_nos) + self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos) + self.po_no = ', '.join(list(set(po_nos))) + + def get_po_nos(self, ref_doctype, ref_fieldname, po_nos): + doc_list = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)])) + if doc_list: + po_no_list = frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) + if po_no_list and po_no_list[0].get('po_no'): + po_nos += [d.po_no for d in po_no_list if d.po_no] def set_gross_profit(self): if self.doctype in ["Sales Order", "Quotation"]: From fa085d7f4bcb163ce02b44db3bc07cd26f19280a Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 27 Oct 2020 13:18:30 +0530 Subject: [PATCH 069/154] fix: Payment Terms not fetched in Purchase Invoice from Purchase Receipt --- erpnext/accounts/party.py | 6 +++--- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e8d3cd322b..64268b8064 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -59,7 +59,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= billing_address=party_address, shipping_address=shipping_address) if fetch_payment_terms_template: - party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) + party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company) if not party_details.get("currency"): party_details["currency"] = currency @@ -315,7 +315,7 @@ def get_due_date(posting_date, party_type, party, company=None, bill_date=None): due_date = None if (bill_date or posting_date) and party: due_date = bill_date or posting_date - template_name = get_pyt_term_template(party, party_type, company) + template_name = get_payment_terms_template(party, party_type, company) if template_name: due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d") @@ -422,7 +422,7 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup @frappe.whitelist() -def get_pyt_term_template(party_name, party_type, company=None): +def get_payment_terms_template(party_name, party_type, company=None): if party_type not in ("Customer", "Supplier"): return template = None diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index faa9dd944b..d964669830 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -533,6 +533,8 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True): @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc + from erpnext.accounts.party import get_payment_terms_template + doc = frappe.get_doc('Purchase Receipt', source_name) returned_qty_map = get_returned_qty_map(source_name) invoiced_qty_map = get_invoiced_qty_map(source_name) @@ -543,6 +545,7 @@ def make_purchase_invoice(source_name, target_doc=None): doc = frappe.get_doc(target) doc.ignore_pricing_rule = 1 + doc.payment_terms_template = get_payment_terms_template(source.supplier, "Supplier", source.company) doc.run_method("onload") doc.run_method("set_missing_values") doc.run_method("calculate_taxes_and_totals") From 67f2c82d907d2aae96287352b9e5112e794eb26e Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 27 Oct 2020 14:49:43 +0530 Subject: [PATCH 070/154] chore: Test case for Payment Terms in PI from PR --- .../purchase_receipt/test_purchase_receipt.py | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index a1e01bf7ce..253edb02c3 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -42,6 +42,30 @@ class TestPurchaseReceipt(unittest.TestCase): frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1) def test_make_purchase_invoice(self): + if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'): + frappe.get_doc({ + 'doctype': 'Payment Terms Template', + 'template_name': '_Test Payment Terms Template For Purchase Invoice', + 'allocate_payment_based_on_payment_terms': 1, + 'terms': [ + { + 'doctype': 'Payment Terms Template Detail', + 'invoice_portion': 50.00, + 'credit_days_based_on': 'Day(s) after invoice date', + 'credit_days': 00 + }, + { + 'doctype': 'Payment Terms Template Detail', + 'invoice_portion': 50.00, + 'credit_days_based_on': 'Day(s) after invoice date', + 'credit_days': 30 + }] + }).insert() + + template = frappe.db.get_value('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice') + old_template_in_supplier = frappe.db.get_value("Supplier", "_Test Supplier", "payment_terms") + frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", template) + pr = make_purchase_receipt(do_not_save=True) self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name) pr.submit() @@ -51,10 +75,23 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pi.doctype, "Purchase Invoice") self.assertEqual(len(pi.get("items")), len(pr.get("items"))) - # modify rate + # test maintaining same rate throughout purchade cycle pi.get("items")[0].rate = 200 self.assertRaises(frappe.ValidationError, frappe.get_doc(pi).submit) + # test if payment terms are fetched and set in PI + self.assertEqual(pi.payment_terms_template, template) + self.assertEqual(pi.payment_schedule[0].payment_amount, flt(pi.grand_total)/2) + self.assertEqual(pi.payment_schedule[0].invoice_portion, 50) + self.assertEqual(pi.payment_schedule[1].payment_amount, flt(pi.grand_total)/2) + self.assertEqual(pi.payment_schedule[1].invoice_portion, 50) + + # teardown + pi.delete() # draft PI + pr.cancel() + frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", old_template_in_supplier) + frappe.get_doc('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice').delete() + def test_purchase_receipt_no_gl_entry(self): company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company') From 81f2efd9b04b307af66b45cf69ca8a06021617f5 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 28 Oct 2020 10:28:25 +0530 Subject: [PATCH 071/154] chore(UAE VAT 201): Rename emirate to vat emirate --- .../regional/report/uae_vat_201/test_uae_vat_201.py | 10 +++++----- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 4 ++-- erpnext/regional/united_arab_emirates/setup.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 3b5d388e25..3037a07698 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -190,7 +190,7 @@ def make_sales_invoices(): "description": "VAT 5% @ 5.0", "rate": 5.0 }) - si.emirate = 'Dubai' + si.vat_emirate = 'Dubai' si.submit() si = create_sales_invoice(company="_Test Company UAE VAT", @@ -205,7 +205,7 @@ def make_sales_invoices(): item = "_Test UAE VAT Item", do_not_save=1 ) - si.emirate = 'Sharjah' + si.vat_emirate = 'Sharjah' si.append("taxes", { "charge_type": "On Net Total", "account_head": "VAT 5% - _TCUV", @@ -230,7 +230,7 @@ def make_sales_invoices(): si.tourist_tax_return = 2 - si.emirate = 'Dubai' + si.vat_emirate = 'Dubai' si.append("taxes", { "charge_type": "On Net Total", @@ -253,7 +253,7 @@ def make_sales_invoices(): item = "_Test UAE VAT Zero Rated Item", do_not_save=1 ) - si.emirate = 'Sharjah' + si.vat_emirate = 'Sharjah' si.submit() si = create_sales_invoice(company="_Test Company UAE VAT", @@ -268,7 +268,7 @@ def make_sales_invoices(): item = "_Test UAE VAT Exempt Item", do_not_save=1 ) - si.emirate = 'Sharjah' + si.vat_emirate = 'Sharjah' si.submit() def create_purchase_invoices(): diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 5bed0e2463..ddbdf96c6a 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -147,9 +147,9 @@ def append_data(data, no, legend, amount, vat_amount): def get_total_emiratewise(filters): """Returns Emiratewise Amount and Taxes.""" return frappe.db.sql(f""" - select emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` + select vat_emirate as emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` where docstatus = 1 {get_conditions(filters)} - group by `tabSales Invoice`.emirate; + group by `tabSales Invoice`.vat_emirate; """, filters) def get_emirates(): diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index c1188d6ccd..62156e46be 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -55,10 +55,10 @@ def make_custom_fields(): dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), - dict(fieldname='emirate', label='Emirate', insert_after='permit_no', fieldtype='Select', + dict(fieldname='vat_emirate', label='VAT Emirate', insert_after='permit_no', fieldtype='Select', options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', - insert_after='emirate', fieldtype='Currency', print_hide=1, default='0'), + insert_after='vat_emirate', fieldtype='Currency', print_hide=1, default='0'), ] invoice_item_fields = [ From c9a6135d6cddc9480db89b26ccf38628571d937e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Oct 2020 13:38:15 +0530 Subject: [PATCH 072/154] fix: Interest accrual after loan topup --- erpnext/loan_management/doctype/loan/loan.json | 5 +++-- .../doctype/loan_disbursement/loan_disbursement.py | 8 +++++--- .../doctype/loan_disbursement/test_loan_disbursement.py | 6 ++++++ .../loan_interest_accrual/loan_interest_accrual.py | 2 +- .../doctype/loan_repayment/loan_repayment.py | 7 ++++--- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index b613d22827..23996afd21 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -344,13 +344,14 @@ "fieldtype": "Currency", "label": "Written Off Amount", "no_copy": 1, - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-22 11:03:43.697394", + "modified": "2020-10-27 23:37:02.785940", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index f58b989a20..949e1412e2 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -77,7 +77,7 @@ class LoanDisbursement(AccountsController): if disbursed_amount == 0: status = "Sanctioned" - total_payment = loan_details.loan_amount + elif disbursed_amount >= loan_details.loan_amount: status = "Disbursed" else: @@ -89,7 +89,7 @@ class LoanDisbursement(AccountsController): disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount total_payment = loan_details.total_payment - if loan_details.status == "Disbursed" and not loan_details.is_term_loan: + if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1), loan=self.against_loan, accrual_type="Disbursement") @@ -108,7 +108,6 @@ class LoanDisbursement(AccountsController): status = "Disbursed" else: status = "Partially Disbursed" - total_payment = disbursed_amount return disbursed_amount, status, total_payment @@ -199,6 +198,9 @@ def get_disbursal_amount(loan): disbursal_amount = flt(security_value) - flt(pending_principal_amount) + if loan_details.is_term_loan and (disbursal_amount + loan_details.loan_amount) > loan_details.loan_amount: + disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount + return disbursal_amount diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index aaaeea8c4e..a8753877a6 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -69,6 +69,12 @@ class TestLoanDisbursement(unittest.TestCase): # After repayment loan disbursement entry should go through make_loan_disbursement_entry(loan.name, 500000, disbursement_date=add_days(last_date, 16)) + # check for disbursement accrual + loan_interest_accrual = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name, + 'accrual_type': 'Disbursement'}) + + self.assertTrue(loan_interest_accrual) + def test_loan_topup_with_additional_pledge(self): pledge = [{ "loan_security": "Test Security 1", diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 1fc41f9ea5..d0b957de56 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -218,7 +218,7 @@ def get_no_of_days_for_interest_accural(loan, posting_date): def get_last_accural_date(loan): last_posting_date = frappe.db.sql(""" SELECT MAX(posting_date) from `tabLoan Interest Accrual` - WHERE loan = %s""", (loan)) + WHERE loan = %s and docstatus = 1""", (loan)) if last_posting_date[0][0]: # interest for last interest accrual date is already booked, so add 1 day diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index c1e83d9305..63d388daf5 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -301,7 +301,8 @@ def get_accrued_interest_entries(against_loan): unpaid_accrued_entries = frappe.db.sql( """ SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount, - payable_principal_amount - paid_principal_amount as payable_principal_amount + payable_principal_amount - paid_principal_amount as payable_principal_amount, + accrual_type FROM `tabLoan Interest Accrual` WHERE @@ -342,7 +343,7 @@ def get_amounts(amounts, against_loan, posting_date): no_of_late_days = date_diff(posting_date, add_days(due_date, loan_type_details.grace_period_in_days)) - if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary): + if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular': penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365 total_pending_interest += entry.interest_amount @@ -353,7 +354,7 @@ def get_amounts(amounts, against_loan, posting_date): 'payable_principal_amount': flt(entry.payable_principal_amount, precision) }) - if not final_due_date: + if due_date and not final_due_date: final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'): From 2c00549dfe9d87ed1b21a600df7275d0459c86c8 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 28 Oct 2020 22:54:56 +0530 Subject: [PATCH 073/154] refactor(UAE VAT 201): Use frappe api instead sql --- .../report/uae_vat_201/uae_vat_201.py | 152 ++++++++++-------- 1 file changed, 86 insertions(+), 66 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index ddbdf96c6a..744eb87d55 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -146,11 +146,14 @@ def append_data(data, no, legend, amount, vat_amount): def get_total_emiratewise(filters): """Returns Emiratewise Amount and Taxes.""" - return frappe.db.sql(f""" - select vat_emirate as emirate, sum(total), sum(total_taxes_and_charges) from `tabSales Invoice` - where docstatus = 1 {get_conditions(filters)} - group by `tabSales Invoice`.vat_emirate; - """, filters) + query_filters = get_filters(filters) + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Sales Invoice', + filters = query_filters, + fields = ['vat_emirate as emirate','sum(total)', 'sum(total_taxes_and_charges)'], + group_by='vat_emirate', + as_list=True + ) def get_emirates(): """Returns a List of emirates in the order that they are to be displayed.""" @@ -174,28 +177,40 @@ def get_conditions(filters): conditions += opts[1] return conditions +def get_filters(filters): + """The conditions to be used to filter data to calculate the total sale.""" + query_filters = {} + if filters.get("company"): + query_filters["company"] = ['=', filters['company']] + if filters.get("from_date"): + query_filters["posting_date"] = ['>=', filters['from_date']] + if filters.get("from_date"): + query_filters["posting_date"] = ['<=', filters['to_date']] + return query_filters + def get_reverse_charge_total(filters): """Returns the sum of the total of each Purchase invoice made.""" - conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(total) from - `tabPurchase Invoice` - where - reverse_charge = "Y" - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + query_filters = get_filters(filters) + query_filters['reverse_charge'] = ['=', 'Y'] + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Purchase Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 def get_reverse_charge_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" select sum(debit) from - `tabPurchase Invoice` inner join `tabGL Entry` - on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name + `tabPurchase Invoice` p inner join `tabGL Entry` gl + on gl.voucher_no = p.name where - `tabPurchase Invoice`.reverse_charge = "Y" - and `tabPurchase Invoice`.docstatus = 1 - and `tabGL Entry`.docstatus = 1 + p.reverse_charge = "Y" + and p.docstatus = 1 + and gl.docstatus = 1 and account in (select account from `tabUAE VAT Account` where parent=%(company)s) {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 @@ -212,75 +227,80 @@ def get_conditions_join(filters): def get_reverse_charge_recoverable_total(filters): """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" - conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(total) from - `tabPurchase Invoice` - where - reverse_charge = "Y" - and recoverable_reverse_charge > 0 - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + query_filters = get_filters(filters) + query_filters['reverse_charge'] = ['=', 'Y'] + query_filters['recoverable_reverse_charge'] = ['>', '0'] + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Purchase Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 def get_reverse_charge_recoverable_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" - select sum(debit * `tabPurchase Invoice`.recoverable_reverse_charge / 100) from - `tabPurchase Invoice` inner join `tabGL Entry` - on `tabGL Entry`.voucher_no = `tabPurchase Invoice`.name + select sum(debit * p.recoverable_reverse_charge / 100) from + `tabPurchase Invoice` p inner join `tabGL Entry` gl + on gl.voucher_no = p.name where - `tabPurchase Invoice`.reverse_charge = "Y" - and `tabPurchase Invoice`.docstatus = 1 - and `tabPurchase Invoice`.recoverable_reverse_charge > 0 - and `tabGL Entry`.docstatus = 1 + p.reverse_charge = "Y" + and p.docstatus = 1 + and p.recoverable_reverse_charge > 0 + and gl.docstatus = 1 and account in (select account from `tabUAE VAT Account` where parent=%(company)s) {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 def get_standard_rated_expenses_total(filters): """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" - conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(total) from - `tabPurchase Invoice` - where - recoverable_standard_rated_expenses > 0 - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + query_filters = get_filters(filters) + query_filters['recoverable_standard_rated_expenses'] = ['>', 0] + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Purchase Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 def get_standard_rated_expenses_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" - conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(recoverable_standard_rated_expenses) from - `tabPurchase Invoice` - where - recoverable_standard_rated_expenses > 0 - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + query_filters = get_filters(filters) + query_filters['recoverable_standard_rated_expenses'] = ['>', 0] + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Purchase Invoice', + filters = query_filters, + fields = ['sum(recoverable_standard_rated_expenses)'], + as_list=True, + limit = 1 + )[0][0] or 0 def get_tourist_tax_return_total(filters): """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return.""" - conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(total) from - `tabSales Invoice` - where - tourist_tax_return > 0 - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + query_filters = get_filters(filters) + query_filters['tourist_tax_return'] = ['>', 0] + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Sales Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 def get_tourist_tax_return_tax(filters): """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return.""" - conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(tourist_tax_return) from - `tabSales Invoice` - where - tourist_tax_return > 0 - and docstatus = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + query_filters = get_filters(filters) + query_filters['tourist_tax_return'] = ['>', 0] + query_filters['docstatus'] = ['=', 1] + return frappe.db.get_list('Sales Invoice', + filters = query_filters, + fields = ['sum(tourist_tax_return)'], + as_list=True, + limit = 1 + )[0][0] or 0 def get_zero_rated_total(filters): """Returns the sum of each Sales Invoice Item Amount which is zero rated.""" From 08846f6b11d88a0cd81888dc7747d3da992cff16 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 28 Oct 2020 23:33:56 +0530 Subject: [PATCH 074/154] refactor(UAE VAT 201): replace cartesian product to inner joins --- .../report/uae_vat_201/uae_vat_201.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 744eb87d55..ba6ff69361 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -9,7 +9,6 @@ def execute(filters=None): columns = get_columns() data, emirates, amounts_by_emirate = get_data(filters) chart = get_chart(emirates, amounts_by_emirate) - return columns, data, None, chart def get_columns(): @@ -50,7 +49,6 @@ def get_data(filters = None): append_vat_on_expenses(data, filters) return data, emirates, amounts_by_emirate - def get_chart(emirates, amounts_by_emirate): """Returns chart data.""" labels = [] @@ -167,16 +165,6 @@ def get_emirates(): 'Fujairah' ] -def get_conditions(filters): - """The conditions to be used to filter data to calculate the total sale.""" - conditions = "" - for opts in (("company", " and company=%(company)s"), - ("from_date", " and posting_date>=%(from_date)s"), - ("to_date", " and posting_date<=%(to_date)s")): - if filters.get(opts[0]): - conditions += opts[1] - return conditions - def get_filters(filters): """The conditions to be used to filter data to calculate the total sale.""" query_filters = {} @@ -215,16 +203,6 @@ def get_reverse_charge_tax(filters): {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 -def get_conditions_join(filters): - """The conditions to be used to filter data to calculate the total vat.""" - conditions = "" - for opts in (("company", " and `tabPurchase Invoice`.company=%(company)s"), - ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s")): - if filters.get(opts[0]): - conditions += opts[1] - return conditions - def get_reverse_charge_recoverable_total(filters): """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" query_filters = get_filters(filters) @@ -254,6 +232,16 @@ def get_reverse_charge_recoverable_tax(filters): {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 +def get_conditions_join(filters): + """The conditions to be used to filter data to calculate the total vat.""" + conditions = "" + for opts in (("company", " and `tabPurchase Invoice`.company=%(company)s"), + ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), + ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s")): + if filters.get(opts[0]): + conditions += opts[1] + return conditions + def get_standard_rated_expenses_total(filters): """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" query_filters = get_filters(filters) @@ -307,8 +295,9 @@ def get_zero_rated_total(filters): conditions = get_conditions(filters) return frappe.db.sql(""" select sum(i.base_amount) as total from - `tabSales Invoice Item` i, `tabSales Invoice` s - where s.docstatus = 1 and i.parent = s.name and i.is_zero_rated = 1 + `tabSales Invoice Item` i inner join `tabSales Invoice` s + on i.parent = s.name + where s.docstatus = 1 and i.is_zero_rated = 1 {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 @@ -317,7 +306,18 @@ def get_exempt_total(filters): conditions = get_conditions(filters) return frappe.db.sql(""" select sum(i.base_amount) as total from - `tabSales Invoice Item` i, `tabSales Invoice` s - where s.docstatus = 1 and i.parent = s.name and i.is_exempt = 1 + `tabSales Invoice Item` i inner join `tabSales Invoice` s + on i.parent = s.name + where s.docstatus = 1 and i.is_exempt = 1 {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 \ No newline at end of file + """.format(where_conditions=conditions), filters)[0][0] or 0 + +def get_conditions(filters): + """The conditions to be used to filter data to calculate the total sale.""" + conditions = "" + for opts in (("company", " and company=%(company)s"), + ("from_date", " and posting_date>=%(from_date)s"), + ("to_date", " and posting_date<=%(to_date)s")): + if filters.get(opts[0]): + conditions += opts[1] + return conditions \ No newline at end of file From bf261a6c394569ec34874a28eff7be511e67ba88 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 29 Oct 2020 00:07:52 +0530 Subject: [PATCH 075/154] fix(UAE VAT 201): fix helper --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index ba6ff69361..758da4205a 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -235,9 +235,9 @@ def get_reverse_charge_recoverable_tax(filters): def get_conditions_join(filters): """The conditions to be used to filter data to calculate the total vat.""" conditions = "" - for opts in (("company", " and `tabPurchase Invoice`.company=%(company)s"), - ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s")): + for opts in (("company", " and p.company=%(company)s"), + ("from_date", " and p.posting_date>=%(from_date)s"), + ("to_date", " and p.posting_date<=%(to_date)s")): if filters.get(opts[0]): conditions += opts[1] return conditions From eb3084b072654573ff4dd3ad97590f15878e48b0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Thu, 29 Oct 2020 09:52:17 +0530 Subject: [PATCH 076/154] refactor(UAE VAT 201): break functions --- .../report/uae_vat_201/uae_vat_201.py | 13 +++++++ .../regional/united_arab_emirates/utils.py | 38 ++++++++++--------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 758da4205a..216762bc8d 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -113,6 +113,7 @@ def standard_rated_expenses_emiratewise(data, filters): "amount": frappe.format(amount, 'Currency'), "vat_amount": frappe.format(vat, 'Currency'), } + amounts_by_emirate = append_emiratewise_expenses(data, emirates, amounts_by_emirate) for d, emirate in enumerate(emirates, 97): if emirate in amounts_by_emirate: @@ -125,6 +126,18 @@ def standard_rated_expenses_emiratewise(data, filters): frappe.format(0, 'Currency'), frappe.format(0, 'Currency')) return emirates, amounts_by_emirate +def append_emiratewise_expenses(data, emirates, amounts_by_emirate): + """Append emiratewise standard rated expenses and vat.""" + for d, emirate in enumerate(emirates, 97): + if emirate in amounts_by_emirate: + amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(d)) + amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate) + data.append(amounts_by_emirate[emirate]) + else: + append_data(data, _('1{0}').format(chr(d)), + _('Standard rated supplies in {0}').format(emirate), + frappe.format(0, 'Currency'), frappe.format(0, 'Currency')) + return amounts_by_emirate def append_vat_on_expenses(data, filters): """Appends Expenses and All Other Inputs.""" diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 7edc2b6720..7430db4fce 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -123,25 +123,29 @@ def make_regional_gl_entries(gl_entries, doc): for tax in doc.get('taxes'): if tax.category not in ("Total", "Valuation and Total"): continue - - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts: - account_currency = get_account_currency(tax.account_head) - - gl_entries.append(doc.get_gl_dict( - { - "account": tax.account_head, - "cost_center": tax.cost_center, - "posting_date": doc.posting_date, - "against": doc.supplier, - dr_or_cr: tax.base_tax_amount_after_discount_amount, - dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ - if account_currency==doc.company_currency \ - else tax.tax_amount_after_discount_amount - }, account_currency, item=tax) - ) + gl_entries = make_gl_entry(tax, gl_entries, doc, tax_accounts) return gl_entries +def make_gl_entry(tax, gl_entries, doc, tax_accounts): + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts: + account_currency = get_account_currency(tax.account_head) + + gl_entries.append(doc.get_gl_dict( + { + "account": tax.account_head, + "cost_center": tax.cost_center, + "posting_date": doc.posting_date, + "against": doc.supplier, + dr_or_cr: tax.base_tax_amount_after_discount_amount, + dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ + if account_currency==doc.company_currency \ + else tax.tax_amount_after_discount_amount + }, account_currency, item=tax) + ) + return gl_entries + + def validate_returns(doc, method): """Standard Rated expenses should not be set when Reverse Charge Applicable is set.""" country = frappe.get_cached_value('Company', doc.company, 'country') From 55c78b17bab76b9feb6aa5bb870858ad9e7df93c Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 29 Oct 2020 12:43:43 +0530 Subject: [PATCH 077/154] fix(UAE VAT 201): change desk page record --- erpnext/accounts/desk_page/accounting/accounting.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index db01254919..64e2b246a7 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -83,7 +83,7 @@ { "hidden": 0, "label": "Value-Added Tax (VAT UAE)", - "links": "[\n {\n \"label\": \"UAE VAT Setting\",\n \"name\": \"UAE VAT Setting\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n]" } ], "category": "Modules", @@ -158,4 +158,4 @@ "type": "Dashboard" } ] -} \ No newline at end of file +} From 900290413185010aba6775878a2be1121bdcdf85 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 29 Oct 2020 17:12:06 +0530 Subject: [PATCH 078/154] feat: added project link in timesheet form --- .../projects/doctype/timesheet/timesheet.js | 17 +- .../projects/doctype/timesheet/timesheet.json | 1389 ++++------------- 2 files changed, 320 insertions(+), 1086 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 607c3fd974..b068245a8b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -133,6 +133,11 @@ frappe.ui.form.on("Timesheet", { frm: frm }); }, + + project: function(frm) { + set_project_in_timelog(frm); + }, + }); frappe.ui.form.on("Timesheet Detail", { @@ -162,7 +167,11 @@ frappe.ui.form.on("Timesheet Detail", { frappe.model.set_value(cdt, cdn, "hours", hours); }, - time_logs_add: function(frm) { + time_logs_add: function(frm, cdt, cdn) { + if(frm.doc.project) { + frappe.model.set_value(cdt, cdn, 'project', frm.doc.project); + } + var $trigger_again = $('.form-grid').find('.grid-row').find('.btn-open-row'); $trigger_again.on('click', () => { $('.form-grid') @@ -297,3 +306,9 @@ const set_employee_and_company = function(frm) { } }); }; + +function set_project_in_timelog(frm) { + if(frm.doc.project){ + erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "time_logs", "project"); + } +} \ No newline at end of file diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index c29c11b746..4c2edf4f03 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -1,1133 +1,352 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-02-28 17:57:33", - "custom": 0, - "description": "", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-02-28 17:57:33", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "naming_series", + "company", + "sales_invoice", + "column_break_3", + "salary_slip", + "status", + "project", + "employee_detail", + "employee", + "employee_name", + "department", + "column_break_9", + "user", + "start_date", + "end_date", + "section_break_5", + "time_logs", + "working_hours", + "total_hours", + "billing_details", + "total_billable_hours", + "total_billed_hours", + "total_costing_amount", + "column_break_10", + "total_billable_amount", + "total_billed_amount", + "per_billed", + "section_break_18", + "note", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "{employee_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "{employee_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "TS-.YYYY.-", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "TS-.YYYY.-", + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "sales_invoice", - "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": "Sales Invoice", - "length": 0, - "no_copy": 1, - "options": "Sales Invoice", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sales_invoice", + "fieldtype": "Link", + "label": "Sales Invoice", + "no_copy": 1, + "options": "Sales Invoice", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "salary_slip", - "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": "Salary Slip", - "length": 0, - "no_copy": 1, - "options": "Salary Slip", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "salary_slip", + "fieldtype": "Link", + "label": "Salary Slip", + "no_copy": 1, + "options": "Salary Slip", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Draft\nSubmitted\nBilled\nPayslip\nCompleted\nCancelled", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Draft\nSubmitted\nBilled\nPayslip\nCompleted\nCancelled", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "eval:!doc.work_order || doc.docstatus == 1", - "fieldname": "employee_detail", - "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": "Employee Detail", - "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 - }, + "depends_on": "eval:!doc.work_order || doc.docstatus == 1", + "fieldname": "employee_detail", + "fieldtype": "Section Break", + "label": "Employee Detail" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Employee", + "options": "Employee" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "employee", - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "employee", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Employee Name", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "user", + "fieldtype": "Link", + "in_global_search": 1, + "label": "User", + "options": "User", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "start_date", - "fieldtype": "Date", - "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": "Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "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, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "time_logs", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time Sheets", - "length": 0, - "no_copy": 0, - "options": "Timesheet Detail", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "time_logs", + "fieldtype": "Table", + "label": "Time Sheets", + "options": "Timesheet Detail", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "working_hours", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "working_hours", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "description": "", - "fieldname": "total_hours", - "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": "Total Working Hours", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "total_hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Working Hours", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "billing_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing Details", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "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 - }, + "collapsible": 1, + "fieldname": "billing_details", + "fieldtype": "Section Break", + "label": "Billing Details", + "permlevel": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_billable_hours", - "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": "Total Billable Hours", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "total_billable_hours", + "fieldtype": "Float", + "label": "Total Billable Hours", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_billed_hours", - "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": "Total Billed Hours", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "total_billed_hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Billed Hours", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_costing_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": "Total Costing Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "total_costing_amount", + "fieldtype": "Currency", + "label": "Total Costing Amount", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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 - }, + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "depends_on": "", - "description": "", - "fieldname": "total_billable_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": "Total Billable Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "total_billable_amount", + "fieldtype": "Currency", + "label": "Total Billable Amount", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_billed_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": "Total Billed Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "total_billed_amount", + "fieldtype": "Currency", + "label": "Total Billed Amount", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "per_billed", - "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": "% Amount Billed", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "per_billed", + "fieldtype": "Percent", + "label": "% Amount Billed", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_18", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_18", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "note", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Note", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "note", + "fieldtype": "Text Editor", + "label": "Note" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Timesheet", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Timesheet", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-clock-o", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-03-05 21:54:02.654690", - "modified_by": "Administrator", - "module": "Projects", - "name": "Timesheet", - "owner": "Administrator", + ], + "icon": "fa fa-clock-o", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2020-10-29 07:50:35.938231", + "modified_by": "Administrator", + "module": "Projects", + "name": "Timesheet", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Employee", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "create": 1, + "read": 1, + "role": "Employee", "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 0, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "permlevel": 1, + "read": 1, + "role": "Accounts User", "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "title_field": "title", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "ASC", + "title_field": "title" } \ No newline at end of file From f116fbf84d9e4ec6c6d8694463faa96d0e7c1ee0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Mon, 2 Nov 2020 10:27:54 +0530 Subject: [PATCH 079/154] refactor(UAE VAT 201): Update erpnext/regional/report/uae_vat_201/test_uae_vat_201.py Co-authored-by: Shivam Mishra --- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 3037a07698..70c6b74f5e 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -15,7 +15,7 @@ from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( get_exempt_total, get_standard_rated_expenses_total, get_standard_rated_expenses_tax, - ) +) test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] @@ -295,4 +295,4 @@ def create_purchase_invoices(): pi.recoverable_standard_rated_expenses = 1 - pi.submit() \ No newline at end of file + pi.submit() From be7a4c8b0aaf5d33e23bbb6f635f47484425637d Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Mon, 2 Nov 2020 10:28:14 +0530 Subject: [PATCH 080/154] refactor(UAE VAT 201): Update erpnext/regional/report/uae_vat_201/test_uae_vat_201.py Co-authored-by: Shivam Mishra --- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 70c6b74f5e..84db9146cc 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -95,8 +95,10 @@ def set_vat_accounts(): fields=["name"], filters = { "company": "_Test Company UAE VAT", - "is_group":0, - "account_type": "Tax"}) + "is_group": 0, + "account_type": "Tax" + } + ) uae_vat_accounts = [] for d in vat_accounts: From 46265d3fc7e1101cea4960e5b593b393ada9deb7 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Mon, 2 Nov 2020 10:28:41 +0530 Subject: [PATCH 081/154] refactor(UAE VAT 201): Update erpnext/regional/report/uae_vat_201/test_uae_vat_201.py Co-authored-by: Shivam Mishra --- .../regional/report/uae_vat_201/test_uae_vat_201.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 84db9146cc..7514652894 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -48,12 +48,12 @@ class TestUaeVat201(TestCase): filters = {"company": "_Test Company UAE VAT"} total_emiratewise = get_total_emiratewise(filters) amounts_by_emirate = {} - for d in total_emiratewise: - emirate, amount, vat= d + for data in total_emiratewise: + emirate, amount, vat = data amounts_by_emirate[emirate] = { - "raw_amount": amount, - "raw_vat_amount": vat, - } + "raw_amount": amount, + "raw_vat_amount": vat, + } self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"],300) self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"],5) self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200) From 7b771d139c66ce2251941349ffb4e4db88ce47c9 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Mon, 2 Nov 2020 10:29:14 +0530 Subject: [PATCH 082/154] refactor(UAE VAT 201): Update erpnext/regional/report/uae_vat_201/test_uae_vat_201.py Co-authored-by: Shivam Mishra --- erpnext/regional/report/uae_vat_201/test_uae_vat_201.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 7514652894..5fdb2645f7 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -101,12 +101,11 @@ def set_vat_accounts(): ) uae_vat_accounts = [] - for d in vat_accounts: - uae_vat_accounts.append( - { + for account in vat_accounts: + uae_vat_accounts.append({ "doctype": "UAE VAT Account", - "account":d.name - }) + "account": account.name + }) frappe.get_doc({ "company": "_Test Company UAE VAT", From d322181fd7d2d6cce9301b41094b314803ce7973 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Mon, 2 Nov 2020 10:29:38 +0530 Subject: [PATCH 083/154] refactor(UAE VAT 201): Update erpnext/regional/report/uae_vat_201/uae_vat_201.py Co-authored-by: Shivam Mishra --- .../regional/report/uae_vat_201/uae_vat_201.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 216762bc8d..201ddf63f5 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -65,14 +65,14 @@ def get_chart(emirates, amounts_by_emirate): datasets.append({'name': _('Vat Amount (AED)'), 'values': vat_amount}) chart = { - "data": { - 'labels': labels, - 'datasets': datasets - } + "type": "bar", + "fieldtype": "Currency" + "data": { + 'labels': labels, + 'datasets': datasets } - - chart["type"] = "bar" - chart["fieldtype"] = "Currency" + } + return chart def append_vat_on_sales(data, filters): @@ -333,4 +333,4 @@ def get_conditions(filters): ("to_date", " and posting_date<=%(to_date)s")): if filters.get(opts[0]): conditions += opts[1] - return conditions \ No newline at end of file + return conditions From c53237f5e0177a5e2a5aef39e476a58077067e9b Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 2 Nov 2020 11:02:41 +0530 Subject: [PATCH 084/154] refactor(UAE VAT 201): solve syntax errors --- .../report/uae_vat_201/uae_vat_201.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 201ddf63f5..f641e652a6 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -66,13 +66,13 @@ def get_chart(emirates, amounts_by_emirate): chart = { "type": "bar", - "fieldtype": "Currency" + "fieldtype": "Currency", "data": { 'labels': labels, 'datasets': datasets } } - + return chart def append_vat_on_sales(data, filters): @@ -89,14 +89,14 @@ def append_vat_on_sales(data, filters): frappe.format(get_reverse_charge_total(filters), 'Currency'), frappe.format(get_reverse_charge_tax(filters), 'Currency')) - append_data(data, '4', _('Zero Rated'), + append_data(data, '4', _('Zero Rated'), frappe.format(get_zero_rated_total(filters), 'Currency'), "-") append_data(data, '5', _('Exempt Supplies'), frappe.format(get_exempt_total(filters), 'Currency'),"-") append_data(data, '', '', '', '') - + return emirates, amounts_by_emirate def standard_rated_expenses_emiratewise(data, filters): @@ -159,7 +159,7 @@ def get_total_emiratewise(filters): """Returns Emiratewise Amount and Taxes.""" query_filters = get_filters(filters) query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Sales Invoice', + return frappe.db.get_list('Sales Invoice', filters = query_filters, fields = ['vat_emirate as emirate','sum(total)', 'sum(total_taxes_and_charges)'], group_by='vat_emirate', @@ -194,7 +194,7 @@ def get_reverse_charge_total(filters): query_filters = get_filters(filters) query_filters['reverse_charge'] = ['=', 'Y'] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', + return frappe.db.get_list('Purchase Invoice', filters = query_filters, fields = ['sum(total)'], as_list=True, @@ -222,7 +222,7 @@ def get_reverse_charge_recoverable_total(filters): query_filters['reverse_charge'] = ['=', 'Y'] query_filters['recoverable_reverse_charge'] = ['>', '0'] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', + return frappe.db.get_list('Purchase Invoice', filters = query_filters, fields = ['sum(total)'], as_list=True, @@ -260,7 +260,7 @@ def get_standard_rated_expenses_total(filters): query_filters = get_filters(filters) query_filters['recoverable_standard_rated_expenses'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', + return frappe.db.get_list('Purchase Invoice', filters = query_filters, fields = ['sum(total)'], as_list=True, @@ -272,7 +272,7 @@ def get_standard_rated_expenses_tax(filters): query_filters = get_filters(filters) query_filters['recoverable_standard_rated_expenses'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', + return frappe.db.get_list('Purchase Invoice', filters = query_filters, fields = ['sum(recoverable_standard_rated_expenses)'], as_list=True, @@ -284,7 +284,7 @@ def get_tourist_tax_return_total(filters): query_filters = get_filters(filters) query_filters['tourist_tax_return'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Sales Invoice', + return frappe.db.get_list('Sales Invoice', filters = query_filters, fields = ['sum(total)'], as_list=True, @@ -296,7 +296,7 @@ def get_tourist_tax_return_tax(filters): query_filters = get_filters(filters) query_filters['tourist_tax_return'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Sales Invoice', + return frappe.db.get_list('Sales Invoice', filters = query_filters, fields = ['sum(tourist_tax_return)'], as_list=True, From b0eca23485b9319754e33af11727605df2f97c82 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 2 Nov 2020 12:47:10 +0530 Subject: [PATCH 085/154] Refactor(UAE VAT 201): Fix fomatiing --- .../report/uae_vat_201/test_uae_vat_201.py | 184 +++++--------- .../report/uae_vat_201/uae_vat_201.py | 227 ++++++++++-------- .../regional/united_arab_emirates/utils.py | 22 +- 3 files changed, 201 insertions(+), 232 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 5fdb2645f7..b0a21de613 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -8,8 +8,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse_account from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( - get_total_emiratewise, - get_tourist_tax_return_total, + get_total_emiratewise, + get_tourist_tax_return_total, get_tourist_tax_return_tax, get_zero_rated_total, get_exempt_total, @@ -26,7 +26,6 @@ class TestUaeVat201(TestCase): frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company UAE VAT'") frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company UAE VAT'") - make_company("_Test Company UAE VAT", "_TCUV") set_vat_accounts() @@ -79,7 +78,6 @@ def make_company(company_name, abbr): else: company = frappe.get_doc("Company", company_name) - # indempotent company.create_default_warehouses() if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): @@ -95,7 +93,7 @@ def set_vat_accounts(): fields=["name"], filters = { "company": "_Test Company UAE VAT", - "is_group": 0, + "is_group": 0, "account_type": "Tax" } ) @@ -103,7 +101,7 @@ def set_vat_accounts(): uae_vat_accounts = [] for account in vat_accounts: uae_vat_accounts.append({ - "doctype": "UAE VAT Account", + "doctype": "UAE VAT Account", "account": account.name }) @@ -125,7 +123,6 @@ def make_customer(): customer = frappe.get_doc("Customer", "_Test UAE Customer") def make_supplier(): - if not frappe.db.exists("Supplier", "_Test UAE Supplier"): frappe.get_doc({ "supplier_group": "Local", @@ -140,15 +137,15 @@ def create_warehouse(warehouse_name, properties=None, company=None): warehouse_id = erpnext.encode_company_abbr(warehouse_name, company) if not frappe.db.exists("Warehouse", warehouse_id): - w = frappe.new_doc("Warehouse") - w.warehouse_name = warehouse_name - w.parent_warehouse = "All Warehouses - _TCUV" - w.company = company - w.account = get_warehouse_account(warehouse_name, company) + warehouse = frappe.new_doc("Warehouse") + warehouse.warehouse_name = warehouse_name + warehouse.parent_warehouse = "All Warehouses - _TCUV" + warehouse.company = company + warehouse.account = get_warehouse_account(warehouse_name, company) if properties: - w.update(properties) - w.save() - return w.name + warehouse.update(properties) + warehouse.save() + return warehouse.name else: return warehouse_id @@ -166,13 +163,15 @@ def make_item(item_code, properties=None): if properties: item.update(properties) - + item.insert() return item def make_sales_invoices(): - si = create_sales_invoice(company="_Test Company UAE VAT", + def make_sales_invoices_wrapper(emirate, item, tax = True, tourist_tax= False): + si = create_sales_invoice( + company="_Test Company UAE VAT", customer = '_Test UAE Customer', currency = 'AED', warehouse = 'Finished Goods - _TCUV', @@ -180,119 +179,60 @@ def make_sales_invoices(): income_account = 'Sales - _TCUV', expense_account = 'Cost of Goods Sold - _TCUV', cost_center = 'Main - _TCUV', - sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", - item = "_Test UAE VAT Item", + item = item, do_not_save=1 ) - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "VAT 5% - _TCUV", - "cost_center": "Main - _TCUV", - "description": "VAT 5% @ 5.0", - "rate": 5.0 - }) - si.vat_emirate = 'Dubai' - si.submit() + si.vat_emirate = emirate + if tax: + si.append( + "taxes", { + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + } + ) + if tourist_tax: + si.tourist_tax_return = 2 + si.submit() - si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test UAE Customer', - currency = 'AED', - warehouse = 'Finished Goods - _TCUV', - debit_to = 'Debtors - _TCUV', - income_account = 'Sales - _TCUV', - expense_account = 'Cost of Goods Sold - _TCUV', - cost_center = 'Main - _TCUV', - sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", - item = "_Test UAE VAT Item", - do_not_save=1 - ) - si.vat_emirate = 'Sharjah' - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "VAT 5% - _TCUV", - "cost_center": "Main - _TCUV", - "description": "VAT 5% @ 5.0", - "rate": 5.0 - }) - si.submit() + #Define Item Names + uae_item = "_Test UAE VAT Item" + uae_exempt_item = "_Test UAE VAT Exempt Item" + uae_zero_rated_item = "_Test UAE VAT Zero Rated Item" - si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test UAE Customer', - currency = 'AED', - warehouse = 'Finished Goods - _TCUV', - debit_to = 'Debtors - _TCUV', - income_account = 'Sales - _TCUV', - expense_account = 'Cost of Goods Sold - _TCUV', - cost_center = 'Main - _TCUV', - sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", - item = "_Test UAE VAT Item", - do_not_save=1 - ) - - si.tourist_tax_return = 2 - - si.vat_emirate = 'Dubai' - - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "VAT 5% - _TCUV", - "cost_center": "Main - _TCUV", - "description": "VAT 5% @ 5.0", - "rate": 5.0 - }) - si.submit() - - si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test UAE Customer', - currency = 'AED', - warehouse = 'Finished Goods - _TCUV', - debit_to = 'Debtors - _TCUV', - income_account = 'Sales - _TCUV', - expense_account = 'Cost of Goods Sold - _TCUV', - cost_center = 'Main - _TCUV', - sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", - item = "_Test UAE VAT Zero Rated Item", - do_not_save=1 - ) - si.vat_emirate = 'Sharjah' - si.submit() - - si = create_sales_invoice(company="_Test Company UAE VAT", - customer = '_Test UAE Customer', - currency = 'AED', - warehouse = 'Finished Goods - _TCUV', - debit_to = 'Debtors - _TCUV', - income_account = 'Sales - _TCUV', - expense_account = 'Cost of Goods Sold - _TCUV', - cost_center = 'Main - _TCUV', - sales_taxes_and_charges_template = "UAE VAT 5% - _TCUV", - item = "_Test UAE VAT Exempt Item", - do_not_save=1 - ) - si.vat_emirate = 'Sharjah' - si.submit() + #Sales Invoice with standard rated expense in Dubai + make_sales_invoices_wrapper('Dubai', uae_item) + #Sales Invoice with standard rated expense in Sharjah + make_sales_invoices_wrapper('Sharjah', uae_item) + #Sales Invoice with Tourist Tax Return + make_sales_invoices_wrapper('Dubai', uae_item, True, True) + #Sales Invoice with Exempt Item + make_sales_invoices_wrapper('Sharjah', uae_exempt_item, False) + #Sales Invoice with Zero Rated Item + make_sales_invoices_wrapper('Sharjah', uae_zero_rated_item, False) def create_purchase_invoices(): - pi = make_purchase_invoice( - company="_Test Company UAE VAT", - supplier = '_Test UAE Supplier', - supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', - warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', - currency = 'AED', - cost_center = 'Main - _TCUV', - expense_account = 'Cost of Goods Sold - _TCUV', - item = "_Test UAE VAT Item", - do_not_save=1, - uom = "Nos" - ) + company="_Test Company UAE VAT", + supplier = '_Test UAE Supplier', + supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', + warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV', + currency = 'AED', + cost_center = 'Main - _TCUV', + expense_account = 'Cost of Goods Sold - _TCUV', + item = "_Test UAE VAT Item", + do_not_save=1, + uom = "Nos" + ) pi.append("taxes", { - "charge_type": "On Net Total", - "account_head": "VAT 5% - _TCUV", - "cost_center": "Main - _TCUV", - "description": "VAT 5% @ 5.0", - "rate": 5.0 - }) + "charge_type": "On Net Total", + "account_head": "VAT 5% - _TCUV", + "cost_center": "Main - _TCUV", + "description": "VAT 5% @ 5.0", + "rate": 5.0 + }) pi.recoverable_standard_rated_expenses = 1 diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index f641e652a6..78dcb367a4 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -54,11 +54,11 @@ def get_chart(emirates, amounts_by_emirate): labels = [] amount = [] vat_amount = [] - for d in emirates: - if d in amounts_by_emirate: - amount.append(amounts_by_emirate[d]["raw_amount"]) - vat_amount.append(amounts_by_emirate[d]["raw_vat_amount"]) - labels.append(d) + for emirate in emirates: + if emirate in amounts_by_emirate: + amount.append(amounts_by_emirate[emirate]["raw_amount"]) + vat_amount.append(amounts_by_emirate[emirate]["raw_vat_amount"]) + labels.append(emirate) datasets = [] datasets.append({'name': _('Amount (AED)'), 'values': amount}) @@ -81,7 +81,8 @@ def append_vat_on_sales(data, filters): emirates, amounts_by_emirate = standard_rated_expenses_emiratewise(data, filters) - append_data(data, '2', _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'), + append_data(data, '2', + _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'), frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'), frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency')) @@ -104,37 +105,26 @@ def standard_rated_expenses_emiratewise(data, filters): total_emiratewise = get_total_emiratewise(filters) emirates = get_emirates() amounts_by_emirate = {} - for d in total_emiratewise: - emirate, amount, vat= d + for emirate, amount, vat in total_emiratewise: amounts_by_emirate[emirate] = { - "legend": emirate, - "raw_amount": amount, - "raw_vat_amount": vat, - "amount": frappe.format(amount, 'Currency'), - "vat_amount": frappe.format(vat, 'Currency'), - } + "legend": emirate, + "raw_amount": amount, + "raw_vat_amount": vat, + "amount": frappe.format(amount, 'Currency'), + "vat_amount": frappe.format(vat, 'Currency'), + } amounts_by_emirate = append_emiratewise_expenses(data, emirates, amounts_by_emirate) - - for d, emirate in enumerate(emirates, 97): - if emirate in amounts_by_emirate: - amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(d)) - amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate) - data.append(amounts_by_emirate[emirate]) - else: - append_data(data, _('1{0}').format(chr(d)), - _('Standard rated supplies in {0}').format(emirate), - frappe.format(0, 'Currency'), frappe.format(0, 'Currency')) return emirates, amounts_by_emirate def append_emiratewise_expenses(data, emirates, amounts_by_emirate): """Append emiratewise standard rated expenses and vat.""" - for d, emirate in enumerate(emirates, 97): + for no, emirate in enumerate(emirates, 97): if emirate in amounts_by_emirate: - amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(d)) + amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(no)) amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate) data.append(amounts_by_emirate[emirate]) else: - append_data(data, _('1{0}').format(chr(d)), + append_data(data, _('1{0}').format(chr(no)), _('Standard rated supplies in {0}').format(emirate), frappe.format(0, 'Currency'), frappe.format(0, 'Currency')) return amounts_by_emirate @@ -145,11 +135,9 @@ def append_vat_on_expenses(data, filters): append_data(data, '9', _('Standard Rated Expenses'), frappe.format(get_standard_rated_expenses_total(filters), 'Currency'), frappe.format(get_standard_rated_expenses_tax(filters), 'Currency')) - append_data(data, '10', _('Supplies subject to the reverse charge provision'), frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'), - frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency') -) + frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency')) def append_data(data, no, legend, amount, vat_amount): """Returns data with appended value.""" @@ -159,7 +147,7 @@ def get_total_emiratewise(filters): """Returns Emiratewise Amount and Taxes.""" query_filters = get_filters(filters) query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Sales Invoice', + return frappe.db.get_all('Sales Invoice', filters = query_filters, fields = ['vat_emirate as emirate','sum(total)', 'sum(total_taxes_and_charges)'], group_by='vat_emirate', @@ -194,26 +182,30 @@ def get_reverse_charge_total(filters): query_filters = get_filters(filters) query_filters['reverse_charge'] = ['=', 'Y'] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', - filters = query_filters, - fields = ['sum(total)'], - as_list=True, - limit = 1 - )[0][0] or 0 + try: + return frappe.db.get_all('Purchase Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_reverse_charge_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" select sum(debit) from - `tabPurchase Invoice` p inner join `tabGL Entry` gl - on gl.voucher_no = p.name + `tabPurchase Invoice` p inner join `tabGL Entry` gl + on + gl.voucher_no = p.name where - p.reverse_charge = "Y" - and p.docstatus = 1 - and gl.docstatus = 1 - and account in (select account from `tabUAE VAT Account` where parent=%(company)s) - {where_conditions} ; + p.reverse_charge = "Y" + and p.docstatus = 1 + and gl.docstatus = 1 + and account in (select account from `tabUAE VAT Account` where parent=%(company)s) + {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 def get_reverse_charge_recoverable_total(filters): @@ -222,27 +214,33 @@ def get_reverse_charge_recoverable_total(filters): query_filters['reverse_charge'] = ['=', 'Y'] query_filters['recoverable_reverse_charge'] = ['>', '0'] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', - filters = query_filters, - fields = ['sum(total)'], - as_list=True, - limit = 1 - )[0][0] or 0 + try: + return frappe.db.get_all('Purchase Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_reverse_charge_recoverable_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" conditions = get_conditions_join(filters) return frappe.db.sql(""" - select sum(debit * p.recoverable_reverse_charge / 100) from - `tabPurchase Invoice` p inner join `tabGL Entry` gl - on gl.voucher_no = p.name + select + sum(debit * p.recoverable_reverse_charge / 100) + from + `tabPurchase Invoice` p inner join `tabGL Entry` gl + on + gl.voucher_no = p.name where - p.reverse_charge = "Y" - and p.docstatus = 1 - and p.recoverable_reverse_charge > 0 - and gl.docstatus = 1 - and account in (select account from `tabUAE VAT Account` where parent=%(company)s) - {where_conditions} ; + p.reverse_charge = "Y" + and p.docstatus = 1 + and p.recoverable_reverse_charge > 0 + and gl.docstatus = 1 + and account in (select account from `tabUAE VAT Account` where parent=%(company)s) + {where_conditions} ; """.format(where_conditions=conditions), filters)[0][0] or 0 def get_conditions_join(filters): @@ -251,8 +249,8 @@ def get_conditions_join(filters): for opts in (("company", " and p.company=%(company)s"), ("from_date", " and p.posting_date>=%(from_date)s"), ("to_date", " and p.posting_date<=%(to_date)s")): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get(opts[0]): + conditions += opts[1] return conditions def get_standard_rated_expenses_total(filters): @@ -260,77 +258,102 @@ def get_standard_rated_expenses_total(filters): query_filters = get_filters(filters) query_filters['recoverable_standard_rated_expenses'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', - filters = query_filters, - fields = ['sum(total)'], - as_list=True, - limit = 1 - )[0][0] or 0 + try: + return frappe.db.get_all('Purchase Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_standard_rated_expenses_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" query_filters = get_filters(filters) query_filters['recoverable_standard_rated_expenses'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Purchase Invoice', - filters = query_filters, - fields = ['sum(recoverable_standard_rated_expenses)'], - as_list=True, - limit = 1 - )[0][0] or 0 + try: + return frappe.db.get_all('Purchase Invoice', + filters = query_filters, + fields = ['sum(recoverable_standard_rated_expenses)'], + as_list=True, + limit = 1 + )[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_tourist_tax_return_total(filters): """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return.""" query_filters = get_filters(filters) query_filters['tourist_tax_return'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Sales Invoice', - filters = query_filters, - fields = ['sum(total)'], - as_list=True, - limit = 1 - )[0][0] or 0 + try: + return frappe.db.get_all('Sales Invoice', + filters = query_filters, + fields = ['sum(total)'], + as_list=True, + limit = 1 + )[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_tourist_tax_return_tax(filters): """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return.""" query_filters = get_filters(filters) query_filters['tourist_tax_return'] = ['>', 0] query_filters['docstatus'] = ['=', 1] - return frappe.db.get_list('Sales Invoice', - filters = query_filters, - fields = ['sum(tourist_tax_return)'], - as_list=True, - limit = 1 - )[0][0] or 0 + try: + return frappe.db.get_all('Sales Invoice', + filters = query_filters, + fields = ['sum(tourist_tax_return)'], + as_list=True, + limit = 1 + )[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_zero_rated_total(filters): """Returns the sum of each Sales Invoice Item Amount which is zero rated.""" conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(i.base_amount) as total from - `tabSales Invoice Item` i inner join `tabSales Invoice` s - on i.parent = s.name - where s.docstatus = 1 and i.is_zero_rated = 1 - {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 + try: + return frappe.db.sql(""" + select + sum(i.base_amount) as total + from + `tabSales Invoice Item` i inner join `tabSales Invoice` s + on + i.parent = s.name + where + s.docstatus = 1 and i.is_zero_rated = 1 + {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_exempt_total(filters): """Returns the sum of each Sales Invoice Item Amount which is Vat Exempt.""" conditions = get_conditions(filters) - return frappe.db.sql(""" - select sum(i.base_amount) as total from - `tabSales Invoice Item` i inner join `tabSales Invoice` s - on i.parent = s.name - where s.docstatus = 1 and i.is_exempt = 1 - {where_conditions} ; - """.format(where_conditions=conditions), filters)[0][0] or 0 - + try: + return frappe.db.sql(""" + select + sum(i.base_amount) as total + from + `tabSales Invoice Item` i inner join `tabSales Invoice` s + on + i.parent = s.name + where + s.docstatus = 1 and i.is_exempt = 1 + {where_conditions} ; + """.format(where_conditions=conditions), filters)[0][0] or 0 + except (IndexError, TypeError): + return 0 def get_conditions(filters): """The conditions to be used to filter data to calculate the total sale.""" conditions = "" for opts in (("company", " and company=%(company)s"), ("from_date", " and posting_date>=%(from_date)s"), ("to_date", " and posting_date<=%(to_date)s")): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get(opts[0]): + conditions += opts[1] return conditions diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 7430db4fce..872839495a 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -35,7 +35,12 @@ def get_account_currency(account): if not account: return def generator(): - account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"]) + account_currency, company = frappe.get_cached_value( + "Account", + account, + ["account_currency", + "company"] + ) if not account_currency: account_currency = frappe.get_cached_value('Company', company, "default_currency") @@ -53,8 +58,8 @@ def get_tax_accounts(company): if not tax_accounts_list and not frappe.flags.in_test: frappe.throw(_('Please set Vat Accounts for Company: "{0}" in UAE VAT Settings').format(company)) - for d in tax_accounts_list: - for key, name in d.items(): + for tax_account in tax_accounts_list: + for _, name in tax_account.items(): tax_accounts_dict[name] = name return tax_accounts_dict @@ -131,8 +136,7 @@ def make_gl_entry(tax, gl_entries, doc, tax_accounts): if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts: account_currency = get_account_currency(tax.account_head) - gl_entries.append(doc.get_gl_dict( - { + gl_entries.append(doc.get_gl_dict({ "account": tax.account_head, "cost_center": tax.cost_center, "posting_date": doc.posting_date, @@ -141,8 +145,8 @@ def make_gl_entry(tax, gl_entries, doc, tax_accounts): dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ if account_currency==doc.company_currency \ else tax.tax_amount_after_discount_amount - }, account_currency, item=tax) - ) + }, account_currency, item=tax + )) return gl_entries @@ -152,4 +156,6 @@ def validate_returns(doc, method): if country != 'United Arab Emirates': return if doc.reverse_charge == 'Y' and flt(doc.recoverable_standard_rated_expenses) != 0: - frappe.throw(_("Recoverable Standard Rated expenses should not be set when Reverse Charge Applicable is Y")) \ No newline at end of file + frappe.throw(_( + "Recoverable Standard Rated expenses should not be set when Reverse Charge Applicable is Y" + )) From 7a034595fe7c2f60090e9bbdc185a98ecd0f4f38 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 2 Nov 2020 13:12:27 +0530 Subject: [PATCH 086/154] fix(UAE VAT 201): Remove Chart --- .../report/uae_vat_201/uae_vat_201.py | 29 +------------------ .../regional/united_arab_emirates/utils.py | 2 +- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 78dcb367a4..c151c622a6 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -8,8 +8,7 @@ from frappe import _ def execute(filters=None): columns = get_columns() data, emirates, amounts_by_emirate = get_data(filters) - chart = get_chart(emirates, amounts_by_emirate) - return columns, data, None, chart + return columns, data def get_columns(): """Creates a list of dictionaries that are used to generate column headers of the data table.""" @@ -49,32 +48,6 @@ def get_data(filters = None): append_vat_on_expenses(data, filters) return data, emirates, amounts_by_emirate -def get_chart(emirates, amounts_by_emirate): - """Returns chart data.""" - labels = [] - amount = [] - vat_amount = [] - for emirate in emirates: - if emirate in amounts_by_emirate: - amount.append(amounts_by_emirate[emirate]["raw_amount"]) - vat_amount.append(amounts_by_emirate[emirate]["raw_vat_amount"]) - labels.append(emirate) - - datasets = [] - datasets.append({'name': _('Amount (AED)'), 'values': amount}) - datasets.append({'name': _('Vat Amount (AED)'), 'values': vat_amount}) - - chart = { - "type": "bar", - "fieldtype": "Currency", - "data": { - 'labels': labels, - 'datasets': datasets - } - } - - return chart - def append_vat_on_sales(data, filters): """Appends Sales and All Other Outputs.""" append_data(data, '', _('VAT on Sales and All Other Outputs'), '', '') diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 872839495a..7d5fd6ecf8 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -59,7 +59,7 @@ def get_tax_accounts(company): if not tax_accounts_list and not frappe.flags.in_test: frappe.throw(_('Please set Vat Accounts for Company: "{0}" in UAE VAT Settings').format(company)) for tax_account in tax_accounts_list: - for _, name in tax_account.items(): + for account, name in tax_account.items(): tax_accounts_dict[name] = name return tax_accounts_dict From a2ba8ea74a39403d9cd2fd2cc2af895059d9cdf8 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Tue, 3 Nov 2020 10:57:52 +0800 Subject: [PATCH 087/154] style: Clean up code formatting --- .../stock/doctype/item_price/item_price.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 54f534f3d1..bed5ea9ab6 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -46,12 +46,7 @@ class ItemPrice(Document): self.item_name, self.item_description = frappe.db.get_value("Item", self.item_code,["item_name", "description"]) def check_duplicates(self): - conditions = """ - where - item_code = %(item_code)s - and price_list = %(price_list)s - and name != %(name)s - """ + conditions = """where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s""" for field in [ "uom", @@ -59,23 +54,18 @@ class ItemPrice(Document): "valid_upto", "packing_unit", "customer", - "supplier", - ]: + "supplier",]: if self.get(field): conditions += " and {0} = %({0})s ".format(field) else: conditions += "and (isnull({0}) or {0} = '')".format(field) - price_list_rate = frappe.db.sql( - """ + price_list_rate = frappe.db.sql(""" select price_list_rate from `tabItem Price` {conditions} - """.format( - conditions=conditions - ), - self.as_dict(), - ) + """.format(conditions=conditions), + self.as_dict(),) if price_list_rate: frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,) From 846ff323e2ef5853b3fe1af614b9589de93df6e2 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 3 Nov 2020 17:35:44 +0530 Subject: [PATCH 088/154] feat: sales order status filter for production plan --- .../doctype/production_plan/production_plan.json | 10 +++++++++- .../doctype/production_plan/production_plan.py | 6 ++++-- .../doctype/production_plan/test_production_plan.py | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 850d5aeff8..63df5f379d 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -19,6 +19,7 @@ "column_break2", "from_date", "to_date", + "sales_order_status", "sales_orders_detail", "get_sales_orders", "sales_orders", @@ -301,13 +302,20 @@ "label": "Warehouses", "options": "Production Plan Material Request Warehouse", "read_only": 1 + }, + { + "depends_on": "eval: doc.get_items_from == \"Sales Order\"", + "fieldname": "sales_order_status", + "fieldtype": "Select", + "label": "Sales Order Status", + "options": "\nDraft\nOn Hold\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-26 13:00:54.335319", + "modified": "2020-11-03 15:19:18.270529", "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 a314a15c23..3833e86d27 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -571,6 +571,8 @@ def get_sales_orders(self): so_filter += " and so.customer = %(customer)s" if self.project: so_filter += " and so.project = %(project)s" + if self.sales_order_status: + so_filter += "and so.status = %(sales_order_status)s" if self.item_code: item_filter += " and so_item.item_code = %(item)s" @@ -594,8 +596,8 @@ def get_sales_orders(self): "customer": self.customer, "project": self.project, "item": self.item_code, - "company": self.company - + "company": self.company, + "sales_order_status": self.sales_order_status }, as_dict=1) return open_so diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index fa9d080cca..27335aa204 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -137,7 +137,8 @@ class TestProductionPlan(unittest.TestCase): 'from_date': so.transaction_date, 'to_date': so.transaction_date, 'customer': so.customer, - 'item_code': item + 'item_code': item, + 'sales_order_status': so.status }) sales_orders = get_sales_orders(pln) or {} sales_orders = [d.get('name') for d in sales_orders if d.get('name') == sales_order] From 574477187fca95792d8c8b6baae8143bd2db11bc Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 3 Nov 2020 20:22:48 +0530 Subject: [PATCH 089/154] chore: Pricing Rule with Item Price Test --- .../doctype/pricing_rule/test_pricing_rule.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 22a031c162..ec0a485bfc 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -484,6 +484,43 @@ class TestPricingRule(unittest.TestCase): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + def test_item_price_with_pricing_rule(self): + item = make_item("Water Flask") + make_item_price("Water Flask", "_Test Price List", 100) + + pricing_rule_record = { + "doctype": "Pricing Rule", + "title": "_Test Water Flask Rule", + "apply_on": "Item Code", + "items": [{ + "item_code": "Water Flask", + }], + "selling": 1, + "currency": "INR", + "rate_or_discount": "Rate", + "rate": 0, + "margin_type": "Percentage", + "margin_rate_or_amount": 2, + "company": "_Test Company" + } + rule = frappe.get_doc(pricing_rule_record) + rule.insert() + + si = create_sales_invoice(do_not_save=True, item_code="Water Flask") + si.selling_price_list = "_Test Price List" + si.save() + + # If rate in Rule is 0, give preference to Item Price if it exists + self.assertEqual(si.items[0].price_list_rate, 100) + self.assertEqual(si.items[0].margin_rate_or_amount, 2) + self.assertEqual(si.items[0].rate_with_margin, 102) + self.assertEqual(si.items[0].rate, 102) + + si.delete() + rule.delete() + frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() + item.delete() + def make_pricing_rule(**args): args = frappe._dict(args) From f2c895ddb5b9d7b5b8f07e1d590e07ff5a745b7c Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 4 Nov 2020 10:31:37 +0530 Subject: [PATCH 090/154] feat(uae vat 201): do not take zero and nil rated items in standard rated expense emiratewise --- .../report/uae_vat_201/test_uae_vat_201.py | 2 +- .../report/uae_vat_201/uae_vat_201.py | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index b0a21de613..daa69768c5 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -53,7 +53,7 @@ class TestUaeVat201(TestCase): "raw_amount": amount, "raw_vat_amount": vat, } - self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"],300) + self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"],100) self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"],5) self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200) self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"],10) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index c151c622a6..f2d5a5e113 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -118,14 +118,23 @@ def append_data(data, no, legend, amount, vat_amount): def get_total_emiratewise(filters): """Returns Emiratewise Amount and Taxes.""" - query_filters = get_filters(filters) - query_filters['docstatus'] = ['=', 1] - return frappe.db.get_all('Sales Invoice', - filters = query_filters, - fields = ['vat_emirate as emirate','sum(total)', 'sum(total_taxes_and_charges)'], - group_by='vat_emirate', - as_list=True - ) + conditions = get_conditions(filters) + try: + return frappe.db.sql(""" + select + s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges) + from + `tabSales Invoice Item` i inner join `tabSales Invoice` s + on + i.parent = s.name + where + s.docstatus = 1 and i.is_exempt != 1 and i.is_zero_rated != 1 + {where_conditions} + group by + s.vat_emirate; + """.format(where_conditions=conditions), filters) + except (IndexError, TypeError): + return 0 def get_emirates(): """Returns a List of emirates in the order that they are to be displayed.""" From bed4e85aec2b8475ab817916f0d357c6e977fe55 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 4 Nov 2020 10:44:22 +0530 Subject: [PATCH 091/154] fix(uae vat 201): date filters were not working --- .../report/uae_vat_201/uae_vat_201.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index f2d5a5e113..de727d2ecd 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -150,20 +150,20 @@ def get_emirates(): def get_filters(filters): """The conditions to be used to filter data to calculate the total sale.""" - query_filters = {} + query_filters = [] if filters.get("company"): - query_filters["company"] = ['=', filters['company']] + query_filters.append(["company", '=', filters['company']]) if filters.get("from_date"): - query_filters["posting_date"] = ['>=', filters['from_date']] + query_filters.append(["posting_date", '>=', filters['from_date']]) if filters.get("from_date"): - query_filters["posting_date"] = ['<=', filters['to_date']] + query_filters.append(["posting_date", '<=', filters['to_date']]) return query_filters def get_reverse_charge_total(filters): """Returns the sum of the total of each Purchase invoice made.""" query_filters = get_filters(filters) - query_filters['reverse_charge'] = ['=', 'Y'] - query_filters['docstatus'] = ['=', 1] + query_filters.append(['reverse_charge', '=', 'Y']) + query_filters.append(['docstatus', '=', 1]) try: return frappe.db.get_all('Purchase Invoice', filters = query_filters, @@ -193,9 +193,9 @@ def get_reverse_charge_tax(filters): def get_reverse_charge_recoverable_total(filters): """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" query_filters = get_filters(filters) - query_filters['reverse_charge'] = ['=', 'Y'] - query_filters['recoverable_reverse_charge'] = ['>', '0'] - query_filters['docstatus'] = ['=', 1] + query_filters.append(['reverse_charge', '=', 'Y']) + query_filters.append(['recoverable_reverse_charge', '>', '0']) + query_filters.append(['docstatus', '=', 1]) try: return frappe.db.get_all('Purchase Invoice', filters = query_filters, @@ -238,8 +238,8 @@ def get_conditions_join(filters): def get_standard_rated_expenses_total(filters): """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge.""" query_filters = get_filters(filters) - query_filters['recoverable_standard_rated_expenses'] = ['>', 0] - query_filters['docstatus'] = ['=', 1] + query_filters.append(['recoverable_standard_rated_expenses', '>', 0]) + query_filters.append(['docstatus', '=', 1]) try: return frappe.db.get_all('Purchase Invoice', filters = query_filters, @@ -253,8 +253,8 @@ def get_standard_rated_expenses_total(filters): def get_standard_rated_expenses_tax(filters): """Returns the sum of the tax of each Purchase invoice made.""" query_filters = get_filters(filters) - query_filters['recoverable_standard_rated_expenses'] = ['>', 0] - query_filters['docstatus'] = ['=', 1] + query_filters.append(['recoverable_standard_rated_expenses', '>', 0]) + query_filters.append(['docstatus', '=', 1]) try: return frappe.db.get_all('Purchase Invoice', filters = query_filters, @@ -268,8 +268,8 @@ def get_standard_rated_expenses_tax(filters): def get_tourist_tax_return_total(filters): """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return.""" query_filters = get_filters(filters) - query_filters['tourist_tax_return'] = ['>', 0] - query_filters['docstatus'] = ['=', 1] + query_filters.append(['tourist_tax_return', '>', 0]) + query_filters.append(['docstatus', '=', 1]) try: return frappe.db.get_all('Sales Invoice', filters = query_filters, @@ -283,8 +283,8 @@ def get_tourist_tax_return_total(filters): def get_tourist_tax_return_tax(filters): """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return.""" query_filters = get_filters(filters) - query_filters['tourist_tax_return'] = ['>', 0] - query_filters['docstatus'] = ['=', 1] + query_filters.append(['tourist_tax_return', '>', 0]) + query_filters.append(['docstatus', '=', 1]) try: return frappe.db.get_all('Sales Invoice', filters = query_filters, @@ -300,7 +300,7 @@ def get_zero_rated_total(filters): conditions = get_conditions(filters) try: return frappe.db.sql(""" - select + select sum(i.base_amount) as total from `tabSales Invoice Item` i inner join `tabSales Invoice` s From cdc17bb9f3c6b2f1eab818ed04ed587da58013b0 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 4 Nov 2020 19:28:55 +0530 Subject: [PATCH 092/154] fix: added code for testing --- .../doctype/sales_order/test_sales_order.py | 1 + .../delivery_note/test_delivery_note.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index a33d401b57..643e7cf38b 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1064,6 +1064,7 @@ def make_sales_order(**args): so.company = args.company or "_Test Company" so.customer = args.customer or "_Test Customer" so.currency = args.currency or "INR" + so.po_no = args.po_no or '12345' if args.selling_price_list: so.selling_price_list = args.selling_price_list diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 0168613415..9566af7b38 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -442,9 +442,15 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(dn.status, "To Bill") self.assertEqual(dn.per_billed, 0) + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(dn.po_no, so.po_no) + si = make_sales_invoice(dn.name) si.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(dn.po_no, si.po_no) + dn.load_from_db() self.assertEqual(dn.get("items")[0].billed_amt, 200) self.assertEqual(dn.per_billed, 100) @@ -461,16 +467,25 @@ class TestDeliveryNote(unittest.TestCase): si.insert() si.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(so.po_no, si.po_no) + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) dn1 = make_delivery_note(so.name) dn1.get("items")[0].qty = 2 dn1.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(so.po_no, dn1.po_no) + dn2 = make_delivery_note(so.name) dn2.get("items")[0].qty = 3 dn2.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(so.po_no, dn2.po_no) + dn1.load_from_db() self.assertEqual(dn1.get("items")[0].billed_amt, 200) self.assertEqual(dn1.per_billed, 100) @@ -492,9 +507,15 @@ class TestDeliveryNote(unittest.TestCase): dn1.get("items")[0].qty = 2 dn1.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(dn1.po_no, so.po_no) + si1 = make_sales_invoice(dn1.name) si1.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(dn1.po_no, si1.po_no) + dn1.load_from_db() self.assertEqual(dn1.per_billed, 100) @@ -502,10 +523,16 @@ class TestDeliveryNote(unittest.TestCase): si2.get("items")[0].qty = 4 si2.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(si2.po_no, so.po_no) + dn2 = make_delivery_note(so.name) dn2.get("items")[0].qty = 5 dn2.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(dn2.po_no, so.po_no) + dn1.load_from_db() self.assertEqual(dn1.get("items")[0].billed_amt, 200) self.assertEqual(dn1.per_billed, 100) @@ -525,9 +552,15 @@ class TestDeliveryNote(unittest.TestCase): si = make_sales_invoice(so.name) si.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(so.po_no, si.po_no) + dn = make_delivery_note(si.name) dn.submit() + # Testing if Customer's Purchase Order No was rightly copied + self.assertEqual(dn.po_no, si.po_no) + self.assertEqual(dn.get("items")[0].billed_amt, 1000) self.assertEqual(dn.per_billed, 100) self.assertEqual(dn.status, "Completed") From aa08fb971659ffd3e801db2954d06277238a6c25 Mon Sep 17 00:00:00 2001 From: igormbq Date: Wed, 4 Nov 2020 11:40:57 -0300 Subject: [PATCH 093/154] Add location on Asset to use make_demo --- erpnext/demo/data/asset.json | 21 ++++++++++++++------- erpnext/demo/data/location.json | 22 ++++++++++++++++++++++ erpnext/demo/setup/manufacture.py | 1 + erpnext/demo/user/stock.py | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 erpnext/demo/data/location.json diff --git a/erpnext/demo/data/asset.json b/erpnext/demo/data/asset.json index 23029ca5e3..44db2ae9e1 100644 --- a/erpnext/demo/data/asset.json +++ b/erpnext/demo/data/asset.json @@ -4,48 +4,55 @@ "item_code": "Computer", "gross_purchase_amount": 100000, "asset_owner": "Company", - "available_for_use_date": "2017-01-02" + "available_for_use_date": "2017-01-02", + "location": "Main Location" }, { "asset_name": "Macbook Air - 1", "item_code": "Computer", "gross_purchase_amount": 60000, "asset_owner": "Company", - "available_for_use_date": "2017-10-02" + "available_for_use_date": "2017-10-02", + "location": "Avg Location" }, { "asset_name": "Conferrence Table", "item_code": "Table", "gross_purchase_amount": 30000, "asset_owner": "Company", - "available_for_use_date": "2018-10-02" + "available_for_use_date": "2018-10-02", + "location": "Zany Location" }, { "asset_name": "Lunch Table", "item_code": "Table", "gross_purchase_amount": 20000, "asset_owner": "Company", - "available_for_use_date": "2018-06-02" + "available_for_use_date": "2018-06-02", + "location": "Fletcher Location" }, { "asset_name": "ERPNext", "item_code": "ERP", "gross_purchase_amount": 100000, "asset_owner": "Company", - "available_for_use_date": "2018-09-02" + "available_for_use_date": "2018-09-02", + "location":"Main Location" }, { "asset_name": "Chair 1", "item_code": "Chair", "gross_purchase_amount": 10000, "asset_owner": "Company", - "available_for_use_date": "2018-07-02" + "available_for_use_date": "2018-07-02", + "location": "Zany Location" }, { "asset_name": "Chair 2", "item_code": "Chair", "gross_purchase_amount": 10000, "asset_owner": "Company", - "available_for_use_date": "2018-07-02" + "available_for_use_date": "2018-07-02", + "location": "Avg Location" } ] diff --git a/erpnext/demo/data/location.json b/erpnext/demo/data/location.json new file mode 100644 index 0000000000..b521aa08c4 --- /dev/null +++ b/erpnext/demo/data/location.json @@ -0,0 +1,22 @@ +[ + { + "location_name": "Main Location", + "latitude": 40.0, + "longitude": 20.0 + }, + { + "location_name": "Avg Location", + "latitude": 63.0, + "longitude": 99.3 + }, + { + "location_name": "Zany Location", + "latitude": 47.5, + "longitude": 10.0 + }, + { + "location_name": "Fletcher Location", + "latitude": 100.90, + "longitude": 80 + } +] \ No newline at end of file diff --git a/erpnext/demo/setup/manufacture.py b/erpnext/demo/setup/manufacture.py index d3846369cd..7d6b5012ea 100644 --- a/erpnext/demo/setup/manufacture.py +++ b/erpnext/demo/setup/manufacture.py @@ -9,6 +9,7 @@ from erpnext.demo.domains import data from six import iteritems def setup_data(): + import_json("Location") import_json("Asset Category") setup_item() setup_workstation() diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py index f95a6b8331..d44da7d127 100644 --- a/erpnext/demo/user/stock.py +++ b/erpnext/demo/user/stock.py @@ -79,7 +79,7 @@ def make_stock_reconciliation(): if item.qty: item.qty = item.qty - round(random.randint(1, item.qty)) try: - stock_reco.insert(ignore_permissions=True) + stock_reco.insert(ignore_permissions=True, ignore_mandatory=True) stock_reco.submit() frappe.db.commit() except OpeningEntryAccountError: From 403822afb315b80c1c48c05ccf9749ba08ff73ae Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 5 Nov 2020 16:24:33 +0530 Subject: [PATCH 094/154] fix: correcting description field in taxes and charges for accounts that have account number + account name --- erpnext/public/js/controllers/accounts.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 6e97d811fc..b1ffcfb6ad 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -146,18 +146,21 @@ cur_frm.cscript.account_head = function(doc, cdt, cdn) { if(!d.charge_type && d.account_head){ frappe.msgprint(__("Please select Charge Type first")); frappe.model.set_value(cdt, cdn, "account_head", ""); - } else if(d.account_head && d.charge_type!=="Actual") { + } else if(d.account_head) { frappe.call({ type:"GET", method: "erpnext.controllers.accounts_controller.get_tax_rate", args: {"account_head":d.account_head}, callback: function(r) { - frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); - frappe.model.set_value(cdt, cdn, "description", r.message.account_name); + if(d.charge_type!=="Actual"){ + frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); + frappe.model.set_value(cdt, cdn, "description", r.message.account_name); + } + else if(d.charge_type == 'Actual'){ + frappe.model.set_value(cdt, cdn, "description", r.message.account_name); + } } }) - } else if (d.charge_type == 'Actual' && d.account_head) { - frappe.model.set_value(cdt, cdn, "description", d.account_head.split(' - ')[0]); } } From 5b02d328269e2654f6253c45ddafe215121d2435 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 5 Nov 2020 18:04:14 +0530 Subject: [PATCH 095/154] fix: trailling spaces removed --- erpnext/public/js/controllers/accounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index b1ffcfb6ad..1bceec0547 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -155,7 +155,7 @@ cur_frm.cscript.account_head = function(doc, cdt, cdn) { if(d.charge_type!=="Actual"){ frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); frappe.model.set_value(cdt, cdn, "description", r.message.account_name); - } + } else if(d.charge_type == 'Actual'){ frappe.model.set_value(cdt, cdn, "description", r.message.account_name); } From 49cc57e76eb61bec1d05c2e6b4f7f15b12008204 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 5 Nov 2020 21:14:07 +0530 Subject: [PATCH 096/154] fix: Negative amount check for amounts --- erpnext/loan_management/doctype/loan/loan.json | 3 ++- .../doctype/loan_disbursement/loan_disbursement.json | 3 ++- .../doctype/loan_repayment/loan_repayment.json | 3 ++- erpnext/loan_management/doctype/pledge/pledge.json | 7 +++++-- .../doctype/proposed_pledge/proposed_pledge.json | 8 ++++++-- erpnext/loan_management/doctype/unpledge/unpledge.json | 4 +++- 6 files changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 23996afd21..e8ecf015c3 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -136,6 +136,7 @@ "fieldname": "loan_amount", "fieldtype": "Currency", "label": "Loan Amount", + "non_negative": 1, "options": "Company:company:default_currency" }, { @@ -351,7 +352,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-27 23:37:02.785940", + "modified": "2020-11-05 10:04:00.762975", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json index 89f671bcc0..cd5df4d3cd 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json @@ -41,6 +41,7 @@ "fieldname": "disbursed_amount", "fieldtype": "Currency", "label": "Disbursed Amount", + "non_negative": 1, "options": "Company:company:default_currency", "reqd": 1 }, @@ -130,7 +131,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-16 10:04:26.229216", + "modified": "2020-11-06 10:04:30.882322", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Disbursement", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 60b20369dc..2b5df4be24 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -108,6 +108,7 @@ "fieldname": "amount_paid", "fieldtype": "Currency", "label": "Amount Paid", + "non_negative": 1, "options": "Company:company:default_currency", "reqd": 1 }, @@ -230,7 +231,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-10 03:49:01.827593", + "modified": "2020-11-05 10:06:58.792841", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", diff --git a/erpnext/loan_management/doctype/pledge/pledge.json b/erpnext/loan_management/doctype/pledge/pledge.json index f22a21e3be..801e3a3117 100644 --- a/erpnext/loan_management/doctype/pledge/pledge.json +++ b/erpnext/loan_management/doctype/pledge/pledge.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-09-09 17:06:16.756573", "doctype": "DocType", "editable_grid": 1, @@ -49,7 +50,8 @@ "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Quantity" + "label": "Quantity", + "non_negative": 1 }, { "fieldname": "loan_security_price", @@ -86,7 +88,8 @@ } ], "istable": 1, - "modified": "2019-12-03 10:59:58.001421", + "links": [], + "modified": "2020-11-05 10:07:15.424937", "modified_by": "Administrator", "module": "Loan Management", "name": "Pledge", diff --git a/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json b/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json index aee7c2ced5..3e7e778a25 100644 --- a/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json +++ b/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-08-29 22:29:37.628178", "doctype": "DocType", "editable_grid": 1, @@ -39,7 +40,8 @@ "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Quantity" + "label": "Quantity", + "non_negative": 1 }, { "fieldname": "loan_security", @@ -56,8 +58,10 @@ "read_only": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-12-02 10:23:11.498308", + "links": [], + "modified": "2020-11-05 10:07:37.542344", "modified_by": "Administrator", "module": "Loan Management", "name": "Proposed Pledge", diff --git a/erpnext/loan_management/doctype/unpledge/unpledge.json b/erpnext/loan_management/doctype/unpledge/unpledge.json index ee192d7377..00356685eb 100644 --- a/erpnext/loan_management/doctype/unpledge/unpledge.json +++ b/erpnext/loan_management/doctype/unpledge/unpledge.json @@ -52,6 +52,7 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Quantity", + "non_negative": 1, "reqd": 1 }, { @@ -62,9 +63,10 @@ "read_only": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-05-06 10:50:18.448552", + "modified": "2020-11-05 10:07:28.106961", "modified_by": "Administrator", "module": "Loan Management", "name": "Unpledge", From a2bff7fbfcbdcda76143dab00468655232105fd7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 5 Nov 2020 21:14:29 +0530 Subject: [PATCH 097/154] fix: Penalty amount calculation fix --- .../loan_management/doctype/loan/test_loan.py | 60 +++++++++---------- .../doctype/loan_repayment/loan_repayment.py | 4 +- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 1634697939..10a7b1143d 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -142,19 +142,19 @@ class TestLoan(unittest.TestCase): no_of_days = date_diff(last_date, first_date) + 1 - accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ - / (days_in_year(get_datetime(first_date).year) * 100) + accrued_interest_amount = flt((loan.loan_amount * loan.rate_of_interest * no_of_days) + / (days_in_year(get_datetime(first_date).year) * 100), 2) make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), 111118.68) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), 111119) repayment_entry.save() repayment_entry.submit() - penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) - self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2)) + penalty_amount = (accrued_interest_amount * 5 * 25) / 100 + self.assertEquals(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount']) @@ -162,8 +162,8 @@ class TestLoan(unittest.TestCase): total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount'] self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) - self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid - - penalty_amount - total_interest_paid, 2)) + self.assertEquals(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - + penalty_amount - total_interest_paid, 0)) def test_loan_closure(self): pledge = [{ @@ -184,10 +184,10 @@ class TestLoan(unittest.TestCase): no_of_days = date_diff(last_date, first_date) + 1 - # Adding 6 since repayment is made 5 days late after due date + # Adding 5 since repayment is made 5 days late after due date # and since payment type is loan closure so interest should be considered for those - # 6 days as well though in grace period - no_of_days += 6 + # 5 days as well though in grace period + no_of_days += 5 accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ / (days_in_year(get_datetime(first_date).year) * 100) @@ -195,7 +195,7 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() @@ -292,7 +292,7 @@ class TestLoan(unittest.TestCase): no_of_days = date_diff(last_date, first_date) + 1 - no_of_days += 6 + no_of_days += 5 accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ / (days_in_year(get_datetime(first_date).year) * 100) @@ -300,7 +300,7 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() request_loan_closure(loan.name) @@ -318,7 +318,7 @@ class TestLoan(unittest.TestCase): self.assertEqual(loan.status, 'Closed') self.assertEquals(sum(pledged_qty.values()), 0) - amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertTrue(amounts['pending_principal_amount'] < 0) self.assertEquals(amounts['payable_principal_amount'], 0.0) self.assertEqual(amounts['interest_amount'], 0) @@ -392,7 +392,7 @@ class TestLoan(unittest.TestCase): no_of_days = date_diff(last_date, first_date) + 1 - no_of_days += 6 + no_of_days += 5 accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ / (days_in_year(get_datetime(first_date).year) * 100) @@ -400,9 +400,9 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', @@ -412,7 +412,7 @@ class TestLoan(unittest.TestCase): loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") - amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertTrue(amounts['pending_principal_amount'] < 0.0) def test_partial_unaccrued_interest_payment(self): @@ -443,9 +443,9 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), paid_amount) repayment_entry.submit() @@ -480,15 +480,15 @@ class TestLoan(unittest.TestCase): amounts = calculate_amounts(loan.name, add_days(last_date, 1)) paid_amount = amounts['interest_amount']/2 - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), paid_amount) repayment_entry.submit() # 30 days - grace period - penalty_days = 30 - 5 + penalty_days = 30 - 4 penalty_applicable_amount = flt(amounts['interest_amount']/2, 2) - penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days)/365, 2) + penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days), 2) process = process_loan_interest_accrual_for_demand_loans(posting_date = '2019-11-30') calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', @@ -514,7 +514,7 @@ class TestLoan(unittest.TestCase): last_date = '2019-10-30' no_of_days = date_diff(last_date, first_date) + 1 - no_of_days += 6 + no_of_days += 5 accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ / (days_in_year(get_datetime(first_date).year) * 100) @@ -523,7 +523,7 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) # repay 50 less so that it can be automatically written off - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount - 50)) repayment_entry.submit() @@ -533,7 +533,7 @@ class TestLoan(unittest.TestCase): self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) - amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50) request_loan_closure(loan.name) @@ -558,7 +558,7 @@ class TestLoan(unittest.TestCase): last_date = '2019-10-30' no_of_days = date_diff(last_date, first_date) + 1 - no_of_days += 6 + no_of_days += 5 accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ / (days_in_year(get_datetime(first_date).year) * 100) @@ -567,7 +567,7 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) # repay 100 less so that it can be automatically written off - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount + accrued_interest_amount - 100)) repayment_entry.submit() @@ -577,13 +577,13 @@ class TestLoan(unittest.TestCase): self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) - amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100) we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount']) we.submit() - amounts = calculate_amounts(loan.name, add_days(last_date, 6)) + amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 63d388daf5..7216c8b813 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -341,10 +341,10 @@ def get_amounts(amounts, against_loan, posting_date): due_date = add_days(entry.posting_date, 1) no_of_late_days = date_diff(posting_date, - add_days(due_date, loan_type_details.grace_period_in_days)) + add_days(due_date, loan_type_details.grace_period_in_days)) + 1 if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular': - penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365 + penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days) total_pending_interest += entry.interest_amount payable_principal_amount += entry.payable_principal_amount From 1145b3796c50308c529955a9654c3afdd27e6470 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 5 Nov 2020 21:21:40 +0530 Subject: [PATCH 098/154] fix: Remove accrual type from process --- .../loan_interest_accrual/loan_interest_accrual.py | 5 +++-- .../process_loan_interest_accrual.json | 9 +-------- .../process_loan_interest_accrual.py | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index d0b957de56..70d1453df4 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -135,7 +135,7 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte for loan in open_loans: calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_interest, accrual_type) -def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_interest, term_loan=None, loan_type=None): +def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_interest, term_loan=None, loan_type=None, accrual_type="Regular"): curr_date = posting_date or add_days(nowdate(), 1) term_loans = get_term_loans(curr_date, term_loan, loan_type) @@ -154,7 +154,8 @@ def make_accrual_interest_entry_for_term_loans(posting_date, process_loan_intere 'payable_principal': loan.principal_amount, 'process_loan_interest': process_loan_interest, 'repayment_schedule_name': loan.payment_entry, - 'posting_date': posting_date + 'posting_date': posting_date, + 'accrual_type': accrual_type }) make_loan_interest_accrual_entry(args) diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index c1296f759f..4c354e6721 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -10,7 +10,6 @@ "loan_type", "loan", "process_type", - "accrual_type", "amended_from" ], "fields": [ @@ -48,18 +47,12 @@ "hidden": 1, "label": "Process Type", "read_only": 1 - }, - { - "fieldname": "accrual_type", - "fieldtype": "Select", - "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-26 07:14:31.491249", + "modified": "2020-11-05 10:49:35.657728", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index 1eeb18b7ea..f0522657eb 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -20,7 +20,7 @@ class ProcessLoanInterestAccrual(Document): if (not self.loan or not loan_doc.is_term_loan) and self.process_type != 'Term Loans': make_accrual_interest_entry_for_demand_loans(self.posting_date, self.name, - open_loans = open_loans, loan_type = self.loan_type, accrual_type=self.accrual_type) + open_loans = open_loans, loan_type = self.loan_type) if (not self.loan or loan_doc.is_term_loan) and self.process_type != 'Demand Loans': make_accrual_interest_entry_for_term_loans(self.posting_date, self.name, term_loan=self.loan, From 815341d45457e7c6ea7bde896d3f7b20b82d37eb Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 5 Nov 2020 16:57:23 +0100 Subject: [PATCH 099/154] feat: validate fiscal year --- .../regional/germany/utils/datev/datev_csv.py | 2 +- erpnext/regional/report/datev/datev.py | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index aae734f8e2..fb7ca71e79 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -104,7 +104,7 @@ def get_header(filters, csv_class): # L = Tax client number (Mandantennummer) datev_settings.get('client_number', '00000'), # M = Start of the fiscal year (Wirtschaftsjahresbeginn) - frappe.utils.formatdate(frappe.defaults.get_user_default('year_start_date'), 'yyyyMMdd'), + frappe.utils.formatdate(filters.get('fiscal_year_start'), 'yyyyMMdd'), # N = Length of account numbers (Sachkontenlänge) datev_settings.get('account_number_length', '4'), # O = Transaction batch start date (YYYYMMDD) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index dd818e6054..04f8928c6a 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -13,7 +13,7 @@ import json import frappe from frappe import _ from six import string_types -from erpnext.regional.germany.utils.datev.datev_csv import download_csv_files_as_zip, get_datev_csv +from erpnext.accounts.utils import get_fiscal_year from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames COLUMNS = [ @@ -98,21 +98,33 @@ def execute(filters=None): def validate(filters): """Make sure all mandatory filters and settings are present.""" - if not filters.get('company'): + company = filters.get('company') + if not company: frappe.throw(_('Company is a mandatory filter.')) - if not filters.get('from_date'): + from_date = filters.get('from_date') + if not from_date: frappe.throw(_('From Date is a mandatory filter.')) - if not filters.get('to_date'): + to_date = filters.get('to_date') + if not to_date: frappe.throw(_('To Date is a mandatory filter.')) + validate_fiscal_year(from_date, to_date, company) + try: frappe.get_doc('DATEV Settings', filters.get('company')) except frappe.DoesNotExistError: frappe.throw(_('Please create DATEV Settings for Company {}.').format(filters.get('company'))) +def validate_fiscal_year(from_date, to_date, company): + from_fiscal_year = get_fiscal_year(date=from_date, company=company) + to_fiscal_year = get_fiscal_year(date=to_date, company=company) + if from_fiscal_year != to_fiscal_year: + frappe.throw(_('Dates {} and {} are not in the same fiscal year.').format(from_date, to_date)) + + def get_transactions(filters, as_dict=1): """ Get a list of accounting entries. @@ -317,9 +329,13 @@ def download_datev_csv(filters): filters = json.loads(filters) validate(filters) + company = filters.get('company') + + fiscal_year = get_fiscal_year(date=filters.get('from_date'), company=company) + filters['fiscal_year_start'] = fiscal_year[1] # set chart of accounts used - coa = frappe.get_value('Company', filters.get('company'), 'chart_of_accounts') + coa = frappe.get_value('Company', company, 'chart_of_accounts') filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '') transactions = get_transactions(filters) From f91d82b5389972d0f952115cb41a30089a054c6b Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 5 Nov 2020 17:28:39 +0100 Subject: [PATCH 100/154] fix: imports --- erpnext/regional/report/datev/datev.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 04f8928c6a..738806321c 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -14,6 +14,7 @@ import frappe from frappe import _ from six import string_types from erpnext.accounts.utils import get_fiscal_year +from erpnext.regional.germany.utils.datev.datev_csv import download_csv_files_as_zip, get_datev_csv from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames COLUMNS = [ From 06ba92be7a2f53e39e0adeecbbfd006388c554cb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 4 Nov 2020 12:57:25 +0530 Subject: [PATCH 101/154] fix: Added link of bank reconciliation and clearance in accounting desk page --- erpnext/accounts/desk_page/accounting/accounting.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index b0371e7c09..1d9236b81e 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -43,7 +43,7 @@ { "hidden": 0, "label": "Bank Statement", - "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -98,7 +98,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-10-08 20:31:46.022470", + "modified": "2020-11-04 13:05:37.922171", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", @@ -108,7 +108,7 @@ "pin_to_top": 0, "shortcuts": [ { - "label": "Chart of Accounts", + "label": "Chart Of Accounts", "link_to": "Account", "type": "DocType" }, From 0b4946ba2799750eae91174d755d36dbffe68bb4 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 6 Nov 2020 13:08:53 +0530 Subject: [PATCH 102/154] fix: Add missing links - Added Bank Transaction Statement Entry and Bank Statement Settings --- erpnext/accounts/desk_page/accounting/accounting.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 1d9236b81e..9172792411 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -43,7 +43,7 @@ { "hidden": 0, "label": "Bank Statement", - "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -98,7 +98,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-11-04 13:05:37.922171", + "modified": "2020-11-06 13:05:58.650150", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", From 3e69756e154a5b3cc1ca467f1c8d9bf7e77a2e60 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 6 Nov 2020 14:25:09 +0530 Subject: [PATCH 103/154] fix: Add better remarks for Loan GL entries --- .../doctype/loan_disbursement/loan_disbursement.py | 4 ++-- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 6 ++++-- .../doctype/loan_repayment/loan_repayment.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index 949e1412e2..bda439fb7c 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -123,7 +123,7 @@ class LoanDisbursement(AccountsController): "debit_in_account_currency": self.disbursed_amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": "Against Loan:" + self.against_loan, + "remarks": _("Disbursement against loan:") + self.against_loan, "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, @@ -139,7 +139,7 @@ class LoanDisbursement(AccountsController): "credit_in_account_currency": self.disbursed_amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": "Against Loan:" + self.against_loan, + "remarks": _("Disbursement against loan:") + self.against_loan, "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 70d1453df4..22ff6663d3 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -49,7 +49,8 @@ class LoanInterestAccrual(AccountsController): "debit_in_account_currency": self.interest_amount, "against_voucher_type": "Loan", "against_voucher": self.loan, - "remarks": _("Against Loan:") + self.loan, + "remarks": _("Interest accrued from {0} to {1} against loan: {2}").format( + get_last_accural_date(self.loan), self.posting_date, self.loan), "cost_center": erpnext.get_default_cost_center(self.company), "posting_date": self.posting_date }) @@ -65,7 +66,8 @@ class LoanInterestAccrual(AccountsController): "credit_in_account_currency": self.interest_amount, "against_voucher_type": "Loan", "against_voucher": self.loan, - "remarks": _("Against Loan:") + self.loan, + "remarks": ("Interest accrued from {0} to {1} against loan: {2}").format( + get_last_accural_date(self.loan), self.posting_date, self.loan), "cost_center": erpnext.get_default_cost_center(self.company), "posting_date": self.posting_date }) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 7216c8b813..e478cb8d43 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -217,7 +217,7 @@ class LoanRepayment(AccountsController): "debit_in_account_currency": self.penalty_amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Against Loan:") + self.against_loan, + "remarks": _("Repayment against loan:") + self.against_loan, "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, @@ -233,7 +233,7 @@ class LoanRepayment(AccountsController): "credit_in_account_currency": self.penalty_amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Against Loan:") + self.against_loan, + "remarks": _("Repayment against loan:") + self.against_loan, "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, From 3899079bb9b0ef733e5b1100359afd7d94cfba5a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 6 Nov 2020 17:39:54 +0530 Subject: [PATCH 104/154] fix: Loan seurity unpledge msg improvement --- .../loan_security_shortfall.py | 2 +- .../loan_security_unpledge/loan_security_unpledge.py | 12 ++++++++++-- .../process_loan_interest_accrual.json | 9 ++++++++- .../process_loan_interest_accrual.py | 4 ++-- 4 files changed, 21 insertions(+), 6 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 0f42bde3c4..8ec0bfb62c 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 @@ -22,7 +22,7 @@ def update_shortfall_status(loan, security_value): if security_value >= loan_security_shortfall.shortfall_amount: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, { "status": "Completed", - "shortfall_value": loan_security_shortfall.shortfall_amount}) + "shortfall_amount": loan_security_shortfall.shortfall_amount}) else: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "shortfall_amount", loan_security_shortfall.shortfall_amount - security_value) diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index 5d4447bf2b..c29f325bfc 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -67,10 +67,18 @@ class LoanSecurityUnpledge(Document): security_value += qty_after_unpledge * current_price if not security_value and flt(pending_principal_amount, 2) > 0: - frappe.throw("Cannot Unpledge, loan to value ratio is breaching") + self._throw(security_value, pending_principal_amount, ltv_ratio) if security_value and flt(pending_principal_amount/security_value) * 100 > ltv_ratio: - frappe.throw("Cannot Unpledge, loan to value ratio is breaching") + self._throw(security_value, pending_principal_amount, ltv_ratio) + + def _throw(self, security_value, pending_principal_amount, ltv_ratio): + msg = _("Loan Security Value after unpledge is {0}").format(frappe.bold(security_value)) + msg += '
' + msg += _("Pending principal amount is {0}").format(frappe.bold(flt(pending_principal_amount, 2))) + msg += '
' + msg += _("Loan To Security Value ratio must always be {0}").format(frappe.bold(ltv_ratio)) + frappe.throw(msg, title=_("Loan To Value ratio breach")) def on_update_after_submit(self): self.approve() diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index 4c354e6721..b78c3ba5d8 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -10,6 +10,7 @@ "loan_type", "loan", "process_type", + "accrual_type", "amended_from" ], "fields": [ @@ -47,12 +48,18 @@ "hidden": 1, "label": "Process Type", "read_only": 1 + }, + { + "fieldname": "accrual_type", + "fieldtype": "Select", + "label": "Accrual Type", + "options": "Regular\nRepayment\nDisbursement" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-05 10:49:35.657728", + "modified": "2020-11-06 04:43:56.581670", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py index f0522657eb..11333dc2aa 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py @@ -20,11 +20,11 @@ class ProcessLoanInterestAccrual(Document): if (not self.loan or not loan_doc.is_term_loan) and self.process_type != 'Term Loans': make_accrual_interest_entry_for_demand_loans(self.posting_date, self.name, - open_loans = open_loans, loan_type = self.loan_type) + open_loans = open_loans, loan_type = self.loan_type, accrual_type=self.accrual_type) if (not self.loan or loan_doc.is_term_loan) and self.process_type != 'Demand Loans': make_accrual_interest_entry_for_term_loans(self.posting_date, self.name, term_loan=self.loan, - loan_type=self.loan_type) + loan_type=self.loan_type, accrual_type=self.accrual_type) def process_loan_interest_accrual_for_demand_loans(posting_date=None, loan_type=None, loan=None, accrual_type="Regular"): From 4d99d695a38d40087e2f9051084f1c9b9016f187 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Fri, 6 Nov 2020 23:33:19 +0530 Subject: [PATCH 105/154] feat(uae vat 201): update desk page entry --- erpnext/accounts/desk_page/accounting/accounting.json | 8 ++++---- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 64e2b246a7..de9ed9fca1 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -83,7 +83,7 @@ { "hidden": 0, "label": "Value-Added Tax (VAT UAE)", - "links": "[\n {\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Modules", @@ -103,8 +103,8 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-10-08 20:31:46.022470", - "modified_by": "Administrator", + "modified": "2020-11-06 23:30:39.515679", + "modified_by": "moha@gmail.com", "module": "Accounts", "name": "Accounting", "onboarding": "Accounts", @@ -158,4 +158,4 @@ "type": "Dashboard" } ] -} +} \ No newline at end of file diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index de727d2ecd..b0614238ba 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -30,14 +30,12 @@ def get_columns(): "label": _("Amount (AED)"), "fieldtype": "Currency", "width": 125, - "options": "currency" }, { "fieldname": "vat_amount", "label": _("VAT Amount (AED)"), "fieldtype": "Currency", "width": 150, - "options": "currency" } ] From c5aad7b6e5f8419c4a2201fff60e20f05362897a Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Fri, 6 Nov 2020 23:36:50 +0530 Subject: [PATCH 106/154] feat(UAE VAT 201): reorder desk page listing --- erpnext/accounts/desk_page/accounting/accounting.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index de9ed9fca1..993d2a625b 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -83,7 +83,7 @@ { "hidden": 0, "label": "Value-Added Tax (VAT UAE)", - "links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n\n]" } ], "category": "Modules", @@ -103,7 +103,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-11-06 23:30:39.515679", + "modified": "2020-11-06 23:34:50.325014", "modified_by": "moha@gmail.com", "module": "Accounts", "name": "Accounting", From 2ad015450ed74a558e693b31ab0b3d27eb82752b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 7 Nov 2020 00:14:40 +0530 Subject: [PATCH 107/154] fix: Remarks fix --- erpnext/loan_management/doctype/loan/loan.js | 3 ++- .../loan_interest_accrual/loan_interest_accrual.json | 10 +++++++++- .../loan_interest_accrual/loan_interest_accrual.py | 11 +++++++---- .../doctype/loan_repayment/loan_repayment.py | 7 +++---- .../process_loan_interest_accrual.json | 6 ++++-- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 8d101b862a..28af3a9c41 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -7,7 +7,8 @@ frappe.ui.form.on('Loan', { setup: function(frm) { frm.make_methods = { 'Loan Disbursement': function() { frm.trigger('make_loan_disbursement') }, - 'Loan Security Unpledge': function() { frm.trigger('create_loan_security_unpledge') } + 'Loan Security Unpledge': function() { frm.trigger('create_loan_security_unpledge') }, + 'Loan Write Off': function() { frm.trigger('make_loan_write_off_entry') } } }, onload: function (frm) { diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 893609e0c7..d6bf08ac51 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -27,6 +27,7 @@ "section_break_15", "process_loan_interest_accrual", "repayment_schedule_name", + "last_accrual_date", "amended_from" ], "fields": [ @@ -163,13 +164,20 @@ "fieldtype": "Currency", "label": "Penalty Amount", "options": "Company:company:default_currency" + }, + { + "fieldname": "last_accrual_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Last Accrual Date", + "read_only": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-11 11:17:44.704694", + "modified": "2020-11-06 13:22:40.197916", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 22ff6663d3..d642400cdc 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -22,6 +22,9 @@ class LoanInterestAccrual(AccountsController): if not self.interest_amount and not self.payable_principal_amount: frappe.throw(_("Interest Amount or Principal Amount is mandatory")) + if not self.last_accrual_date: + self.last_accrual_date = get_last_accrual_date(self.loan) + def on_submit(self): self.make_gl_entries() @@ -50,7 +53,7 @@ class LoanInterestAccrual(AccountsController): "against_voucher_type": "Loan", "against_voucher": self.loan, "remarks": _("Interest accrued from {0} to {1} against loan: {2}").format( - get_last_accural_date(self.loan), self.posting_date, self.loan), + self.last_accrual_date, self.posting_date, self.loan), "cost_center": erpnext.get_default_cost_center(self.company), "posting_date": self.posting_date }) @@ -67,7 +70,7 @@ class LoanInterestAccrual(AccountsController): "against_voucher_type": "Loan", "against_voucher": self.loan, "remarks": ("Interest accrued from {0} to {1} against loan: {2}").format( - get_last_accural_date(self.loan), self.posting_date, self.loan), + self.last_accrual_date, self.posting_date, self.loan), "cost_center": erpnext.get_default_cost_center(self.company), "posting_date": self.posting_date }) @@ -213,13 +216,13 @@ def make_loan_interest_accrual_entry(args): def get_no_of_days_for_interest_accural(loan, posting_date): - last_interest_accrual_date = get_last_accural_date(loan.name) + last_interest_accrual_date = get_last_accrual_date(loan.name) no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1 return no_of_days -def get_last_accural_date(loan): +def get_last_accrual_date(loan): last_posting_date = frappe.db.sql(""" SELECT MAX(posting_date) from `tabLoan Interest Accrual` WHERE loan = %s and docstatus = 1""", (loan)) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index e478cb8d43..bb91abd628 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -14,7 +14,7 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans -from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest, get_last_accural_date +from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import get_per_day_interest, get_last_accrual_date class LoanRepayment(AccountsController): @@ -78,7 +78,7 @@ class LoanRepayment(AccountsController): if self.total_interest_paid > self.interest_payable: if not self.is_term_loan: # get last loan interest accrual date - last_accrual_date = get_last_accural_date(self.against_loan) + last_accrual_date = get_last_accrual_date(self.against_loan) # get posting date upto which interest has to be accrued per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, @@ -87,7 +87,6 @@ class LoanRepayment(AccountsController): no_of_days = flt(flt(self.total_interest_paid - self.interest_payable, precision)/per_day_interest, 0) - 1 - posting_date = add_days(last_accrual_date, no_of_days) # book excess interest paid @@ -368,7 +367,7 @@ def get_amounts(amounts, against_loan, posting_date): if due_date: pending_days = date_diff(posting_date, due_date) + 1 else: - last_accrual_date = get_last_accural_date(against_loan_doc.name) + last_accrual_date = get_last_accrual_date(against_loan_doc.name) pending_days = date_diff(posting_date, last_accrual_date) + 1 if pending_days > 0: diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index b78c3ba5d8..828df2e35f 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -52,14 +52,16 @@ { "fieldname": "accrual_type", "fieldtype": "Select", + "hidden": 1, "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement" + "options": "Regular\nRepayment\nDisbursement", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-06 04:43:56.581670", + "modified": "2020-11-06 13:28:51.478909", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", From dd94587ef807f6d5e9797a8ddf1db9ff4d9a14ff Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 7 Nov 2020 17:08:30 +0530 Subject: [PATCH 108/154] fix: Loan write off precision issue --- .../loan_interest_accrual.json | 3 ++- .../doctype/loan_write_off/loan_write_off.js | 18 ++++++++++++++++++ .../doctype/loan_write_off/loan_write_off.py | 6 ++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index d6bf08ac51..f157f0df8f 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -142,6 +142,7 @@ "read_only": 1 }, { + "depends_on": "eval:doc.is_term_loan", "fieldname": "paid_principal_amount", "fieldtype": "Currency", "label": "Paid Principal Amount", @@ -177,7 +178,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-06 13:22:40.197916", + "modified": "2020-11-07 05:49:25.448875", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js index cc5cd0d3a0..4e3319c208 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.js @@ -4,6 +4,12 @@ {% include 'erpnext/loan_management/loan_common.js' %}; frappe.ui.form.on('Loan Write Off', { + loan: function(frm) { + frm.trigger('show_pending_principal_amount'); + }, + onload: function(frm) { + frm.trigger('show_pending_principal_amount'); + }, refresh: function(frm) { frm.set_query('write_off_account', function(){ return { @@ -14,5 +20,17 @@ frappe.ui.form.on('Loan Write Off', { } } }); + }, + show_pending_principal_amount: function(frm) { + if (frm.doc.loan && frm.doc.docstatus === 0) { + frappe.db.get_value('Loan', frm.doc.loan, ['total_payment', 'total_interest_payable', + 'total_principal_paid', 'written_off_amount'], function(values) { + frm.set_df_property('write_off_amount', 'description', + "Pending principal amount is " + cstr(flt(values.total_payment - values.total_interest_payable + - values.total_principal_paid - values.written_off_amount, 2))); + frm.refresh_field('write_off_amount'); + }); + + } } }); diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py index 823e6a904f..6e402edcd2 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import getdate, flt +from frappe.utils import getdate, flt, cint from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries @@ -19,10 +19,12 @@ class LoanWriteOff(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) def validate_write_off_amount(self): + precision = cint(frappe.db.get_default("currency_precision")) or 2 total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid','total_interest_payable', 'written_off_amount']) - pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount) + pending_principal_amount = flt(flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount), + precision) if self.write_off_amount > pending_principal_amount: frappe.throw(_("Write off amount cannot be greater than pending principal amount")) From d53abf194e900aa36d96d9793a889923fbd1901c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 9 Nov 2020 18:34:38 +0530 Subject: [PATCH 109/154] fix: Party for loan ledger entries --- .../loan_management/doctype/loan/loan_list.js | 16 ++++++++++++++++ .../loan_disbursement/loan_disbursement.py | 2 -- .../loan_interest_accrual.py | 2 -- .../doctype/loan_repayment/loan_repayment.py | 4 ---- .../doctype/loan_write_off/loan_write_off.py | 2 -- 5 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 erpnext/loan_management/doctype/loan/loan_list.js diff --git a/erpnext/loan_management/doctype/loan/loan_list.js b/erpnext/loan_management/doctype/loan/loan_list.js new file mode 100644 index 0000000000..6591b72996 --- /dev/null +++ b/erpnext/loan_management/doctype/loan/loan_list.js @@ -0,0 +1,16 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.listview_settings['Loan'] = { + get_indicator: function(doc) { + var status_color = { + "Draft": "red", + "Sanctioned": "blue", + "Disbursed": "orange", + "Partially Disbursed": "yellow", + "Loan Closure Requested": "green", + "Closed": "green" + }; + return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + }, +}; diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index bda439fb7c..233862bcfe 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -141,8 +141,6 @@ class LoanDisbursement(AccountsController): "against_voucher": self.against_loan, "remarks": _("Disbursement against loan:") + self.against_loan, "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, "posting_date": self.disbursement_date }) ) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index d642400cdc..d17f5af490 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -62,8 +62,6 @@ class LoanInterestAccrual(AccountsController): gle_map.append( self.get_gl_dict({ "account": self.interest_income_account, - "party_type": self.applicant_type, - "party": self.applicant, "against": self.loan_account, "credit": self.interest_amount, "credit_in_account_currency": self.interest_amount, diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index bb91abd628..a8887d7b24 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -234,8 +234,6 @@ class LoanRepayment(AccountsController): "against_voucher": self.against_loan, "remarks": _("Repayment against loan:") + self.against_loan, "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, "posting_date": getdate(self.posting_date) }) ) @@ -251,8 +249,6 @@ class LoanRepayment(AccountsController): "against_voucher": self.against_loan, "remarks": _("Against Loan:") + self.against_loan, "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, "posting_date": getdate(self.posting_date) }) ) diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py index 6e402edcd2..54a3f2cbb1 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.py @@ -63,8 +63,6 @@ class LoanWriteOff(AccountsController): "against_voucher": self.loan, "remarks": _("Against Loan:") + self.loan, "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, "posting_date": getdate(self.posting_date) }) ) From 7915a3acaeb00ffe38a7aeffaa83d4d283b7ae36 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 9 Nov 2020 18:37:28 +0530 Subject: [PATCH 110/154] chore: Update translations (#23856) Co-authored-by: frappe --- erpnext/translations/af.csv | 8 +++-- erpnext/translations/am.csv | 8 +++-- erpnext/translations/ar.csv | 8 +++-- erpnext/translations/bg.csv | 8 +++-- erpnext/translations/bn.csv | 8 +++-- erpnext/translations/bs.csv | 8 +++-- erpnext/translations/ca.csv | 8 +++-- erpnext/translations/cs.csv | 8 +++-- erpnext/translations/da.csv | 8 +++-- erpnext/translations/de.csv | 8 +++-- erpnext/translations/el.csv | 8 +++-- erpnext/translations/es.csv | 60 ++++++++++++++++++---------------- erpnext/translations/es_ar.csv | 1 - erpnext/translations/es_gt.csv | 1 - erpnext/translations/et.csv | 8 +++-- erpnext/translations/fa.csv | 8 +++-- erpnext/translations/fi.csv | 8 +++-- erpnext/translations/fr.csv | 10 ++++-- erpnext/translations/gu.csv | 8 +++-- erpnext/translations/he.csv | 8 +++-- erpnext/translations/hi.csv | 8 +++-- erpnext/translations/hr.csv | 8 +++-- erpnext/translations/hu.csv | 8 +++-- erpnext/translations/id.csv | 8 +++-- erpnext/translations/is.csv | 8 +++-- erpnext/translations/it.csv | 8 +++-- erpnext/translations/ja.csv | 8 +++-- erpnext/translations/km.csv | 8 +++-- erpnext/translations/kn.csv | 8 +++-- erpnext/translations/ko.csv | 8 +++-- erpnext/translations/ku.csv | 8 +++-- erpnext/translations/lo.csv | 8 +++-- erpnext/translations/lt.csv | 8 +++-- erpnext/translations/lv.csv | 8 +++-- erpnext/translations/mk.csv | 8 +++-- erpnext/translations/ml.csv | 8 +++-- erpnext/translations/mr.csv | 8 +++-- erpnext/translations/ms.csv | 8 +++-- erpnext/translations/my.csv | 8 +++-- erpnext/translations/nl.csv | 8 +++-- erpnext/translations/no.csv | 8 +++-- erpnext/translations/pl.csv | 8 +++-- erpnext/translations/ps.csv | 8 +++-- erpnext/translations/pt.csv | 8 +++-- erpnext/translations/pt_br.csv | 1 + erpnext/translations/ro.csv | 8 +++-- erpnext/translations/ru.csv | 8 +++-- erpnext/translations/rw.csv | 8 +++-- erpnext/translations/si.csv | 8 +++-- erpnext/translations/sk.csv | 8 +++-- erpnext/translations/sl.csv | 8 +++-- erpnext/translations/sq.csv | 8 +++-- erpnext/translations/sr.csv | 8 +++-- erpnext/translations/sr_sp.csv | 1 - erpnext/translations/sv.csv | 8 +++-- erpnext/translations/sw.csv | 8 +++-- erpnext/translations/ta.csv | 8 +++-- erpnext/translations/te.csv | 8 +++-- erpnext/translations/th.csv | 8 +++-- erpnext/translations/tr.csv | 8 +++-- erpnext/translations/uk.csv | 8 +++-- erpnext/translations/ur.csv | 8 +++-- erpnext/translations/uz.csv | 8 +++-- erpnext/translations/vi.csv | 8 +++-- erpnext/translations/zh.csv | 8 +++-- erpnext/translations/zh_tw.csv | 8 +++-- 66 files changed, 400 insertions(+), 154 deletions(-) diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index 07f81e9979..45435d8600 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -4238,7 +4238,6 @@ Download as JSON,Laai af as Json, End date can not be less than start date,Einddatum kan nie minder wees as die begin datum nie, For Default Supplier (Optional),Vir Standaardverskaffer (opsioneel), From date cannot be greater than To date,Vanaf datum kan nie groter wees as Datum, -Get items from,Kry items van, Group by,Groep By, In stock,In voorraad, Item name,Item naam, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Aankooporders, Purchase Receipt Trends,Aankoopontvangstendense, Purchase Register,Aankoopregister, Quotation Trends,Aanhalingstendense, -Quoted Item Comparison,Genoteerde Item Vergelyking, Received Items To Be Billed,Items ontvang om gefaktureer te word, Qty to Order,Hoeveelheid om te bestel, Requested Items To Be Transferred,Gevraagde items wat oorgedra moet word, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Die item waarna verwys word Therapy Session overlaps with {0},Terapiesessie oorvleuel met {0}, Therapy Sessions Overlapping,Terapiesessies oorvleuel, Therapy Plans,Terapieplanne, +"Item Code, warehouse, quantity are required on row {0}","Itemkode, pakhuis, hoeveelheid word in ry {0} vereis", +Get Items from Material Requests against this Supplier,Kry items uit materiaalversoeke teen hierdie verskaffer, +Enable European Access,Aktiveer Europese toegang, +Creating Purchase Order ...,Skep tans bestelling ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Kies 'n verskaffer uit die verstekverskaffers van die onderstaande items. By seleksie sal 'n bestelling slegs gemaak word teen items wat tot die geselekteerde verskaffer behoort., +Row #{}: You must select {} serial numbers for item {}.,Ry # {}: u moet {} reeksnommers vir item {} kies., diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv index 1d0533fd99..554b0a54f1 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -4238,7 +4238,6 @@ Download as JSON,እንደ ጆንሰን አውርድ ፡፡, End date can not be less than start date,የማብቂያ ቀን ከመጀመሪያ ቀን ያነሰ መሆን አይችልም, For Default Supplier (Optional),ነባሪ አቅራቢ (አማራጭ), From date cannot be greater than To date,ቀን ቀን ወደ በላይ ሊሆን አይችልም ከ, -Get items from,ከ ንጥሎችን ያግኙ, Group by,ቡድን በ, In stock,ለሽያጭ የቀረበ እቃ, Item name,ንጥል ስም, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ትዕዛዝ በመታየት ላይ ይግዙ, Purchase Receipt Trends,የግዢ ደረሰኝ በመታየት ላይ ያሉ, Purchase Register,የግዢ ይመዝገቡ, Quotation Trends,በትዕምርተ ጥቅስ አዝማሚያዎች, -Quoted Item Comparison,የተጠቀሰ ንጥል ንጽጽር, Received Items To Be Billed,ተቀብሏል ንጥሎች እንዲከፍሉ ለማድረግ, Qty to Order,ለማዘዝ ብዛት, Requested Items To Be Transferred,ተጠይቋል ንጥሎች መወሰድ, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,የተጠቀሰው ንጥ Therapy Session overlaps with {0},ቴራፒ ክፍለ-ጊዜ ከ {0} ጋር ይደራረባል, Therapy Sessions Overlapping,ቴራፒ ክፍለ-ጊዜዎች መደራረብ, Therapy Plans,የሕክምና ዕቅዶች, +"Item Code, warehouse, quantity are required on row {0}",የእቃ ኮድ ፣ መጋዘን ፣ ብዛት በረድፍ {0} ላይ ያስፈልጋሉ, +Get Items from Material Requests against this Supplier,በዚህ አቅራቢ ላይ እቃዎችን ከቁሳዊ ጥያቄዎች ያግኙ, +Enable European Access,የአውሮፓ መዳረሻን ያንቁ, +Creating Purchase Order ...,የግዢ ትዕዛዝ በመፍጠር ላይ ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",ከዚህ በታች ካሉ ዕቃዎች ነባሪ አቅራቢዎች አቅራቢ ይምረጡ። በምርጫ ወቅት ለተመረጠው አቅራቢ ብቻ በሆኑ ዕቃዎች ላይ የግዢ ትዕዛዝ ይደረጋል።, +Row #{}: You must select {} serial numbers for item {}.,ረድፍ # {}: {} ለንጥል ተከታታይ ቁጥሮች {} መምረጥ አለብዎት።, diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index de75e893c7..91a9da9f16 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -4238,7 +4238,6 @@ Download as JSON,تنزيل باسم Json, End date can not be less than start date,تاريخ النهاية لا يمكن أن يكون اقل من تاريخ البدء\n
\nEnd Date can not be less than Start Date, For Default Supplier (Optional),للمورد الافتراضي (اختياري), From date cannot be greater than To date,(من تاريخ) لا يمكن أن يكون أكبر (الي التاريخ), -Get items from,الحصول على البنود من, Group by,المجموعة حسب, In stock,في المخزن, Item name,اسم السلعة, @@ -8549,7 +8548,6 @@ Purchase Order Trends,اتجهات امر الشراء, Purchase Receipt Trends,شراء اتجاهات الإيصال, Purchase Register,سجل شراء, Quotation Trends,مؤشرات المناقصة, -Quoted Item Comparison,مقارنة بند المناقصة, Received Items To Be Billed,العناصر الواردة إلى أن توصف, Qty to Order,الكمية للطلب, Requested Items To Be Transferred,العناصر المطلوبة على أن يتم تحويلها, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,العنصر المشار Therapy Session overlaps with {0},تتداخل جلسة العلاج مع {0}, Therapy Sessions Overlapping,جلسات العلاج متداخلة, Therapy Plans,خطط العلاج, +"Item Code, warehouse, quantity are required on row {0}",مطلوب رمز الصنف والمستودع والكمية في الصف {0}, +Get Items from Material Requests against this Supplier,الحصول على عناصر من طلبات المواد ضد هذا المورد, +Enable European Access,تمكين الوصول الأوروبي, +Creating Purchase Order ...,إنشاء أمر شراء ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",حدد موردًا من الموردين الافتراضيين للعناصر أدناه. عند التحديد ، سيتم إجراء طلب الشراء مقابل العناصر التي تنتمي إلى المورد المحدد فقط., +Row #{}: You must select {} serial numbers for item {}.,الصف # {}: يجب تحديد {} الأرقام التسلسلية للعنصر {}., diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv index 79be6dfc19..15278a6a40 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -4238,7 +4238,6 @@ Download as JSON,Изтеглете като JSON, End date can not be less than start date,Крайна дата не може да бъде по-малка от началната дата, For Default Supplier (Optional),За доставчик по подразбиране (по избор), From date cannot be greater than To date,От дата не може да бъде по-голямо от до дата, -Get items from,Вземете елементи от, Group by,Групирай по, In stock,В наличност, Item name,Име на артикул, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Поръчката Trends, Purchase Receipt Trends,Покупка Квитанция Trends, Purchase Register,Покупка Регистрация, Quotation Trends,Оферта Тенденции, -Quoted Item Comparison,Сравнение на редове от оферти, Received Items To Be Billed,"Приети артикули, които да се фактирират", Qty to Order,Количество към поръчка, Requested Items To Be Transferred,Желани артикули да бъдат прехвърлени, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Позицията, пос Therapy Session overlaps with {0},Терапевтичната сесия се припокрива с {0}, Therapy Sessions Overlapping,Терапевтични сесии Припокриване, Therapy Plans,Планове за терапия, +"Item Code, warehouse, quantity are required on row {0}","Код на артикул, склад, количество се изискват на ред {0}", +Get Items from Material Requests against this Supplier,Вземете артикули от заявки за материали срещу този доставчик, +Enable European Access,Активирайте европейския достъп, +Creating Purchase Order ...,Създаване на поръчка ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Изберете доставчик от доставчиците по подразбиране на елементите по-долу. При избора ще бъде направена Поръчка за покупка срещу артикули, принадлежащи само на избрания Доставчик.", +Row #{}: You must select {} serial numbers for item {}.,Ред № {}: Трябва да изберете {} серийни номера за артикул {}., diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv index 1e7413b1cd..cf0971667b 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -4238,7 +4238,6 @@ Download as JSON,জসন হিসাবে ডাউনলোড করুন End date can not be less than start date,শেষ তারিখ শুরু তারিখ থেকে কম হতে পারে না, For Default Supplier (Optional),ডিফল্ট সরবরাহকারীর জন্য (ঐচ্ছিক), From date cannot be greater than To date,তারিখ থেকে তারিখের চেয়ে বেশি হতে পারে না, -Get items from,থেকে আইটেম পান, Group by,গ্রুপ দ্বারা, In stock,স্টক ইন, Item name,আইটেম নাম, @@ -8549,7 +8548,6 @@ Purchase Order Trends,অর্ডার প্রবণতা ক্রয়, Purchase Receipt Trends,কেনার রসিদ প্রবণতা, Purchase Register,ক্রয় নিবন্ধন, Quotation Trends,উদ্ধৃতি প্রবণতা, -Quoted Item Comparison,উদ্ধৃত আইটেম তুলনা, Received Items To Be Billed,গৃহীত চলছে বিল তৈরি করা, Qty to Order,অর্ডার Qty, Requested Items To Be Transferred,অনুরোধ করা চলছে স্থানান্তর করা, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} দ্বার Therapy Session overlaps with {0},থেরাপি সেশন {0 with দিয়ে ওভারল্যাপ করে, Therapy Sessions Overlapping,থেরাপি সেশনস ওভারল্যাপিং, Therapy Plans,থেরাপি পরিকল্পনা, +"Item Code, warehouse, quantity are required on row {0}","আইটেম কোড, গুদাম, পরিমাণ সারিতে প্রয়োজন {0}", +Get Items from Material Requests against this Supplier,এই সরবরাহকারীর বিরুদ্ধে উপাদান অনুরোধগুলি থেকে আইটেমগুলি পান, +Enable European Access,ইউরোপীয় অ্যাক্সেস সক্ষম করুন, +Creating Purchase Order ...,ক্রয় ক্রম তৈরি করা হচ্ছে ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","নীচের আইটেমগুলির ডিফল্ট সরবরাহকারী থেকে কোনও সরবরাহকারী নির্বাচন করুন। নির্বাচনের সময়, কেবলমাত্র নির্বাচিত সরবরাহকারীর অন্তর্ভুক্ত আইটেমগুলির বিরুদ্ধে ক্রয় আদেশ দেওয়া হবে।", +Row #{}: You must select {} serial numbers for item {}.,সারি # {}: আইটেমের জন্য আপনাকে অবশ্যই}} ক্রমিক সংখ্যা নির্বাচন করতে হবে {}।, diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv index 4fec5e0cc8..6ef445a1af 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -4238,7 +4238,6 @@ Download as JSON,Preuzmi kao JSON, End date can not be less than start date,Datum završetka ne može biti manja od početnog datuma, For Default Supplier (Optional),Za podrazumevani dobavljač, From date cannot be greater than To date,Od datuma ne može biti veća od To Date, -Get items from,Get stavke iz, Group by,Group By, In stock,Na zalihama, Item name,Naziv artikla, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Trendovi narudžbenica kupnje, Purchase Receipt Trends,Račun kupnje trendovi, Purchase Register,Kupnja Registracija, Quotation Trends,Trendovi ponude, -Quoted Item Comparison,Citirano Stavka Poređenje, Received Items To Be Billed,Primljeni Proizvodi se naplaćuje, Qty to Order,Količina za narudžbu, Requested Items To Be Transferred,Traženi stavki za prijenos, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Stavka na koju se poziva {0 Therapy Session overlaps with {0},Sjednica terapije preklapa se sa {0}, Therapy Sessions Overlapping,Preklapanje terapijskih sesija, Therapy Plans,Planovi terapije, +"Item Code, warehouse, quantity are required on row {0}","Šifra artikla, skladište, količina su obavezni na retku {0}", +Get Items from Material Requests against this Supplier,Nabavite predmete od materijalnih zahtjeva protiv ovog dobavljača, +Enable European Access,Omogućiti evropski pristup, +Creating Purchase Order ...,Kreiranje narudžbenice ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Odaberite dobavljača od zadanih dobavljača dolje navedenih stavki. Nakon odabira, narudžbenica će se izvršiti samo za proizvode koji pripadaju odabranom dobavljaču.", +Row #{}: You must select {} serial numbers for item {}.,Red # {}: Morate odabrati {} serijske brojeve za stavku {}., diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv index 58e6edb0a8..18fa52a2b7 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -4238,7 +4238,6 @@ Download as JSON,Descarregueu com a Json, End date can not be less than start date,Data de finalització no pot ser inferior a data d'inici, For Default Supplier (Optional),Per proveïdor predeterminat (opcional), From date cannot be greater than To date,Des de la data no pot ser superior a la data, -Get items from,Obtenir articles de, Group by,Agrupar per, In stock,En estoc, Item name,Nom de l'article, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Compra Tendències Sol·licitar, Purchase Receipt Trends,Purchase Receipt Trends, Purchase Register,Compra de Registre, Quotation Trends,Quotation Trends, -Quoted Item Comparison,Citat article Comparació, Received Items To Be Billed,Articles rebuts per a facturar, Qty to Order,Quantitat de comanda, Requested Items To Be Transferred,Articles sol·licitats per a ser transferits, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,L'element a què fa ref Therapy Session overlaps with {0},La sessió de teràpia es coincideix amb {0}, Therapy Sessions Overlapping,Sessions de teràpia superposades, Therapy Plans,Plans de teràpia, +"Item Code, warehouse, quantity are required on row {0}","El codi de l'article, el magatzem, la quantitat són obligatoris a la fila {0}", +Get Items from Material Requests against this Supplier,Obteniu articles de sol·licituds de material contra aquest proveïdor, +Enable European Access,Activa l'accés europeu, +Creating Purchase Order ...,S'està creant una comanda de compra ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Seleccioneu un proveïdor dels proveïdors predeterminats dels articles següents. En seleccionar-lo, es farà una Comanda de compra únicament contra articles pertanyents al Proveïdor seleccionat.", +Row #{}: You must select {} serial numbers for item {}.,Fila núm. {}: Heu de seleccionar {} números de sèrie de l'element {}., diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv index 43fd472b41..705e471d27 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -4238,7 +4238,6 @@ Download as JSON,Stáhnout jako JSON, End date can not be less than start date,Datum ukončení nesmí být menší než datum zahájení, For Default Supplier (Optional),Výchozí dodavatel (volitelné), From date cannot be greater than To date,Od Datum nemůže být větší než Datum, -Get items from,Položka získaná z, Group by,Seskupit podle, In stock,Na skladě, Item name,Název položky, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Nákupní objednávka trendy, Purchase Receipt Trends,Doklad o koupi Trendy, Purchase Register,Nákup Register, Quotation Trends,Uvozovky Trendy, -Quoted Item Comparison,Citoval Položka Porovnání, Received Items To Be Billed,"Přijaté položek, které mají být účtovány", Qty to Order,Množství k objednávce, Requested Items To Be Transferred,Požadované položky mají být převedeny, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Položka, na kterou odkazu Therapy Session overlaps with {0},Terapie se překrývá s {0}, Therapy Sessions Overlapping,Terapeutické relace se překrývají, Therapy Plans,Terapeutické plány, +"Item Code, warehouse, quantity are required on row {0}","Na řádku {0} je vyžadován kód položky, sklad, množství", +Get Items from Material Requests against this Supplier,Získejte položky z materiálových požadavků vůči tomuto dodavateli, +Enable European Access,Povolit evropský přístup, +Creating Purchase Order ...,Vytváření nákupní objednávky ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Vyberte dodavatele z výchozích dodavatelů níže uvedených položek. Při výběru bude provedena objednávka pouze na položky patřící vybranému dodavateli., +Row #{}: You must select {} serial numbers for item {}.,Řádek # {}: Musíte vybrat {} sériová čísla pro položku {}., diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv index 7222daf77f..c0d0146694 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -4238,7 +4238,6 @@ Download as JSON,Download som JSON, End date can not be less than start date,Slutdato kan ikke være mindre end startdato, For Default Supplier (Optional),For standardleverandør (valgfrit), From date cannot be greater than To date,Fra dato ikke kan være større end til dato, -Get items from,Hent varer fra, Group by,Sortér efter, In stock,På lager, Item name,Varenavn, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Indkøbsordre Trends, Purchase Receipt Trends,Købskvittering Tendenser, Purchase Register,Indkøb Register, Quotation Trends,Tilbud trends, -Quoted Item Comparison,Sammenligning Citeret Vare, Received Items To Be Billed,Modtagne varer skal faktureres, Qty to Order,Antal til ordre, Requested Items To Be Transferred,"Anmodet Varer, der skal overføres", @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Den vare, der henvises til Therapy Session overlaps with {0},Terapisession overlapper med {0}, Therapy Sessions Overlapping,Terapisessioner overlapper hinanden, Therapy Plans,Terapiplaner, +"Item Code, warehouse, quantity are required on row {0}","Varekode, lager, antal kræves i række {0}", +Get Items from Material Requests against this Supplier,Få varer fra materialeanmodninger mod denne leverandør, +Enable European Access,Aktiver europæisk adgang, +Creating Purchase Order ...,Opretter indkøbsordre ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Vælg en leverandør fra standardleverandørerne af nedenstående varer. Ved valg foretages en indkøbsordre kun mod varer, der tilhører den valgte leverandør.", +Row #{}: You must select {} serial numbers for item {}.,Række nr. {}: Du skal vælge {} serienumre for varen {}., diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index b65c9436ef..ca03a787cd 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -4238,7 +4238,6 @@ Download as JSON,Als JSON herunterladen, End date can not be less than start date,Das Enddatum darf nicht kleiner als das Startdatum sein, For Default Supplier (Optional),Für Standardlieferanten (optional), From date cannot be greater than To date,Das Ab-Datum kann nicht größer als das Bis-Datum sein, -Get items from,Holen Sie Elemente aus, Group by,Gruppieren nach, In stock,Auf Lager, Item name,Artikelname, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Entwicklung Lieferantenaufträge, Purchase Receipt Trends,Trendanalyse Kaufbelege, Purchase Register,Übersicht über Einkäufe, Quotation Trends,Trendanalyse Angebote, -Quoted Item Comparison,Vergleich angebotener Artikel, Received Items To Be Billed,"Von Lieferanten gelieferte Artikel, die noch abgerechnet werden müssen", Qty to Order,Zu bestellende Menge, Requested Items To Be Transferred,"Angeforderte Artikel, die übertragen werden sollen", @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Der Artikel, auf den {0} - Therapy Session overlaps with {0},Die Therapiesitzung überschneidet sich mit {0}, Therapy Sessions Overlapping,Überlappende Therapiesitzungen, Therapy Plans,Therapiepläne, +"Item Code, warehouse, quantity are required on row {0}","Artikelcode, Lager, Menge sind in Zeile {0} erforderlich.", +Get Items from Material Requests against this Supplier,Erhalten Sie Artikel aus Materialanfragen gegen diesen Lieferanten, +Enable European Access,Ermöglichen Sie den europäischen Zugang, +Creating Purchase Order ...,Bestellung anlegen ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Wählen Sie einen Lieferanten aus den Standardlieferanten der folgenden Artikel aus. Bei der Auswahl erfolgt eine Bestellung nur für Artikel, die dem ausgewählten Lieferanten gehören.", +Row #{}: You must select {} serial numbers for item {}.,Zeile # {}: Sie müssen {} Seriennummern für Artikel {} auswählen., diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv index 2db4ac8c1c..acf5db5d46 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -4238,7 +4238,6 @@ Download as JSON,Λήψη ως JSON, End date can not be less than start date,Η ημερομηνία λήξης δεν μπορεί να είναι προγενέστερη της ημερομηνίας έναρξης, For Default Supplier (Optional),Για προεπιλεγμένο προμηθευτή (προαιρετικό), From date cannot be greater than To date,Από την ημερομηνία αυτή δεν μπορεί να είναι μεταγενέστερη από την έως ημερομηνία, -Get items from,Πάρτε τα στοιχεία από, Group by,Ομαδοποίηση κατά, In stock,Σε απόθεμα, Item name,Όνομα είδους, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Τάσεις παραγγελίας αγοράς, Purchase Receipt Trends,Τάσεις αποδεικτικού παραλαβής αγοράς, Purchase Register,Ταμείο αγορών, Quotation Trends,Τάσεις προσφορών, -Quoted Item Comparison,Εισηγμένες Στοιχείο Σύγκριση, Received Items To Be Billed,Είδη που παραλήφθηκαν και πρέπει να τιμολογηθούν, Qty to Order,Ποσότητα για παραγγελία, Requested Items To Be Transferred,Είδη που ζητήθηκε να μεταφερθούν, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Το στοιχείο πο Therapy Session overlaps with {0},Η συνεδρία θεραπείας αλληλεπικαλύπτεται με {0}, Therapy Sessions Overlapping,Συνεδρίες συνεδρίας, Therapy Plans,Σχέδια θεραπείας, +"Item Code, warehouse, quantity are required on row {0}","Κωδικός είδους, αποθήκη, ποσότητα απαιτείται στη σειρά {0}", +Get Items from Material Requests against this Supplier,Λάβετε στοιχεία από αιτήματα υλικών έναντι αυτού του προμηθευτή, +Enable European Access,Ενεργοποίηση ευρωπαϊκής πρόσβασης, +Creating Purchase Order ...,Δημιουργία παραγγελίας αγοράς ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Επιλέξτε έναν προμηθευτή από τους προεπιλεγμένους προμηθευτές των παρακάτω στοιχείων. Κατά την επιλογή, μια εντολή αγοράς θα πραγματοποιείται έναντι αντικειμένων που ανήκουν στον επιλεγμένο Προμηθευτή μόνο.", +Row #{}: You must select {} serial numbers for item {}.,Σειρά # {}: Πρέπει να επιλέξετε {} σειριακούς αριθμούς για το στοιχείο {}., diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv index be195aad11..0f2259db21 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -421,7 +421,7 @@ Buildings,Edificios, Bundle items at time of sale.,Agrupe elementos al momento de la venta., Business Development Manager,Gerente de Desarrollo de Negocios, Buy,Comprar, -Buying,Comprando, +Buying,Compras, Buying Amount,Importe de compra, Buying Price List,Lista de precios de compra, Buying Rate,Tipo de Cambio de Compra, @@ -535,7 +535,7 @@ City/Town,Ciudad / Provincia, Claimed Amount,Cantidad reclamada, Clay,Arcilla, Clear filters,Filtros claros, -Clear values,Valores claros, +Clear values,Quitar valores, Clearance Date,Fecha de liquidación, Clearance Date not mentioned,Fecha de liquidación no definida, Clearance Date updated,Fecha de liquidación actualizada, @@ -728,7 +728,7 @@ Current Liabilities,Pasivo circulante, Current Qty,Cant. Actual, Current invoice {0} is missing,La factura actual {0} falta, Custom HTML,HTML Personalizado, -Custom?,Personalizado?, +Custom?,¿Personalizado?, Customer,Cliente, Customer Addresses And Contacts,Direcciones de clientes y contactos, Customer Contact,Contacto del Cliente, @@ -783,7 +783,7 @@ Default Activity Cost exists for Activity Type - {0},Existe una actividad de cos Default BOM ({0}) must be active for this item or its template,La lista de materiales (LdM) por defecto ({0}) debe estar activa para este producto o plantilla, Default BOM for {0} not found,BOM por defecto para {0} no encontrado, Default BOM not found for Item {0} and Project {1},La lista de materiales predeterminada no se encontró para el Elemento {0} y el Proyecto {1}, -Default Letter Head,Encabezado predeterminado, +Default Letter Head,Encabezado Predeterminado, Default Tax Template,Plantilla de impuesto predeterminado, Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,Unidad de medida predeterminada para el artículo {0} no se puede cambiar directamente porque ya ha realizado alguna transacción (s) con otra UOM. Usted tendrá que crear un nuevo elemento a utilizar un UOM predeterminado diferente., Default Unit of Measure for Variant '{0}' must be same as in Template '{1}',Unidad de medida predeterminada para variante '{0}' debe ser la mismo que en la plantilla '{1}', @@ -966,7 +966,7 @@ Equity,Patrimonio, Error Log,Registro de Errores, Error evaluating the criteria formula,Error al evaluar la fórmula de criterios, Error in formula or condition: {0},Error Fórmula o Condición: {0}, -Error: Not a valid id?,Error: No es un ID válido?, +Error: Not a valid id?,Error: ¿No es un ID válido?, Estimated Cost,Costo estimado, Evaluation,Evaluación, "Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:","Incluso si hay varias reglas de precios con mayor prioridad, se aplican entonces siguientes prioridades internas:", @@ -1338,7 +1338,7 @@ Issue Material,Distribuir materiales, Issued,Emitido, Issues,Incidencias, It is needed to fetch Item Details.,Se necesita a buscar Detalles del artículo., -Item,Productos, +Item,Producto, Item 1,Elemento 1, Item 2,Elemento 2, Item 3,Elemento 3, @@ -1426,7 +1426,7 @@ Last Purchase Price,Último precio de compra, Last Purchase Rate,Tasa de cambio de última compra, Latest,Más reciente, Latest price updated in all BOMs,Último precio actualizado en todas las Listas de Materiales, -Lead,Dirigir, +Lead,Iniciativa, Lead Count,Cuenta de Iniciativa, Lead Owner,Propietario de la iniciativa, Lead Owner cannot be same as the Lead,Propietario de Iniciativa no puede ser igual que el de la Iniciativa, @@ -1535,7 +1535,7 @@ Mark Attendance,Marcar Asistencia, Mark Half Day,Marcar medio día, Mark Present,Marcar Presente, Marketing,Márketing, -Marketing Expenses,GASTOS DE PUBLICIDAD, +Marketing Expenses,Gastos de Publicidad, Marketplace,Mercado, Marketplace Error,Error de Marketplace, Masters,Maestros, @@ -1867,7 +1867,7 @@ Parents Teacher Meeting Attendance,Padres Maestros Asistencia a la Reunión, Part-time,Tiempo parcial, Partially Depreciated,Despreciables Parcialmente, Partially Received,Parcialmente recibido, -Party,Partido, +Party,Tercero, Party Name,Nombre de Parte, Party Type,Tipo de entidad, Party Type and Party is mandatory for {0} account,Tipo de Tercero y Tercero es obligatorio para la Cuenta {0}, @@ -2027,7 +2027,7 @@ Please select Company first,"Por favor, seleccione primero la compañía", Please select Completion Date for Completed Asset Maintenance Log,Seleccione Fecha de Finalización para el Registro de Mantenimiento de Activos Completado, Please select Completion Date for Completed Repair,Seleccione Fecha de Finalización para la Reparación Completa, Please select Course,Por favor seleccione Curso, -Please select Drug,Seleccione Droga, +Please select Drug,Por favor seleccione fármaco, Please select Employee,Por favor selecciona Empleado, Please select Existing Company for creating Chart of Accounts,"Por favor, seleccione empresa ya existente para la creación del plan de cuentas", Please select Healthcare Service,Por favor seleccione Servicio de Salud, @@ -2118,7 +2118,7 @@ Please specify from/to range,"Por favor, especifique el rango (desde / hasta)", Please supply the specified items at the best possible rates,Por favor suministrar los elementos especificados en las mejores tasas posibles, Please update your status for this training event,Actualice su estado para este evento de capacitación., Please wait 3 days before resending the reminder.,Espere 3 días antes de volver a enviar el recordatorio., -Point of Sale,Punto de venta, +Point of Sale,Punto de Venta, Point-of-Sale,Punto de Venta (POS), Point-of-Sale Profile,Perfiles de punto de venta (POS), Portal,Portal, @@ -2627,7 +2627,7 @@ Sell,Vender, Selling,Ventas, Selling Amount,Cantidad de venta, Selling Price List,Lista de precios de venta, -Selling Rate,Tasa de ventas, +Selling Rate,Precio de venta, "Selling must be checked, if Applicable For is selected as {0}","'Ventas' debe ser seleccionada, si la opción: 'Aplicable para' esta seleccionado como {0}", Send Grant Review Email,Enviar correo electrónico de revisión de subvención, Send Now,Enviar ahora, @@ -2700,9 +2700,9 @@ Setup cheque dimensions for printing,Configurar dimensiones de cheque para la im Setup default values for POS Invoices,Configurar los valores predeterminados para facturas de POS, Setup mode of POS (Online / Offline),Modo de configuración de POS (Online / Offline), Setup your Institute in ERPNext,Configura tu instituto en ERPNext, -Share Balance,Compartir Saldo, +Share Balance,Balance de Acciones, Share Ledger,Share Ledger, -Share Management,Gestión de Compartir, +Share Management,Administración de Acciones, Share Transfer,Transferir Acciones, Share Type,Tipo de acción, Shareholder,Accionista, @@ -3302,7 +3302,7 @@ Where manufacturing operations are carried.,Dónde se realizan las operaciones d White,Blanco, Wire Transfer,Transferencia bancaria, WooCommerce Products,Productos WooCommerce, -Work In Progress,Trabajo en progreso, +Work In Progress,Trabajo en Proceso, Work Order,Orden de trabajo, Work Order already created for all items with BOM,Órden de Trabajo ya creada para todos los artículos con lista de materiales, Work Order cannot be raised against a Item Template,La Órden de Trabajo no puede levantarse contra una Plantilla de Artículo, @@ -3582,7 +3582,7 @@ Accounting Masters,Maestros Contables, Accounting Period overlaps with {0},El período contable se superpone con {0}, Activity,Actividad, Add / Manage Email Accounts.,Añadir / Administrar cuentas de correo electrónico., -Add Child,Agregar niño, +Add Child,Agregar hijo, Add Loan Security,Agregar seguridad de préstamo, Add Multiple,Añadir Multiple, Add Participants,Agregar Participantes, @@ -3706,7 +3706,7 @@ Description,Descripción, Designation,Puesto, Difference Value,Valor de diferencia, Dimension Filter,Filtro de dimensiones, -Disabled,Discapacitado, +Disabled,Deshabilitado, Disbursement and Repayment,Desembolso y reembolso, Distance cannot be greater than 4000 kms,La distancia no puede ser mayor a 4000 kms, Do you want to submit the material request,¿Quieres enviar la solicitud de material?, @@ -4193,7 +4193,7 @@ Total Income This Year,Ingresos totales este año, Barcode,Código de barras, Bold,Negrita, Center,Centro, -Clear,Claro, +Clear,Quitar, Comment,Comentario, Comments,Comentarios, DocType,DocType, @@ -4238,7 +4238,6 @@ Download as JSON,Descargar como JSON, End date can not be less than start date,la fecha final no puede ser inferior a fecha de Inicio, For Default Supplier (Optional),Para el proveedor predeterminado (opcional), From date cannot be greater than To date,La fecha 'Desde' no puede ser mayor que la fecha 'Hasta', -Get items from,Obtener artículos de, Group by,Agrupar por, In stock,En stock, Item name,Nombre del producto, @@ -4305,7 +4304,7 @@ Assets not created for {0}. You will have to create asset manually.,Activos no c Invalid Account,Cuenta no válida, Purchase Order Required,Orden de compra requerida, Purchase Receipt Required,Recibo de compra requerido, -Account Missing,Falta la cuenta, +Account Missing,Cuenta Faltante, Requested,Solicitado, Partially Paid,Parcialmente pagado, Invalid Account Currency,Moneda de la cuenta no válida, @@ -5600,7 +5599,7 @@ Call Log,Registro de llamadas, Received By,Recibido por, Caller Information,Información de la llamada, Contact Name,Nombre de contacto, -Lead ,Dirigir, +Lead ,Iniciativa, Lead Name,Nombre de la iniciativa, Ringing,Zumbido, Missed,Perdido, @@ -6619,7 +6618,7 @@ Employee External Work History,Historial de de trabajos anteriores, Total Experience,Experiencia total, Default Leave Policy,Política de Licencia Predeterminada, Default Salary Structure,Estructura de Salario Predeterminada, -Employee Group Table,Mesa de grupo de empleados, +Employee Group Table,Tabla de grupo de empleados, ERPNext User ID,ERP ID de usuario siguiente, Employee Health Insurance,Seguro de Salud para Empleados, Health Insurance Name,Nombre del Seguro de Salud, @@ -6838,10 +6837,10 @@ Employees,Empleados, Number Of Employees,Número de Empleados, Employee Details,Detalles del Empleado, Validate Attendance,Validar la Asistencia, -Salary Slip Based on Timesheet,Nomina basada en el Parte de Horas, +Salary Slip Based on Timesheet,Nomina basada horas, Select Payroll Period,Seleccione el Período de Nómina, Deduct Tax For Unclaimed Employee Benefits,Deducir Impuestos para beneficios de Empleados no Reclamados, -Deduct Tax For Unsubmitted Tax Exemption Proof,Deducir impuestos por prueba de exención de impuestos sin enviar, +Deduct Tax For Unsubmitted Tax Exemption Proof,Deducir impuestos por soporte de exención de impuestos sin enviar, Select Payment Account to make Bank Entry,Seleccionar la cuenta de pago para hacer la entrada del Banco, Salary Slips Created,Salario Slips creado, Salary Slips Submitted,Nómina Salarial Validada, @@ -7093,7 +7092,7 @@ Total Amount Paid,Cantidad Total Pagada, Loan Manager,Gerente de préstamos, Loan Info,Información del Préstamo, Rate of Interest,Tasa de interés, -Proposed Pledges,Promesas Propuestas, +Proposed Pledges,Prendas Propuestas, Maximum Loan Amount,Cantidad máxima del préstamo, Repayment Info,Información de la Devolución, Total Payable Interest,Interés Total a Pagar, @@ -7340,7 +7339,7 @@ Available Qty at Source Warehouse,Cantidad Disponible en Almacén Fuente, Available Qty at WIP Warehouse,Cantidad Disponible en Almacén WIP, Work Order Operation,Operación de Órden de Trabajo, Operation Description,Descripción de la operación, -Operation completed for how many finished goods?,Se completo la operación para la cantidad de productos terminados?, +Operation completed for how many finished goods?,¿Operación completada para cuántos productos terminados?, Work in Progress,Trabajo en proceso, Estimated Time and Cost,Tiempo estimado y costo, Planned Start Time,Hora prevista de inicio, @@ -8452,7 +8451,7 @@ Asset Depreciation Ledger,Libro Mayor Depreciacion de Activos, Asset Depreciations and Balances,Depreciaciones de Activos y Saldos, Available Stock for Packing Items,Inventario Disponible de Artículos de Embalaje, Bank Clearance Summary,Resumen de Cambios Bancarios, -Bank Remittance,Remesa bancaria, +Bank Remittance,Giro Bancario, Batch Item Expiry Status,Estado de Caducidad de Lote de Productos, Batch-Wise Balance History,Historial de Saldo por Lotes, BOM Explorer,BOM Explorer, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Tendencias de ordenes de compra, Purchase Receipt Trends,Tendencias de recibos de compra, Purchase Register,Registro de compras, Quotation Trends,Tendencias de Presupuestos, -Quoted Item Comparison,Comparación de artículos de Cotización, Received Items To Be Billed,Recepciones por facturar, Qty to Order,Cantidad a Solicitar, Requested Items To Be Transferred,Artículos solicitados para ser transferidos, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,El artículo al que hace re Therapy Session overlaps with {0},La sesión de terapia se superpone con {0}, Therapy Sessions Overlapping,Superposición de sesiones de terapia, Therapy Plans,Planes de terapia, +"Item Code, warehouse, quantity are required on row {0}","El código de artículo, el almacén y la cantidad son obligatorios en la fila {0}", +Get Items from Material Requests against this Supplier,Obtener artículos de solicitudes de material contra este proveedor, +Enable European Access,Habilitar el acceso europeo, +Creating Purchase Order ...,Creando orden de compra ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Seleccione un proveedor de los proveedores predeterminados de los artículos a continuación. En la selección, se realizará una orden de compra contra los artículos que pertenecen al proveedor seleccionado únicamente.", +Row #{}: You must select {} serial numbers for item {}.,Fila # {}: debe seleccionar {} números de serie para el artículo {}., diff --git a/erpnext/translations/es_ar.csv b/erpnext/translations/es_ar.csv index 9bdcba1195..1fd87236f1 100644 --- a/erpnext/translations/es_ar.csv +++ b/erpnext/translations/es_ar.csv @@ -1,5 +1,4 @@ Employee {0} on Half day on {1},"Empleado {0}, media jornada el día {1}", -Item,Producto, Communication,Comunicacion, Components,Componentes, Cheque Size,Tamaño de Cheque, diff --git a/erpnext/translations/es_gt.csv b/erpnext/translations/es_gt.csv index efd2daaa31..29ce2b8a87 100644 --- a/erpnext/translations/es_gt.csv +++ b/erpnext/translations/es_gt.csv @@ -1,4 +1,3 @@ -Item,Producto, Lead Time Days,Tiempo de ejecución en días, Outstanding,Pendiente, Outstanding Amount,Saldo Pendiente, diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv index b778eff4d6..ba32187c68 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -4238,7 +4238,6 @@ Download as JSON,Laadige alla kui Json, End date can not be less than start date,End Date saa olla väiksem kui alguskuupäev, For Default Supplier (Optional),Vaikimisi tarnija (valikuline), From date cannot be greater than To date,Siit kuupäev ei saa olla suurem kui kuupäev, -Get items from,Võta esemed, Group by,Group By, In stock,Laos, Item name,Toote nimi, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Ostutellimuse Trends, Purchase Receipt Trends,Ostutšekk Trends, Purchase Register,Ostu Registreeri, Quotation Trends,Tsitaat Trends, -Quoted Item Comparison,Tsiteeritud Punkt võrdlus, Received Items To Be Billed,Saadud objekte arve, Qty to Order,Kogus tellida, Requested Items To Be Transferred,Taotletud üleantavate, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Üksus, millele viitab {0} Therapy Session overlaps with {0},Teraapiaseanss kattub rakendusega {0}, Therapy Sessions Overlapping,Teraapiaseansid kattuvad, Therapy Plans,Teraapiakavad, +"Item Code, warehouse, quantity are required on row {0}","Real {0} on nõutav kaubakood, ladu ja kogus", +Get Items from Material Requests against this Supplier,Hankige esemeid selle tarnija vastu esitatud materjalitaotlustest, +Enable European Access,Luba Euroopa juurdepääs, +Creating Purchase Order ...,Ostutellimuse loomine ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Valige allpool olevate üksuste vaiketarnijatest tarnija. Valiku alusel tehakse ostutellimus ainult valitud tarnijale kuuluvate kaupade kohta., +Row #{}: You must select {} serial numbers for item {}.,Rida nr {}: peate valima {} üksuse seerianumbrid {}., diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv index 0e4d19a01b..4a7c979499 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -4238,7 +4238,6 @@ Download as JSON,به عنوان JSON بارگیری کنید, End date can not be less than start date,تاریخ پایان نمی تواند کمتر از تاریخ شروع, For Default Supplier (Optional),برای تامین کننده پیش فرض (اختیاری), From date cannot be greater than To date,از تاریخ نمی تواند بیشتر از تاریخ باشد, -Get items from,گرفتن اقلام از, Group by,گروه توسط, In stock,در انبار, Item name,نام آیتم, @@ -8549,7 +8548,6 @@ Purchase Order Trends,خرید سفارش روند, Purchase Receipt Trends,روند رسید خرید, Purchase Register,خرید ثبت نام, Quotation Trends,روند نقل قول, -Quoted Item Comparison,مورد نقل مقایسه, Received Items To Be Billed,دریافت گزینه هایی که صورتحساب, Qty to Order,تعداد سفارش, Requested Items To Be Transferred,آیتم ها درخواست می شود منتقل, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,مورد ارجاع شده Therapy Session overlaps with {0},جلسه درمانی با {0} همپوشانی دارد, Therapy Sessions Overlapping,جلسات درمانی با هم تداخل دارند, Therapy Plans,برنامه های درمانی, +"Item Code, warehouse, quantity are required on row {0}",کد مورد ، انبار ، مقدار در ردیف {0} لازم است, +Get Items from Material Requests against this Supplier,مواردی را از درخواستهای ماده در برابر این تأمین کننده دریافت کنید, +Enable European Access,دسترسی اروپا را فعال کنید, +Creating Purchase Order ...,در حال ایجاد سفارش خرید ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",از تامین کنندگان پیش فرض موارد زیر یک تأمین کننده انتخاب کنید. هنگام انتخاب ، سفارش خرید فقط در مورد کالاهای متعلق به تنها تامین کننده انتخاب شده انجام می شود., +Row #{}: You must select {} serial numbers for item {}.,ردیف شماره {}: شما باید {} شماره سریال را برای مورد {} انتخاب کنید., diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv index 23fd7a7276..29eb56702d 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -4238,7 +4238,6 @@ Download as JSON,Lataa nimellä JSON, End date can not be less than start date,päättymispäivä ei voi olla ennen aloituspäivää, For Default Supplier (Optional),Oletuksena toimittaja (valinnainen), From date cannot be greater than To date,Alkaen päivä ei voi olla suurempi kuin päättymispäivä, -Get items from,Hae nimikkeet, Group by,ryhmän, In stock,Varastossa, Item name,Nimikkeen nimi, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Ostotilausten kehitys, Purchase Receipt Trends,Saapumisten kehitys, Purchase Register,Osto Rekisteröidy, Quotation Trends,Tarjousten kehitys, -Quoted Item Comparison,Noteeratut Kohta Vertailu, Received Items To Be Billed,Saivat kohteet laskuttamat, Qty to Order,Tilattava yksikkömäärä, Requested Items To Be Transferred,siirrettävät pyydetyt tuotteet, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Kohde, johon {0} - {1} vii Therapy Session overlaps with {0},Hoitoistunto on päällekkäinen kohteen {0} kanssa, Therapy Sessions Overlapping,Hoitoistunnot ovat päällekkäisiä, Therapy Plans,Hoitosuunnitelmat, +"Item Code, warehouse, quantity are required on row {0}","Tuotekoodi, varasto, määrä vaaditaan rivillä {0}", +Get Items from Material Requests against this Supplier,Hanki tuotteita tältä toimittajalta saaduista materiaalipyynnöistä, +Enable European Access,Ota käyttöön eurooppalainen käyttöoikeus, +Creating Purchase Order ...,Luodaan ostotilausta ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Valitse toimittaja alla olevien tuotteiden oletustoimittajista. Valinnan yhteydessä ostotilaus tehdään vain valitulle toimittajalle kuuluvista tuotteista., +Row #{}: You must select {} serial numbers for item {}.,Rivi # {}: Sinun on valittava {} tuotteen sarjanumerot {}., diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 09a90331c7..3cdae454ab 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -583,7 +583,7 @@ Condition,Conditions, Configure,Configurer, Configure {0},Configurer {0}, Confirmed orders from Customers.,Commandes confirmées des clients., -Connect Amazon with ERPNext,Connectez Amazon avec ERPNext, +Connect Amazon with ERPNext,Connecter Amazon avec ERPNext, Connect Shopify with ERPNext,Connectez Shopify avec ERPNext, Connect to Quickbooks,Se connecter à Quickbooks, Connected to QuickBooks,Connecté à QuickBooks, @@ -4238,7 +4238,6 @@ Download as JSON,Télécharger en JSON, End date can not be less than start date,La date de Fin ne peut pas être antérieure à la Date de Début, For Default Supplier (Optional),Pour le fournisseur par défaut (facultatif), From date cannot be greater than To date,La Date Initiale ne peut pas être postérieure à la Date Finale, -Get items from,Obtenir les articles de, Group by,Grouper Par, In stock,En stock, Item name,Nom de l'article, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Tendances des Bons de Commande, Purchase Receipt Trends,Tendances des Reçus d'Achats, Purchase Register,Registre des Achats, Quotation Trends,Tendances des Devis, -Quoted Item Comparison,Comparaison d'Article Soumis, Received Items To Be Billed,Articles Reçus à Facturer, Qty to Order,Quantité à Commander, Requested Items To Be Transferred,Articles Demandés à Transférer, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,L'article référencé Therapy Session overlaps with {0},La session de thérapie chevauche {0}, Therapy Sessions Overlapping,Chevauchement des séances de thérapie, Therapy Plans,Plans de thérapie, +"Item Code, warehouse, quantity are required on row {0}","Le code article, l'entrepôt et la quantité sont obligatoires sur la ligne {0}", +Get Items from Material Requests against this Supplier,Obtenir des articles à partir de demandes d'articles auprès de ce fournisseur, +Enable European Access,Activer l'accès européen, +Creating Purchase Order ...,Création d'une commande d'achat ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Sélectionnez un fournisseur parmi les fournisseurs par défaut des articles ci-dessous. Lors de la sélection, un bon de commande sera effectué contre des articles appartenant uniquement au fournisseur sélectionné.", +Row #{}: You must select {} serial numbers for item {}.,Ligne n ° {}: vous devez sélectionner {} numéros de série pour l'article {}., diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv index e25c6bba4a..5c2b520d9e 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -4238,7 +4238,6 @@ Download as JSON,જેસન તરીકે ડાઉનલોડ કરો, End date can not be less than start date,સમાપ્તિ તારીખ પ્રારંભ તારીખ કરતાં ઓછી હોઈ શકતી નથી, For Default Supplier (Optional),ડિફોલ્ટ સપ્લાયર માટે (વૈકલ્પિક), From date cannot be greater than To date,તારીખથી તારીખ કરતાં વધુ હોઈ શકતી નથી, -Get items from,વસ્તુઓ મેળવો, Group by,ગ્રુપ દ્વારા, In stock,ઉપલબ્ધ છે, Item name,વસ્તુ નામ, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ઓર્ડર પ્રવાહો ખરીદી, Purchase Receipt Trends,ખરીદી રસીદ પ્રવાહો, Purchase Register,ખરીદી રજીસ્ટર, Quotation Trends,અવતરણ પ્રવાહો, -Quoted Item Comparison,નોંધાયેલા વસ્તુ સરખામણી, Received Items To Be Billed,પ્રાપ્ત વસ્તુઓ બિલ કરવા, Qty to Order,ઓર્ડર Qty, Requested Items To Be Transferred,વિનંતી વસ્તુઓ ટ્રાન્સફર કરી, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} દ્વાર Therapy Session overlaps with {0},ઉપચાર સત્ર {0 with સાથે ઓવરલેપ થાય છે, Therapy Sessions Overlapping,ઉપચાર સત્રો ઓવરલેપિંગ, Therapy Plans,ઉપચાર યોજનાઓ, +"Item Code, warehouse, quantity are required on row {0}","આઇટમ કોડ, વેરહાઉસ, જથ્થો પંક્તિ પર આવશ્યક છે {0}", +Get Items from Material Requests against this Supplier,આ સપ્લાયર સામે સામગ્રી વિનંતીઓમાંથી આઇટમ્સ મેળવો, +Enable European Access,યુરોપિયન Enableક્સેસને સક્ષમ કરો, +Creating Purchase Order ...,ખરીદી Orderર્ડર બનાવી રહ્યાં છે ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","નીચેની આઇટમ્સના ડિફaultલ્ટ સપ્લાયર્સમાંથી સપ્લાયર પસંદ કરો. પસંદગી પર, ફક્ત પસંદ કરેલા સપ્લાયરની વસ્તુઓ સામે ખરીદ ઓર્ડર આપવામાં આવશે.", +Row #{}: You must select {} serial numbers for item {}.,પંક્તિ # {}: તમારે આઇટમ for for માટે સીરીયલ નંબરો પસંદ કરવા આવશ્યક છે., diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv index 43ca52cbe1..29e6f6afc3 100644 --- a/erpnext/translations/he.csv +++ b/erpnext/translations/he.csv @@ -4238,7 +4238,6 @@ Download as JSON,הורד כ- JSON, End date can not be less than start date,תאריך סיום לא יכול להיות פחות מתאריך ההתחלה, For Default Supplier (Optional),לספק ברירת מחדל (אופציונלי), From date cannot be greater than To date,מתאריך לא יכול להיות גדול יותר מאשר תאריך, -Get items from,קבל פריטים מ, Group by,קבוצה על ידי, In stock,במלאי, Item name,שם פריט, @@ -8549,7 +8548,6 @@ Purchase Order Trends,לרכוש מגמות להזמין, Purchase Receipt Trends,מגמות קבלת רכישה, Purchase Register,רכישת הרשמה, Quotation Trends,מגמות ציטוט, -Quoted Item Comparison,פריט מצוטט השוואה, Received Items To Be Billed,פריטים שהתקבלו לחיוב, Qty to Order,כמות להזמנה, Requested Items To Be Transferred,פריטים מבוקשים שיועברו, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,הפריט אליו הוז Therapy Session overlaps with {0},מושב טיפולי חופף עם {0}, Therapy Sessions Overlapping,מפגשי טיפול חופפים, Therapy Plans,תוכניות טיפול, +"Item Code, warehouse, quantity are required on row {0}","קוד פריט, מחסן, כמות נדרשים בשורה {0}", +Get Items from Material Requests against this Supplier,קבל פריטים מבקשות חומר נגד ספק זה, +Enable European Access,אפשר גישה אירופית, +Creating Purchase Order ...,יוצר הזמנת רכש ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","בחר ספק מבין ספקי ברירת המחדל של הפריטים למטה. בבחירה, הזמנת רכש תתבצע כנגד פריטים השייכים לספק שנבחר בלבד.", +Row #{}: You must select {} serial numbers for item {}.,שורה מספר {}: עליך לבחור {} מספרים סידוריים לפריט {}., diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv index c18532b974..c385fc6ef6 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON के रूप में डाउनलोड करे End date can not be less than start date,समाप्ति तिथि आरंभ तिथि से कम नहीं हो सकता, For Default Supplier (Optional),डिफ़ॉल्ट प्रदायक (वैकल्पिक) के लिए, From date cannot be greater than To date,दिनांक से दिनांक से अधिक नहीं हो सकता है, -Get items from,से आइटम प्राप्त, Group by,समूह द्वारा, In stock,स्टॉक में, Item name,मद का नाम, @@ -8549,7 +8548,6 @@ Purchase Order Trends,आदेश रुझान खरीद, Purchase Receipt Trends,खरीद रसीद रुझान, Purchase Register,इन पंजीकृत खरीद, Quotation Trends,कोटेशन रुझान, -Quoted Item Comparison,उद्धरित मद तुलना, Received Items To Be Billed,बिल करने के लिए प्राप्त आइटम, Qty to Order,मात्रा आदेश को, Requested Items To Be Transferred,हस्तांतरित करने का अनुरोध आइटम, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} द्वार Therapy Session overlaps with {0},थेरेपी सत्र {0} से ओवरलैप होता है, Therapy Sessions Overlapping,थेरेपी सेशन ओवरलैपिंग, Therapy Plans,थेरेपी योजनाएं, +"Item Code, warehouse, quantity are required on row {0}","पंक्ति {0} पर आइटम कोड, गोदाम, मात्रा आवश्यक है", +Get Items from Material Requests against this Supplier,इस आपूर्तिकर्ता के विरुद्ध सामग्री अनुरोध से आइटम प्राप्त करें, +Enable European Access,यूरोपीय पहुँच सक्षम करें, +Creating Purchase Order ...,क्रय आदेश बनाना ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","नीचे दी गई वस्तुओं के डिफ़ॉल्ट आपूर्तिकर्ता से एक आपूर्तिकर्ता का चयन करें। चयन पर, केवल चयनित आपूर्तिकर्ता से संबंधित वस्तुओं के खिलाफ एक खरीद ऑर्डर किया जाएगा।", +Row #{}: You must select {} serial numbers for item {}.,पंक्ति # {}: आपको आइटम {} के लिए {} सीरियल नंबर का चयन करना होगा।, diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv index aabacdc0af..a544e98868 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -4238,7 +4238,6 @@ Download as JSON,Preuzmi kao Json, End date can not be less than start date,Datum završetka ne može biti manja od početnog datuma, For Default Supplier (Optional),Za dobavljača zadano (neobavezno), From date cannot be greater than To date,Datum ne može biti veći od datuma, -Get items from,Nabavite stavke iz, Group by,Grupiranje prema, In stock,Na lageru, Item name,Naziv proizvoda, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Trendovi narudžbenica kupnje, Purchase Receipt Trends,Trend primki, Purchase Register,Popis nabave, Quotation Trends,Trend ponuda, -Quoted Item Comparison,Citirano predmeta za usporedbu, Received Items To Be Billed,Primljeni Proizvodi se naplaćuje, Qty to Order,Količina za narudžbu, Requested Items To Be Transferred,Traženi proizvodi spremni za transfer, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Stavka na koju se poziva {0 Therapy Session overlaps with {0},Sjednica terapije preklapa se s {0}, Therapy Sessions Overlapping,Preklapajuće se terapijske sesije, Therapy Plans,Planovi terapije, +"Item Code, warehouse, quantity are required on row {0}","Šifra artikla, skladište, količina potrebni su na retku {0}", +Get Items from Material Requests against this Supplier,Nabavite stavke iz materijalnih zahtjeva protiv ovog dobavljača, +Enable European Access,Omogućiti europski pristup, +Creating Purchase Order ...,Izrada narudžbenice ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Odaberite dobavljača od zadanih dobavljača dolje navedenih stavki. Nakon odabira, narudžbenica će se izvršiti samo za proizvode koji pripadaju odabranom dobavljaču.", +Row #{}: You must select {} serial numbers for item {}.,Redak {{}: Morate odabrati {} serijske brojeve za stavku {}., diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv index 09acda372e..29f347ecbc 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -4238,7 +4238,6 @@ Download as JSON,Töltse le JSON néven, End date can not be less than start date,"A befejezés dátuma nem lehet kevesebb, mint a kezdő dátum", For Default Supplier (Optional),Az alapértelmezett beszállító számára (opcionális), From date cannot be greater than To date,"A dátum nem lehet nagyobb, mint a dátum", -Get items from,Tételeket kér le innen, Group by,Csoportosítva, In stock,Raktáron, Item name,Tétel neve, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Beszerzési megrendelések alakulása, Purchase Receipt Trends,Beszerzési nyugták alakulása, Purchase Register,Beszerzési Regisztráció, Quotation Trends,Árajánlatok alakulása, -Quoted Item Comparison,Ajánlott tétel összehasonlítás, Received Items To Be Billed,Számlázandó Beérkezett tételek, Qty to Order,Mennyiség Rendeléshez, Requested Items To Be Transferred,Kérte az átvinni kívánt elemeket, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,A (z) {0} - {1} által hiva Therapy Session overlaps with {0},A terápiás munkamenet átfedésben van a következővel: {0}, Therapy Sessions Overlapping,A terápiás foglalkozások átfedésben vannak, Therapy Plans,Terápiás tervek, +"Item Code, warehouse, quantity are required on row {0}","A (z) {0} sorban tételszám, raktár, mennyiség szükséges", +Get Items from Material Requests against this Supplier,Tételeket szerezhet a szállítóval szembeni anyagi igényekből, +Enable European Access,Engedélyezze az európai hozzáférést, +Creating Purchase Order ...,Megrendelés létrehozása ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Válasszon szállítót az alábbi elemek alapértelmezett szállítói közül. A kiválasztáskor csak a kiválasztott szállítóhoz tartozó termékekre készül megrendelés., +Row #{}: You must select {} serial numbers for item {}.,#. Sor:} {0} sorszámokat kell kiválasztania., diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv index 53122ccb71..7175ad2fa7 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -4238,7 +4238,6 @@ Download as JSON,Unduh sebagai JSON, End date can not be less than start date,Tanggal Berakhir tidak boleh kurang dari Tanggal Mulai, For Default Supplier (Optional),Untuk Pemasok Default (Opsional), From date cannot be greater than To date,Dari Tanggal tidak dapat lebih besar dari To Date, -Get items from,Mendapatkan Stok Barang-Stok Barang dari, Group by,Kelompok Dengan, In stock,Persediaan, Item name,Nama Item, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Trend Order Pembelian, Purchase Receipt Trends,Tren Nota Penerimaan, Purchase Register,Register Pembelian, Quotation Trends,Trend Penawaran, -Quoted Item Comparison,Perbandingan Produk/Barang yang ditawarkan, Received Items To Be Billed,Produk Diterima Akan Ditagih, Qty to Order,Kuantitas untuk diorder, Requested Items To Be Transferred,Permintaan Produk Akan Ditransfer, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Item yang direferensikan ol Therapy Session overlaps with {0},Sesi Terapi tumpang tindih dengan {0}, Therapy Sessions Overlapping,Sesi Terapi Tumpang Tindih, Therapy Plans,Rencana Terapi, +"Item Code, warehouse, quantity are required on row {0}","Kode Barang, gudang, kuantitas diperlukan di baris {0}", +Get Items from Material Requests against this Supplier,Dapatkan Item dari Permintaan Material terhadap Pemasok ini, +Enable European Access,Aktifkan Akses Eropa, +Creating Purchase Order ...,Membuat Pesanan Pembelian ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Pilih Pemasok dari Pemasok Default item di bawah ini. Saat dipilih, Pesanan Pembelian akan dibuat terhadap barang-barang milik Pemasok terpilih saja.", +Row #{}: You must select {} serial numbers for item {}.,Baris # {}: Anda harus memilih {} nomor seri untuk item {}., diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv index 50828baeee..5f56aff3dc 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -4238,7 +4238,6 @@ Download as JSON,Sæktu sem JSON, End date can not be less than start date,Lokadagur getur ekki verið minna en upphafsdagur, For Default Supplier (Optional),Fyrir Sjálfgefið Birgir (valfrjálst), From date cannot be greater than To date,Frá Dagsetning má ekki vera meiri en Til Dagsetning, -Get items from,Fá atriði úr, Group by,Hópa eftir, In stock,Á lager, Item name,Item Name, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Purchase Order Trends, Purchase Receipt Trends,Kvittun Trends, Purchase Register,kaup Register, Quotation Trends,Tilvitnun Trends, -Quoted Item Comparison,Vitnað Item Samanburður, Received Items To Be Billed,Móttekin Items verður innheimt, Qty to Order,Magn til að panta, Requested Items To Be Transferred,Umbeðin Items til að flytja, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Atriðið sem {0} - {1} ví Therapy Session overlaps with {0},Meðferðarlotan skarast við {0}, Therapy Sessions Overlapping,Meðferðarlotur skarast, Therapy Plans,Meðferðaráætlanir, +"Item Code, warehouse, quantity are required on row {0}","Vörukóði, vöruhús, magn er krafist í línu {0}", +Get Items from Material Requests against this Supplier,Fáðu hluti frá beiðnum um efni gagnvart þessum birgi, +Enable European Access,Virkja evrópskan aðgang, +Creating Purchase Order ...,Býr til innkaupapöntun ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Veldu birgja frá sjálfgefnum birgjum hlutanna hér að neðan. Við val verður eingöngu gerð innkaupapöntun á hlutum sem tilheyra völdum birgi., +Row #{}: You must select {} serial numbers for item {}.,Röð nr. {}: Þú verður að velja {} raðnúmer fyrir hlut {}., diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv index 4da11f1989..3a1d73f344 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -4238,7 +4238,6 @@ Download as JSON,Scarica come JSON, End date can not be less than start date,Data di Fine non può essere inferiore a Data di inizio, For Default Supplier (Optional),Per fornitore predefinito (facoltativo), From date cannot be greater than To date,Dalla data non può essere maggiore di Alla data, -Get items from,Ottenere elementi dal, Group by,Raggruppa per, In stock,disponibile, Item name,Nome Articolo, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Acquisto Tendenze Ordine, Purchase Receipt Trends,Acquisto Tendenze Receipt, Purchase Register,Registro Acquisti, Quotation Trends,Tendenze di preventivo, -Quoted Item Comparison,Articolo Citato Confronto, Received Items To Be Billed,Oggetti ricevuti da fatturare, Qty to Order,Qtà da Ordinare, Requested Items To Be Transferred,Voci si chiede il trasferimento, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,L'articolo a cui fa rif Therapy Session overlaps with {0},La sessione di terapia si sovrappone a {0}, Therapy Sessions Overlapping,Sessioni di terapia sovrapposte, Therapy Plans,Piani terapeutici, +"Item Code, warehouse, quantity are required on row {0}","Codice articolo, magazzino e quantità sono obbligatori nella riga {0}", +Get Items from Material Requests against this Supplier,Ottieni articoli da richieste di materiale contro questo fornitore, +Enable European Access,Consentire l'accesso europeo, +Creating Purchase Order ...,Creazione dell'ordine di acquisto ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Seleziona un fornitore dai fornitori predefiniti degli articoli seguenti. Alla selezione, verrà effettuato un Ordine di acquisto solo per gli articoli appartenenti al Fornitore selezionato.", +Row #{}: You must select {} serial numbers for item {}.,Riga # {}: è necessario selezionare i {} numeri di serie per l'articolo {}., diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv index 3fa1c18a48..6e2eaae4a4 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSONとしてダウンロード, End date can not be less than start date,終了日は開始日より短くすることはできません, For Default Supplier (Optional),デフォルトサプライヤ(オプション), From date cannot be greater than To date,開始日を終了日より大きくすることはできません, -Get items from,アイテム取得元, Group by,グループ化, In stock,在庫あり, Item name,アイテム名, @@ -8549,7 +8548,6 @@ Purchase Order Trends,発注傾向, Purchase Receipt Trends,領収書傾向, Purchase Register,仕入帳, Quotation Trends,見積傾向, -Quoted Item Comparison,引用符で囲まれた項目の比較, Received Items To Be Billed,支払予定受領アイテム, Qty to Order,注文数, Requested Items To Be Transferred,移転予定の要求アイテム, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0}-{1}で参照されて Therapy Session overlaps with {0},セラピーセッションが{0}と重複しています, Therapy Sessions Overlapping,重複する治療セッション, Therapy Plans,治療計画, +"Item Code, warehouse, quantity are required on row {0}",行{0}にはアイテムコード、倉庫、数量が必要です, +Get Items from Material Requests against this Supplier,このサプライヤーに対する重要な要求からアイテムを取得する, +Enable European Access,ヨーロッパへのアクセスを有効にする, +Creating Purchase Order ...,注文書の作成..。, +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",以下の項目のデフォルトサプライヤーからサプライヤーを選択します。選択時に、選択したサプライヤーに属するアイテムに対してのみ発注書が作成されます。, +Row #{}: You must select {} serial numbers for item {}.,行#{}:アイテム{}の{}シリアル番号を選択する必要があります。, diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv index e1a01b018c..e2a528cea2 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -4238,7 +4238,6 @@ Download as JSON,ទាញយកជា Json ។, End date can not be less than start date,កាលបរិច្ឆេទបញ្ចប់មិនអាចតិចជាងកាលបរិច្ឆេទចាប់ផ្ដើមទេ, For Default Supplier (Optional),សម្រាប់អ្នកផ្គត់ផ្គង់លំនាំដើម (ស្រេចចិត្ត), From date cannot be greater than To date,ពីកាលបរិច្ឆេទមិនអាចមានចំនួនច្រើនជាងកាលបរិច្ឆេទ, -Get items from,ទទួលបានមុខទំនិញពី, Group by,ក្រុមតាម, In stock,នៅក្នុងស្តុក, Item name,ឈ្មោះធាតុ, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ទិញលំដាប់និន្នាការ, Purchase Receipt Trends,និន្នាការបង្កាន់ដៃទិញ, Purchase Register,ទិញចុះឈ្មោះ, Quotation Trends,សម្រង់និន្នាការ, -Quoted Item Comparison,ធាតុដកស្រង់សម្តីប្រៀបធៀប, Received Items To Be Billed,ទទួលបានធាតុដែលនឹងត្រូវបានផ្សព្វផ្សាយ, Qty to Order,qty ម៉ង់ទិញ, Requested Items To Be Transferred,ធាតុដែលបានស្នើសុំឱ្យគេបញ្ជូន, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,ធាតុដែលយោ Therapy Session overlaps with {0},វគ្គនៃការព្យាបាលត្រួតគ្នាជាមួយ {0}, Therapy Sessions Overlapping,ការព្យាបាលដោយការត្រួតគ្នា, Therapy Plans,ផែនការព្យាបាល, +"Item Code, warehouse, quantity are required on row {0}",លេខកូដទំនិញឃ្លាំងបរិមាណត្រូវបានទាមទារនៅជួរ {0}, +Get Items from Material Requests against this Supplier,ទទួលបានវត្ថុពីសំណើសម្ភារៈប្រឆាំងនឹងអ្នកផ្គត់ផ្គង់នេះ, +Enable European Access,បើកដំណើរការចូលអឺរ៉ុប, +Creating Purchase Order ...,កំពុងបង្កើតការបញ្ជាទិញ ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",ជ្រើសរើសអ្នកផ្គត់ផ្គង់ពីអ្នកផ្គត់ផ្គង់លំនាំដើមនៃធាតុខាងក្រោម។ នៅពេលជ្រើសរើសការបញ្ជាទិញនឹងត្រូវធ្វើឡើងប្រឆាំងនឹងរបស់របរដែលជាកម្មសិទ្ធិរបស់អ្នកផ្គត់ផ្គង់ដែលបានជ្រើសរើសតែប៉ុណ្ណោះ។, +Row #{}: You must select {} serial numbers for item {}.,ជួរទី # {}៖ អ្នកត្រូវតែជ្រើសរើស {} លេខស៊េរីសម្រាប់ធាតុ {} ។, diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv index fdd798dbfc..4a9173d4a0 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON ಆಗಿ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, End date can not be less than start date,ಅಂತಿಮ ದಿನಾಂಕ ಪ್ರಾರಂಭ ದಿನಾಂಕಕ್ಕಿಂತ ಕಡಿಮೆ ಇರುವಂತಿಲ್ಲ, For Default Supplier (Optional),ಡೀಫಾಲ್ಟ್ ಪೂರೈಕೆದಾರರಿಗಾಗಿ (ಐಚ್ಛಿಕ), From date cannot be greater than To date,ದಿನಾಂಕದಿಂದ ದಿನಾಂಕಕ್ಕಿಂತಲೂ ಹೆಚ್ಚಿನದಾಗಿರುವಂತಿಲ್ಲ, -Get items from,ಐಟಂಗಳನ್ನು ಪಡೆಯಿರಿ, Group by,ಗುಂಪಿನ, In stock,ಉಪಲಬ್ದವಿದೆ, Item name,ಐಟಂ ಹೆಸರು, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ಆರ್ಡರ್ ಟ್ರೆಂಡ್ಸ್ ಖರಿ Purchase Receipt Trends,ಖರೀದಿ ರಸೀತಿ ಟ್ರೆಂಡ್ಸ್, Purchase Register,ಖರೀದಿ ನೋಂದಣಿ, Quotation Trends,ನುಡಿಮುತ್ತುಗಳು ಟ್ರೆಂಡ್ಸ್, -Quoted Item Comparison,ಉಲ್ಲೇಖಿಸಿದ ಐಟಂ ಹೋಲಿಕೆ, Received Items To Be Billed,ಪಾವತಿಸಬೇಕಾಗುತ್ತದೆ ಸ್ವೀಕರಿಸಿದ ಐಟಂಗಳು, Qty to Order,ಪ್ರಮಾಣ ಆರ್ಡರ್, Requested Items To Be Transferred,ಬದಲಾಯಿಸಿಕೊಳ್ಳುವಂತೆ ವಿನಂತಿಸಲಾಗಿದೆ ಐಟಂಗಳು, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1 by ನಿಂದ Therapy Session overlaps with {0},ಥೆರಪಿ ಸೆಷನ್ {0 with ನೊಂದಿಗೆ ಅತಿಕ್ರಮಿಸುತ್ತದೆ, Therapy Sessions Overlapping,ಥೆರಪಿ ಸೆಷನ್‌ಗಳು ಅತಿಕ್ರಮಿಸುತ್ತವೆ, Therapy Plans,ಚಿಕಿತ್ಸೆಯ ಯೋಜನೆಗಳು, +"Item Code, warehouse, quantity are required on row {0}","Code 0 row ಸಾಲಿನಲ್ಲಿ ಐಟಂ ಕೋಡ್, ಗೋದಾಮು, ಪ್ರಮಾಣ ಅಗತ್ಯವಿದೆ", +Get Items from Material Requests against this Supplier,ಈ ಸರಬರಾಜುದಾರರ ವಿರುದ್ಧ ವಸ್ತು ವಿನಂತಿಗಳಿಂದ ವಸ್ತುಗಳನ್ನು ಪಡೆಯಿರಿ, +Enable European Access,ಯುರೋಪಿಯನ್ ಪ್ರವೇಶವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ, +Creating Purchase Order ...,ಖರೀದಿ ಆದೇಶವನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","ಕೆಳಗಿನ ಐಟಂಗಳ ಡೀಫಾಲ್ಟ್ ಪೂರೈಕೆದಾರರಿಂದ ಸರಬರಾಜುದಾರರನ್ನು ಆಯ್ಕೆಮಾಡಿ. ಆಯ್ಕೆಯ ಮೇಲೆ, ಆಯ್ದ ಸರಬರಾಜುದಾರರಿಗೆ ಮಾತ್ರ ಸೇರಿದ ವಸ್ತುಗಳ ವಿರುದ್ಧ ಖರೀದಿ ಆದೇಶವನ್ನು ಮಾಡಲಾಗುತ್ತದೆ.", +Row #{}: You must select {} serial numbers for item {}.,ಸಾಲು # {}: ನೀವು item item ಐಟಂಗೆ {} ಸರಣಿ ಸಂಖ್ಯೆಗಳನ್ನು ಆರಿಸಬೇಕು., diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv index 1ba119cdb9..c051b07bea 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON으로 다운로드, End date can not be less than start date,종료일은 시작일보다 짧을 수 없습니다., For Default Supplier (Optional),기본 공급 업체 (선택 사항), From date cannot be greater than To date,날짜에서 날짜보다 클 수 없습니다, -Get items from,에서 항목을 가져 오기, Group by,그룹으로, In stock,재고, Item name,품명, @@ -8549,7 +8548,6 @@ Purchase Order Trends,주문 동향을 구매, Purchase Receipt Trends,구매 영수증 동향, Purchase Register,회원에게 구매, Quotation Trends,견적 동향, -Quoted Item Comparison,인용 상품 비교, Received Items To Be Billed,청구에 주어진 항목, Qty to Order,수량은 주문, Requested Items To Be Transferred,전송할 요청 항목, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0}-{1}에서 참조하는 Therapy Session overlaps with {0},치료 세션이 {0}와 (과) 겹칩니다., Therapy Sessions Overlapping,겹치는 치료 세션, Therapy Plans,치료 계획, +"Item Code, warehouse, quantity are required on row {0}","{0} 행에 품목 코드, 창고, 수량이 필요합니다.", +Get Items from Material Requests against this Supplier,이 공급자에 대한 자재 요청에서 품목 가져 오기, +Enable European Access,유럽 액세스 활성화, +Creating Purchase Order ...,구매 오더 생성 ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",아래 항목의 기본 공급자에서 공급자를 선택합니다. 선택시 선택한 공급 업체에 속한 품목에 대해서만 구매 주문이 작성됩니다., +Row #{}: You must select {} serial numbers for item {}.,행 # {} : 항목 {}에 대해 {} 일련 번호를 선택해야합니다., diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv index 5d9d472071..6962ea1ef1 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -4238,7 +4238,6 @@ Download as JSON,Wekî JSON dakêşin, End date can not be less than start date,Dîrok Dîroka Destpêk Destpêk Dibe, For Default Supplier (Optional),Ji bo Default Supplier (alternatîf), From date cannot be greater than To date,Ji Date ne dikarin bibin mezintir To Date, -Get items from,Get tomar ji, Group by,Koma By, In stock,Ez bêzarim, Item name,Navê Navekî, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Bikirin Order Trends, Purchase Receipt Trends,Trends kirînê Meqbûz, Purchase Register,Buy Register, Quotation Trends,Trends quotation, -Quoted Item Comparison,Babetê têbinî eyna, Received Items To Be Billed,Pêşwaziya Nawy ye- Be, Qty to Order,Qty siparîş, Requested Items To Be Transferred,Nawy xwestin veguhestin, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Tişta ku ji hêla {0} - {1 Therapy Session overlaps with {0},Danişîna Terapiyê bi {0} re li hevûdu dike, Therapy Sessions Overlapping,Danişînên Terapiyê Li Hev Dikevin, Therapy Plans,Planên Terapiyê, +"Item Code, warehouse, quantity are required on row {0}","Koda tiştê, embarê, hejmar li ser rêzê hewce ne {0}", +Get Items from Material Requests against this Supplier,Li dijî vê Pêşkêşkerê Tiştan ji Daxwazên Maddî bistînin, +Enable European Access,Destûra Ewropî çalak bikin, +Creating Purchase Order ...,Afirandina Biryara Kirînê ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Ji Pargîdaniyên Pêşwext ên jêrîn ve Pêşkêşvanek hilbijêrin. Li ser hilbijartinê, dê Biryarnameyek Kirînê li dijî tiştên ku tenê ji Pêşkêşkarê hilbijartî ne pêk were.", +Row #{}: You must select {} serial numbers for item {}.,Rêzok # {}: Divê hûn {} hejmarên rêzê ji bo hêmanê {} hilbijêrin., diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv index 2220ac3c17..b61476cf17 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -4238,7 +4238,6 @@ Download as JSON,ດາວໂຫລດເປັນ JSON, End date can not be less than start date,ວັນສິ້ນສຸດບໍ່ສາມາດນ້ອຍກວ່າວັນເລີ່ມຕົ້ນ, For Default Supplier (Optional),ສໍາລັບຜູ້ໃຫ້ບໍລິການມາດຕະຖານ (ທາງເລືອກ), From date cannot be greater than To date,ຈາກວັນທີບໍ່ສາມາດຈະມີຫຼາຍຂຶ້ນກ່ວາເຖິງວັນທີ່, -Get items from,ໄດ້ຮັບການລາຍການຈາກ, Group by,ກຸ່ມໂດຍ, In stock,ໃນສາງ, Item name,ຊື່ສິນຄ້າ, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ຊື້ແນວໂນ້ມຄໍາສັ່ງ, Purchase Receipt Trends,ແນວໂນ້ມການຊື້ຮັບ, Purchase Register,ລົງທະບຽນການຊື້, Quotation Trends,ແນວໂນ້ມວົງຢືມ, -Quoted Item Comparison,ປຽບທຽບບາຍດີທຸກທ່ານ Item, Received Items To Be Billed,ລາຍການທີ່ໄດ້ຮັບການໄດ້ຮັບການ billed, Qty to Order,ຈໍານວນທີ່ຈະສັ່ງຊື້ສິນຄ້າ, Requested Items To Be Transferred,ການຮ້ອງຂໍໃຫ້ໄດ້ຮັບການໂອນ, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,ລາຍການທີ່ Therapy Session overlaps with {0},Therapy Session ຊ້ ຳ ກັບ {0}, Therapy Sessions Overlapping,ການປິ່ນປົວດ້ວຍການຊໍ້າຊ້ອນ, Therapy Plans,ແຜນການປິ່ນປົວ, +"Item Code, warehouse, quantity are required on row {0}","ລະຫັດສິນຄ້າ, ຄັງສິນຄ້າ, ຈຳ ນວນທີ່ຕ້ອງການຢູ່ແຖວ {0}", +Get Items from Material Requests against this Supplier,ໄດ້ຮັບສິນຄ້າຈາກຂໍ້ຮຽກຮ້ອງດ້ານວັດຖຸຕໍ່ຜູ້ສະ ໜອງ ນີ້, +Enable European Access,ເປີດໃຊ້ງານ European Access, +Creating Purchase Order ...,ການສ້າງໃບສັ່ງຊື້ ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","ເລືອກຜູ້ສະ ໜອງ ສິນຄ້າຈາກຜູ້ສະ ໜອງ ສິນຄ້າເລີ່ມຕົ້ນຂອງລາຍການຂ້າງລຸ່ມນີ້. ໃນການເລືອກ, ຄຳ ສັ່ງຊື້ສິນຄ້າຈະຖືກຕໍ່ຕ້ານກັບສິນຄ້າທີ່ເປັນຂອງຜູ້ສະ ໜອງ ທີ່ຖືກຄັດເລືອກເທົ່ານັ້ນ.", +Row #{}: You must select {} serial numbers for item {}.,ແຖວ # {}: ທ່ານຕ້ອງເລືອກ {} ເລກ ລຳ ດັບ ສຳ ລັບລາຍການ {}., diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv index e98f2f2143..78571f9624 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -4238,7 +4238,6 @@ Download as JSON,Atsisiųsti kaip JSON, End date can not be less than start date,Galutinė data negali būti mažesnė už pradžios datą, For Default Supplier (Optional),Numatytam tiekėjui (neprivaloma), From date cannot be greater than To date,Nuo datos negali būti didesnis nei iki datos, -Get items from,Gauk elementus iš, Group by,Grupuoti pagal, In stock,Sandelyje, Item name,Daikto pavadinimas, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Pirkimui užsakyti tendencijos, Purchase Receipt Trends,Pirkimo kvito tendencijos, Purchase Register,pirkimo Registruotis, Quotation Trends,Kainų tendencijos, -Quoted Item Comparison,Cituojamas punktas Palyginimas, Received Items To Be Billed,Gauti duomenys turi būti apmokestinama, Qty to Order,Kiekis užsisakyti, Requested Items To Be Transferred,Pageidaujami daiktai turi būti perkeltos, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Elementas, nurodytas {0} - Therapy Session overlaps with {0},Terapijos seansas sutampa su {0}, Therapy Sessions Overlapping,Terapijos seansai sutampa, Therapy Plans,Terapijos planai, +"Item Code, warehouse, quantity are required on row {0}","Prekės kodas, sandėlis, kiekis būtini {0} eilutėje", +Get Items from Material Requests against this Supplier,Gaukite elementus iš šio tiekėjo materialinių užklausų, +Enable European Access,Įgalinti Europos prieigą, +Creating Purchase Order ...,Kuriamas pirkimo užsakymas ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Pasirinkite tiekėją iš toliau nurodytų gaminių iš numatytųjų tiekėjų. Pasirinkus pirkimą, bus sudarytos tik prekės, priklausančios pasirinktam tiekėjui.", +Row #{}: You must select {} serial numbers for item {}.,# Eilutė {}: turite pasirinkti {} prekės serijos numerius {}., diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv index 5fdedc8fc7..cbf04855d0 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -4238,7 +4238,6 @@ Download as JSON,Lejupielādēt kā JSON, End date can not be less than start date,Beigu Datums nevar būt mazāks par sākuma datuma, For Default Supplier (Optional),Paredzētajam piegādātājam (neobligāti), From date cannot be greater than To date,No datuma nevar būt lielāka par datumu, -Get items from,Dabūtu preces no, Group by,Group By, In stock,Noliktavā, Item name,Vienības nosaukums, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Pirkuma pasūtījuma tendences, Purchase Receipt Trends,Pirkuma čeka tendences, Purchase Register,Pirkuma Reģistrēties, Quotation Trends,Piedāvājumu tendences, -Quoted Item Comparison,Citēts Prece salīdzinājums, Received Items To Be Billed,Saņemtie posteņi ir Jāmaksā, Qty to Order,Daudz pasūtījuma, Requested Items To Be Transferred,Pieprasīto pozīcijas jāpārskaita, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Vienumam, uz kuru atsaucas Therapy Session overlaps with {0},Terapijas sesija pārklājas ar {0}, Therapy Sessions Overlapping,Terapijas sesijas pārklājas, Therapy Plans,Terapijas plāni, +"Item Code, warehouse, quantity are required on row {0}","Rindā {0} ir nepieciešams preces kods, noliktava un daudzums", +Get Items from Material Requests against this Supplier,Iegūstiet preces no materiāliem pieprasījumiem pret šo piegādātāju, +Enable European Access,Iespējot Eiropas piekļuvi, +Creating Purchase Order ...,Notiek pirkuma pasūtījuma izveide ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Izvēlieties piegādātāju no tālāk norādīto vienumu noklusējuma piegādātājiem. Pēc izvēles tiks veikts pirkuma pasūtījums tikai par precēm, kas pieder izvēlētajam piegādātājam.", +Row #{}: You must select {} serial numbers for item {}.,#. Rinda: jums jāizvēlas {} vienuma sērijas numuri {}., diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv index fa3de63675..7008025534 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -4238,7 +4238,6 @@ Download as JSON,Преземете како JSON, End date can not be less than start date,Крајниот датум не може да биде помал од датумот на почеток, For Default Supplier (Optional),За стандарден добавувач (опционално), From date cannot be greater than To date,Од датумот не може да биде поголем од датум, -Get items from,Се предмети од, Group by,Со група, In stock,На залиха, Item name,Точка Име, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Нарачка трендови, Purchase Receipt Trends,Купување Потврда трендови, Purchase Register,Купување Регистрирај се, Quotation Trends,Трендови на Понуди, -Quoted Item Comparison,Цитирано Точка споредба, Received Items To Be Billed,Примените предмети да бидат фактурирани, Qty to Order,Количина да нарачате, Requested Items To Be Transferred,Бара предмети да бидат префрлени, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Ставката на ко Therapy Session overlaps with {0},Сесијата за терапија се преклопува со {0}, Therapy Sessions Overlapping,Сесии за терапија што се преклопуваат, Therapy Plans,Планови за терапија, +"Item Code, warehouse, quantity are required on row {0}","Кодот на објектот, складиштето, количината се потребни на редот {0}", +Get Items from Material Requests against this Supplier,Набавете предмети од материјални барања против овој добавувач, +Enable European Access,Овозможете европски пристап, +Creating Purchase Order ...,Создавање налог за набавка ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Изберете снабдувач од Стандардните добавувачи на ставките подолу. При избор, ќе се изврши Нарачка за набавка само на предмети што припаѓаат на избраниот Добавувач.", +Row #{}: You must select {} serial numbers for item {}.,Ред # {}: Мора да изберете {} сериски броеви за ставка {}., diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv index 6c9bcfc655..f917969345 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON ആയി ഡൗൺലോഡുചെയ്യുക, End date can not be less than start date,അവസാന തീയതി ആരംഭ തീയതിക്ക് കുറവായിരിക്കരുത്, For Default Supplier (Optional),സ്ഥിര വിതരണക്കാരന് (ഓപ്ഷണൽ), From date cannot be greater than To date,തീയതി മുതൽ തീയതി വരെ വലിയതായിരിക്കരുത്, -Get items from,നിന്ന് ഇനങ്ങൾ നേടുക, Group by,ഗ്രൂപ്പ്, In stock,സ്റ്റോക്കുണ്ട്, Item name,ഇനം പേര്, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ഓർഡർ ട്രെൻഡുകൾ വാങ്ങ Purchase Receipt Trends,വാങ്ങൽ രസീത് ട്രെൻഡുകൾ, Purchase Register,രജിസ്റ്റർ വാങ്ങുക, Quotation Trends,ക്വട്ടേഷൻ ട്രെൻഡുകൾ, -Quoted Item Comparison,ഉദ്ധരിച്ച ഇനം താരതമ്യം, Received Items To Be Billed,ബില്ല് ലഭിച്ച ഇനങ്ങൾ, Qty to Order,ഓർഡർ Qty, Requested Items To Be Transferred,മാറ്റിയത് അഭ്യർത്ഥിച്ചു ഇനങ്ങൾ, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1 by പരാമർ Therapy Session overlaps with {0},തെറാപ്പി സെഷൻ {0 with ഓവർലാപ്പ് ചെയ്യുന്നു, Therapy Sessions Overlapping,തെറാപ്പി സെഷനുകൾ ഓവർലാപ്പുചെയ്യുന്നു, Therapy Plans,തെറാപ്പി പദ്ധതികൾ, +"Item Code, warehouse, quantity are required on row {0}","Code 0 row വരിയിൽ ഇനം കോഡ്, വെയർഹ house സ്, അളവ് ആവശ്യമാണ്", +Get Items from Material Requests against this Supplier,ഈ വിതരണക്കാരനെതിരായ മെറ്റീരിയൽ അഭ്യർത്ഥനകളിൽ നിന്ന് ഇനങ്ങൾ നേടുക, +Enable European Access,യൂറോപ്യൻ ആക്സസ് പ്രാപ്തമാക്കുക, +Creating Purchase Order ...,വാങ്ങൽ ഓർഡർ സൃഷ്ടിക്കുന്നു ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","ചുവടെയുള്ള ഇനങ്ങളുടെ സ്ഥിരസ്ഥിതി വിതരണക്കാരിൽ നിന്ന് ഒരു വിതരണക്കാരനെ തിരഞ്ഞെടുക്കുക. തിരഞ്ഞെടുക്കുമ്പോൾ, തിരഞ്ഞെടുത്ത വിതരണക്കാരന്റെ മാത്രം ഇനങ്ങൾക്കെതിരെ ഒരു വാങ്ങൽ ഓർഡർ നൽകും.", +Row #{}: You must select {} serial numbers for item {}.,വരി # {}: നിങ്ങൾ item item ഇനത്തിനായി {} സീരിയൽ നമ്പറുകൾ തിരഞ്ഞെടുക്കണം., diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv index 1d63caf508..9c41ce6f33 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -4238,7 +4238,6 @@ Download as JSON,जेसन म्हणून डाउनलोड करा End date can not be less than start date,समाप्ती तारीख प्रारंभ तारखेच्या पेक्षा कमी असू शकत नाही, For Default Supplier (Optional),डीफॉल्ट सप्लायर (वैकल्पिक) साठी, From date cannot be greater than To date,तारखेपासून तारखेपेक्षा जास्त असू शकत नाही, -Get items from,आयटम मिळवा, Group by,गट, In stock,स्टॉक मध्ये, Item name,आयटम नाव, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ऑर्डर ट्रेन्ड खरेदी, Purchase Receipt Trends,खरेदी पावती ट्रेन्ड, Purchase Register,खरेदी नोंदणी, Quotation Trends,कोटेशन ट्रेन्ड, -Quoted Item Comparison,उद्धृत बाबींचा तुलना, Received Items To Be Billed,बिल करायचे प्राप्त आयटम, Qty to Order,मागणी करण्यासाठी Qty, Requested Items To Be Transferred,विनंती आयटम हस्तांतरित करणे, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} द्वार Therapy Session overlaps with {0},थेरपी सत्र {0 with सह आच्छादित होते, Therapy Sessions Overlapping,थेरपी सत्रे आच्छादित, Therapy Plans,थेरपी योजना, +"Item Code, warehouse, quantity are required on row {0}","पंक्तीवर आयटम कोड, कोठार, प्रमाण आवश्यक आहे {0}", +Get Items from Material Requests against this Supplier,या पुरवठादाराविरूद्ध मटेरियल रिक्वेस्टचे आयटम मिळवा, +Enable European Access,युरोपियन प्रवेश सक्षम करा, +Creating Purchase Order ...,खरेदी ऑर्डर तयार करत आहे ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","खालील बाबींच्या डीफॉल्ट पुरवठादाराकडील पुरवठादार निवडा. निवडीवर, केवळ निवडलेल्या पुरवठादाराच्या वस्तूंच्या विरुद्ध खरेदी ऑर्डर देण्यात येईल.", +Row #{}: You must select {} serial numbers for item {}.,पंक्ती # {}: आपण आयटम for for साठी} numbers अनुक्रमांक निवडणे आवश्यक आहे., diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv index 1d9aa75987..1483844990 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -4238,7 +4238,6 @@ Download as JSON,Muat turun sebagai JSON, End date can not be less than start date,Tarikh akhir tidak boleh kurang daripada tarikh mula, For Default Supplier (Optional),Untuk pembekal lalai (pilihan), From date cannot be greater than To date,Dari Tarikh tidak boleh lebih besar daripada Dating, -Get items from,Mendapatkan barangan dari, Group by,Group By, In stock,Dalam stok, Item name,Nama Item, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Membeli Trend Pesanan, Purchase Receipt Trends,Trend Resit Pembelian, Purchase Register,Pembelian Daftar, Quotation Trends,Trend Sebut Harga, -Quoted Item Comparison,Perkara dipetik Perbandingan, Received Items To Be Billed,Barangan yang diterima dikenakan caj, Qty to Order,Qty Aturan, Requested Items To Be Transferred,Item yang diminta Akan Dipindahkan, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Item yang dirujuk oleh {0} Therapy Session overlaps with {0},Sesi Terapi bertindih dengan {0}, Therapy Sessions Overlapping,Sesi Terapi Bertindih, Therapy Plans,Rancangan Terapi, +"Item Code, warehouse, quantity are required on row {0}","Kod Item, gudang, kuantiti diperlukan pada baris {0}", +Get Items from Material Requests against this Supplier,Dapatkan Item dari Permintaan Bahan terhadap Pembekal ini, +Enable European Access,Dayakan Akses Eropah, +Creating Purchase Order ...,Membuat Pesanan Pembelian ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Pilih Pembekal dari Pembekal Lalai item di bawah. Pada pilihan, Pesanan Pembelian akan dibuat terhadap barang-barang milik Pembekal terpilih sahaja.", +Row #{}: You must select {} serial numbers for item {}.,Baris # {}: Anda mesti memilih {} nombor siri untuk item {}., diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv index a505a5ccb5..d15ec1ec71 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON အဖြစ် Download, End date can not be less than start date,အဆုံးနေ့စွဲ Start ကိုနေ့စွဲထက်လျော့နည်းမဖွစျနိုငျ, For Default Supplier (Optional),ပုံမှန်ပေးသွင်း (ရွေးချယ်နိုင်), From date cannot be greater than To date,နေ့စွဲကနေနေ့စွဲရန်ထက် သာ. ကြီးမြတ်မဖွစျနိုငျ, -Get items from,အထဲကပစ္စည်းတွေကို Get, Group by,Group မှဖြင့်, In stock,ကုန်ပစ္စည်းလက်ဝယ်ရှိ, Item name,item အမည်, @@ -8549,7 +8548,6 @@ Purchase Order Trends,အမိန့်ခေတ်ရေစီးကြော Purchase Receipt Trends,ဝယ်ယူခြင်းပြေစာခေတ်ရေစီးကြောင်း, Purchase Register,မှတ်ပုံတင်မည်ဝယ်ယူ, Quotation Trends,စျေးနှုန်းခေတ်ရေစီးကြောင်း, -Quoted Item Comparison,ကိုးကားအရာဝတ္ထုနှိုင်းယှဉ်ခြင်း, Received Items To Be Billed,ကြေညာတဲ့ခံရဖို့ရရှိထားသည့်ပစ္စည်းများ, Qty to Order,ရမလဲမှ Qty, Requested Items To Be Transferred,လွှဲပြောင်းရန်မေတ္တာရပ်ခံပစ္စည်းများ, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} ကညွှန Therapy Session overlaps with {0},ကုထုံးတွေ့ဆုံဆွေးနွေးမှု {0} နှင့်ထပ်နေသည်, Therapy Sessions Overlapping,ကုထုံးတွေ့ဆုံဆွေးနွေးပွဲ, Therapy Plans,ကုထုံးအစီအစဉ်များ, +"Item Code, warehouse, quantity are required on row {0}","item ကုဒ်, ဂိုဒေါင်, အရေအတွက်အတန်း {0} တွင်လိုအပ်သည်။", +Get Items from Material Requests against this Supplier,ဒီပေးသွင်းသူဆန့်ကျင်ပစ္စည်းတောင်းဆိုမှုများမှပစ္စည်းများရယူပါ, +Enable European Access,ဥရောပ Access ကိုဖွင့်, +Creating Purchase Order ...,ဝယ်ယူမှုအမိန့်ကိုဖန်တီးခြင်း ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",အောက်ဖော်ပြပါပစ္စည်းများ၏ပုံမှန်ပေးသွင်းသူထံမှပေးသွင်းရွေးချယ်ပါ။ ရွေးချယ်မှုတွင်ရွေးချယ်ထားသောပေးသွင်းသူနှင့်သာသက်ဆိုင်သောပစ္စည်းများကိုသာ ၀ ယ်ရန်အမိန့်ပေးလိမ့်မည်။, +Row #{}: You must select {} serial numbers for item {}.,Row # {} - item {} အတွက်နံပါတ်စဉ်ဆက်ရွေးပါ။, diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv index 33b1098e17..fbadc02327 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -4238,7 +4238,6 @@ Download as JSON,Downloaden als JSON, End date can not be less than start date,Einddatum kan niet vroeger zijn dan startdatum, For Default Supplier (Optional),Voor standaardleverancier (optioneel), From date cannot be greater than To date,Vanaf de datum kan niet groter zijn dan tot nu toe, -Get items from,Krijgen items uit, Group by,Groeperen volgens, In stock,Op voorraad, Item name,Artikelnaam, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Inkooporder Trends, Purchase Receipt Trends,Ontvangstbevestiging Trends, Purchase Register,Inkoop Register, Quotation Trends,Offerte Trends, -Quoted Item Comparison,Geciteerd Item Vergelijking, Received Items To Be Billed,Ontvangen artikelen nog te factureren, Qty to Order,Aantal te bestellen, Requested Items To Be Transferred,Aangevraagde Artikelen te Verplaatsen, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Het artikel waarnaar wordt Therapy Session overlaps with {0},Therapiesessie overlapt met {0}, Therapy Sessions Overlapping,Therapiesessies overlappen elkaar, Therapy Plans,Therapieplannen, +"Item Code, warehouse, quantity are required on row {0}","Artikelcode, magazijn, aantal zijn vereist op rij {0}", +Get Items from Material Requests against this Supplier,Artikelen ophalen van materiaalverzoeken tegen deze leverancier, +Enable European Access,Schakel Europese toegang in, +Creating Purchase Order ...,Inkooporder creëren ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Selecteer een leverancier uit de standaardleveranciers van de onderstaande items. Bij selectie wordt er alleen een inkooporder gemaakt voor artikelen van de geselecteerde leverancier., +Row #{}: You must select {} serial numbers for item {}.,Rij # {}: u moet {} serienummers voor artikel {} selecteren., diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv index bb8dbf25f3..150e5ca4a2 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -4238,7 +4238,6 @@ Download as JSON,Last ned som JSON, End date can not be less than start date,Sluttdato kan ikke være mindre enn startdato, For Default Supplier (Optional),For standardleverandør (valgfritt), From date cannot be greater than To date,Fra dato ikke kan være større enn To Date, -Get items from,Få elementer fra, Group by,Grupper etter, In stock,På lager, Item name,Navn, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Innkjøpsordre Trender, Purchase Receipt Trends,Kvitteringen Trender, Purchase Register,Kjøp Register, Quotation Trends,Anførsels Trender, -Quoted Item Comparison,Sitert Element Sammenligning, Received Items To Be Billed,Mottatte elementer å bli fakturert, Qty to Order,Antall å bestille, Requested Items To Be Transferred,Etterspør elementene som skal overføres, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Varen det refereres til av Therapy Session overlaps with {0},Behandlingsøkten overlapper med {0}, Therapy Sessions Overlapping,Terapisessioner overlapper hverandre, Therapy Plans,Terapiplaner, +"Item Code, warehouse, quantity are required on row {0}","Varekode, lager, antall kreves på rad {0}", +Get Items from Material Requests against this Supplier,Få varer fra materialforespørsler mot denne leverandøren, +Enable European Access,Aktiver europeisk tilgang, +Creating Purchase Order ...,Opprette innkjøpsordre ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Velg en leverandør fra standardleverandørene av artiklene nedenfor. Ved valg vil det kun gjøres en innkjøpsordre mot varer som tilhører den valgte leverandøren., +Row #{}: You must select {} serial numbers for item {}.,Rad nr. {}: Du må velge {} serienumre for varen {}., diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv index c30d055e02..8340b7272f 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -4238,7 +4238,6 @@ Download as JSON,Pobierz jako JSON, End date can not be less than start date,"Data zakończenia nie może być wcześniejsza, niż data rozpoczęcia", For Default Supplier (Optional),Dla dostawcy domyślnego (opcjonalnie), From date cannot be greater than To date,Data od - nie może być późniejsza niż Data do, -Get items from,Pobierz zawartość z, Group by,Grupuj według, In stock,W magazynie, Item name,Nazwa pozycji, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Trendy Zamówienia Kupna, Purchase Receipt Trends,Trendy Potwierdzenia Zakupu, Purchase Register,Rejestracja Zakupu, Quotation Trends,Trendy Wyceny, -Quoted Item Comparison,Porównanie cytowany Item, Received Items To Be Billed,Otrzymane przedmioty czekające na zaksięgowanie, Qty to Order,Ilość do zamówienia, Requested Items To Be Transferred,Proszę o Przetranferowanie Przedmiotów, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Pozycja, do której odwoł Therapy Session overlaps with {0},Sesja terapeutyczna pokrywa się z {0}, Therapy Sessions Overlapping,Nakładanie się sesji terapeutycznych, Therapy Plans,Plany terapii, +"Item Code, warehouse, quantity are required on row {0}","Kod pozycji, magazyn, ilość są wymagane w wierszu {0}", +Get Items from Material Requests against this Supplier,Pobierz pozycje z żądań materiałowych od tego dostawcy, +Enable European Access,Włącz dostęp w Europie, +Creating Purchase Order ...,Tworzenie zamówienia zakupu ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Wybierz dostawcę spośród domyślnych dostawców z poniższych pozycji. Po dokonaniu wyboru, Zamówienie zostanie złożone wyłącznie dla pozycji należących do wybranego Dostawcy.", +Row #{}: You must select {} serial numbers for item {}.,Wiersz nr {}: należy wybrać {} numery seryjne dla towaru {}., diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv index d8a51aede8..1dcaf48d79 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -4238,7 +4238,6 @@ Download as JSON,د Json په څیر ډاونلوډ کړئ, End date can not be less than start date,د پای نیټه نه شي کولای په پرتله د پیل نیټه کمه وي, For Default Supplier (Optional),د اصلي عرضه کوونکي لپاره (اختیاري), From date cannot be greater than To date,د نیټې څخه د نیټې څخه تر ډیره لوی نه وي, -Get items from,له توکي ترلاسه کړئ, Group by,ډله په, In stock,په ګدام کښي, Item name,د قالب نوم, @@ -8549,7 +8548,6 @@ Purchase Order Trends,پیري نظم رجحانات, Purchase Receipt Trends,رانيول رسيد رجحانات, Purchase Register,رانيول د نوم ثبتول, Quotation Trends,د داوطلبۍ رجحانات, -Quoted Item Comparison,له خولې د قالب پرتله, Received Items To Be Billed,ترلاسه توکي چې د محاسبې ته شي, Qty to Order,Qty ته اخلي., Requested Items To Be Transferred,غوښتنه سامان ته انتقال شي, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,هغه توکی چې د {0} Therapy Session overlaps with {0},د درملنې ناسته د over 0 with سره پراخه کیږي, Therapy Sessions Overlapping,د تهيريپي ناستې پوړونه, Therapy Plans,د درملنې پلانونه, +"Item Code, warehouse, quantity are required on row {0}",د توکي کوډ ، ګودام ، مقدار په قطار کې اړین دي {0}, +Get Items from Material Requests against this Supplier,د دې عرضه کونکي په وړاندې د موادو غوښتنو څخه توکي ترلاسه کړئ, +Enable European Access,اروپایی لاسرسی وړ کړئ, +Creating Purchase Order ...,د پیرود امر رامینځته کول ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",د لاندې شیانو ډیفالټ عرضه کونکو څخه یو عرضه کونکی غوره کړئ. په انتخاب کولو کې ، د پیرود امر به یوازې هغه انتخاب شوي چمتو کونکي پورې اړه لرونکي توکو په مقابل کې وي., +Row #{}: You must select {} serial numbers for item {}.,قطار # {}: تاسو باید د توکي {{لپاره سریال نمبرونه وټاکئ., diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv index 3289ef4f4c..3b8a0a0f43 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -4238,7 +4238,6 @@ Download as JSON,Baixe como Json, End date can not be less than start date,A Data de Término não pode ser mais recente que a Data de Início, For Default Supplier (Optional),Para fornecedor padrão (opcional), From date cannot be greater than To date,De data não pode ser maior que a data, -Get items from,Obter itens de, Group by,Agrupar Por, In stock,Em estoque, Item name,Nome do item, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Tendências de Ordens de Compra, Purchase Receipt Trends,Tendências de Recibo de Compra, Purchase Register,Registo de Compra, Quotation Trends,Tendências de Cotação, -Quoted Item Comparison,Comparação de Cotação de Item, Received Items To Be Billed,Itens Recebidos a Serem Faturados, Qty to Order,Qtd a Encomendar, Requested Items To Be Transferred,Itens Solicitados A Serem Transferidos, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,O item referenciado por {0} Therapy Session overlaps with {0},A sessão de terapia se sobrepõe a {0}, Therapy Sessions Overlapping,Sobreposição de sessões de terapia, Therapy Plans,Planos de Terapia, +"Item Code, warehouse, quantity are required on row {0}","Código do item, armazém, quantidade são necessários na linha {0}", +Get Items from Material Requests against this Supplier,Obtenha itens de solicitações de materiais contra este fornecedor, +Enable European Access,Habilitar acesso europeu, +Creating Purchase Order ...,Criando pedido de compra ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Selecione um fornecedor dos fornecedores padrão dos itens abaixo. Na seleção, um pedido de compra será feito apenas para itens pertencentes ao fornecedor selecionado.", +Row #{}: You must select {} serial numbers for item {}.,Nº da linha {}: você deve selecionar {} números de série para o item {}., diff --git a/erpnext/translations/pt_br.csv b/erpnext/translations/pt_br.csv index 620fe273c4..cda5ee85f9 100644 --- a/erpnext/translations/pt_br.csv +++ b/erpnext/translations/pt_br.csv @@ -985,6 +985,7 @@ Purpose must be one of {0},Objetivo deve ser um dos {0}, Qty,Qtde, Qty To Manufacture,Qtde para Fabricar, Qty for {0},Qtde para {0}, +Quality,QCคุณภาพ, Quality Goal.,Objetivos de Qualidade., Quality Inspection,Inspeção de Qualidade, Quality Inspection: {0} is not submitted for the item: {1} in row {2},Inspeção de Qualidade: {0} não foi submetida para o item: {1} na linha {2}, diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv index 54211e2ee5..643b8c5c3e 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -4238,7 +4238,6 @@ Download as JSON,Descărcați ca JSON, End date can not be less than start date,Data de Incheiere nu poate fi anterioara Datei de Incepere, For Default Supplier (Optional),Pentru furnizor implicit (opțional), From date cannot be greater than To date,De la data nu poate fi mai mare decât la data, -Get items from,Obține elemente din, Group by,Grupul De, In stock,In stoc, Item name,Denumire Articol, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Comandă de aprovizionare Tendințe, Purchase Receipt Trends,Tendințe Primirea de cumpărare, Purchase Register,Cumpărare Inregistrare, Quotation Trends,Cotație Tendințe, -Quoted Item Comparison,Compararea Articol citat, Received Items To Be Billed,Articole primite Pentru a fi facturat, Qty to Order,Cantitate pentru comandă, Requested Items To Be Transferred,Articole solicitate de transferat, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Elementul la care face refe Therapy Session overlaps with {0},Sesiunea de terapie se suprapune cu {0}, Therapy Sessions Overlapping,Sesiunile de terapie se suprapun, Therapy Plans,Planuri de terapie, +"Item Code, warehouse, quantity are required on row {0}","Codul articolului, depozitul, cantitatea sunt necesare pe rândul {0}", +Get Items from Material Requests against this Supplier,Obțineți articole din solicitările materiale împotriva acestui furnizor, +Enable European Access,Activați accesul european, +Creating Purchase Order ...,Se creează comanda de cumpărare ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Selectați un furnizor din furnizorii prestabiliți ai articolelor de mai jos. La selectare, o comandă de achiziție va fi făcută numai pentru articolele aparținând furnizorului selectat.", +Row #{}: You must select {} serial numbers for item {}.,Rândul # {}: trebuie să selectați {} numere de serie pentru articol {}., diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv index 00a19b360c..7fcb7b08f7 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -4238,7 +4238,6 @@ Download as JSON,Скачать как JSON, End date can not be less than start date,"Дата окончания не может быть меньше, чем Дата начала", For Default Supplier (Optional),Поставщик по умолчанию (необязательно), From date cannot be greater than To date,"С даты не может быть больше, чем к дате", -Get items from,Получить продукты от, Group by,Group By, In stock,В наличии, Item name,Название продукта, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Заказ на покупку Тенденции, Purchase Receipt Trends,Динамика Получения Поставок, Purchase Register,Покупка Становиться на учет, Quotation Trends,Динамика предложений, -Quoted Item Comparison,Цитируется Сравнение товара, Received Items To Be Billed,"Полученные товары, на которые нужно выписать счет", Qty to Order,Кол-во в заказ, Requested Items To Be Transferred,Запрашиваемые продукты к доставке, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"На товар, на ко Therapy Session overlaps with {0},Сеанс терапии совпадает с {0}, Therapy Sessions Overlapping,Совмещение сеансов терапии, Therapy Plans,Планы терапии, +"Item Code, warehouse, quantity are required on row {0}","Код товара, склад, количество требуются в строке {0}", +Get Items from Material Requests against this Supplier,Получить товары из запросов материалов к этому поставщику, +Enable European Access,Включить европейский доступ, +Creating Purchase Order ...,Создание заказа на поставку ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Выберите поставщика из списка поставщиков по умолчанию для позиций ниже. При выборе Заказ на поставку будет сделан в отношении товаров, принадлежащих только выбранному Поставщику.", +Row #{}: You must select {} serial numbers for item {}.,Строка № {}: необходимо выбрать {} серийных номеров для позиции {}., diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv index 5995db1719..64591399af 100644 --- a/erpnext/translations/rw.csv +++ b/erpnext/translations/rw.csv @@ -4238,7 +4238,6 @@ Download as JSON,Kuramo nka JSON, End date can not be less than start date,Itariki yo kurangiriraho ntishobora kuba munsi yitariki yo gutangiriraho, For Default Supplier (Optional),Kubisanzwe Bitanga (Bihitamo), From date cannot be greater than To date,Kuva ku italiki ntishobora kurenza Kumunsi, -Get items from,Shaka ibintu, Group by,Itsinda by, In stock,Mububiko, Item name,Izina ryikintu, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Kugura Ibiciro, Purchase Receipt Trends,Kugura inyemezabwishyu, Purchase Register,Kwiyandikisha, Quotation Trends,Imirongo, -Quoted Item Comparison,Kugereranya Ikintu Kugereranya, Received Items To Be Billed,Yakiriye Ibintu Byishyurwa, Qty to Order,Qty gutumiza, Requested Items To Be Transferred,Ibintu Byasabwe Kwimurwa, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Ikintu kivugwa na {0} - {1} Therapy Session overlaps with {0},Isomo ryo kuvura ryuzuzanya na {0}, Therapy Sessions Overlapping,Amasomo yo kuvura, Therapy Plans,Gahunda yo kuvura, +"Item Code, warehouse, quantity are required on row {0}","Kode yikintu, ububiko, ingano irakenewe kumurongo {0}", +Get Items from Material Requests against this Supplier,Shakisha Ibintu Mubisabwa Ibikoresho Kurwanya Utanga isoko, +Enable European Access,Gushoboza Uburayi, +Creating Purchase Order ...,Gushiraho gahunda yo kugura ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Hitamo Utanga isoko uhereye kubisanzwe bitanga ibintu hepfo. Muguhitamo, Iteka ryubuguzi rizakorwa kurwanya ibintu byatoranijwe gusa.", +Row #{}: You must select {} serial numbers for item {}.,Umurongo # {}: Ugomba guhitamo {} nimero yuruhererekane kubintu {}., diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv index ba45b42006..690c47332d 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON ලෙස බාගන්න, End date can not be less than start date,අවසන් දිනය ඇරඹුම් දිනය ඊට වඩා අඩු විය නොහැක, For Default Supplier (Optional),Default සැපයුම්කරු සඳහා (විකල්ප), From date cannot be greater than To date,දිනය සිට දිනට වඩා වැඩි විය නොහැක, -Get items from,සිට භාණ්ඩ ලබා ගන්න, Group by,කණ්ඩායම විසින්, In stock,ගබඩාවේ ඇත, Item name,අයිතමය නම, @@ -8549,7 +8548,6 @@ Purchase Order Trends,මිලදී ගැනීමේ නියෝගයක Purchase Receipt Trends,මිලදී ගැනීම රිසිට්පත ප්රවණතා, Purchase Register,මිලදී රෙජිස්ටර්, Quotation Trends,උද්ධෘත ප්රවණතා, -Quoted Item Comparison,උපුටා අයිතමය සංසන්දනය, Received Items To Be Billed,ලැබී අයිතම බිල්පතක්, Qty to Order,ඇණවුම් යවන ලද, Requested Items To Be Transferred,ඉල්ලන අයිතම මාරු කර, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1 by විසින Therapy Session overlaps with {0},චිකිත්සක සැසිය {0 with සමඟ අතිච්ඡාදනය වේ, Therapy Sessions Overlapping,චිකිත්සක සැසි අතිච්ඡාදනය, Therapy Plans,චිකිත්සක සැලසුම්, +"Item Code, warehouse, quantity are required on row {0}","Code 0 row පේළියේ අයිතම කේතය, ගබඩාව, ප්‍රමාණය අවශ්‍ය වේ", +Get Items from Material Requests against this Supplier,මෙම සැපයුම්කරුට එරෙහිව ද්‍රව්‍යමය ඉල්ලීම් වලින් අයිතම ලබා ගන්න, +Enable European Access,යුරෝපීය ප්‍රවේශය සක්‍රීය කරන්න, +Creating Purchase Order ...,මිලදී ගැනීමේ ඇණවුමක් නිර්මාණය කිරීම ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","පහත අයිතමවල පෙරනිමි සැපයුම්කරුවන්ගෙන් සැපයුම්කරුවෙකු තෝරන්න. තෝරාගැනීමේදී, තෝරාගත් සැපයුම්කරුට පමණක් අයත් භාණ්ඩවලට එරෙහිව මිලදී ගැනීමේ නියෝගයක් කරනු ලැබේ.", +Row #{}: You must select {} serial numbers for item {}.,පේළිය # {}: ඔබ item item අයිතමය සඳහා {} අනුක්‍රමික අංක තෝරාගත යුතුය., diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv index d31adbadc2..cb4a7fed4b 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -4238,7 +4238,6 @@ Download as JSON,Stiahnuť ako JSON, End date can not be less than start date,Dátum ukončenia nemôže byť menší ako dátum začiatku, For Default Supplier (Optional),Pre predvoleného dodávateľa (nepovinné), From date cannot be greater than To date,Dátum OD nemôže byť väčší ako dátum DO, -Get items from,Získať predmety z, Group by,Seskupit podle, In stock,Skladom, Item name,Názov položky, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Nákupní objednávka trendy, Purchase Receipt Trends,Doklad o koupi Trendy, Purchase Register,Nákup Register, Quotation Trends,Vývoje ponúk, -Quoted Item Comparison,Citoval Položka Porovnanie, Received Items To Be Billed,"Přijaté položek, které mají být účtovány", Qty to Order,Množství k objednávce, Requested Items To Be Transferred,Požadované položky mají být převedeny, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Položka, na ktorú odkazu Therapy Session overlaps with {0},Terapeutické sedenie sa prekrýva s {0}, Therapy Sessions Overlapping,Terapeutické sedenia sa prekrývajú, Therapy Plans,Terapeutické plány, +"Item Code, warehouse, quantity are required on row {0}","V riadku {0} sa vyžaduje kód položky, sklad, množstvo", +Get Items from Material Requests against this Supplier,Získajte položky z materiálových požiadaviek voči tomuto dodávateľovi, +Enable European Access,Umožniť európsky prístup, +Creating Purchase Order ...,Vytvára sa objednávka ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Z predvolených dodávateľov nižšie uvedených položiek vyberte dodávateľa. Pri výbere sa uskutoční objednávka iba na položky patriace vybranému dodávateľovi., +Row #{}: You must select {} serial numbers for item {}.,Riadok č. {}: Musíte zvoliť {} sériové čísla pre položku {}., diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv index 15795dccf0..8beec6be16 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -4238,7 +4238,6 @@ Download as JSON,Prenesite kot JSON, End date can not be less than start date,Datum konca ne sme biti krajši od začetnega datuma, For Default Supplier (Optional),Za privzeto dobavitelja (neobvezno), From date cannot be greater than To date,Od datuma ne more biti večje od datuma, -Get items from,Pridobi artikle iz, Group by,Skupina avtorja, In stock,Na zalogi, Item name,Ime predmeta, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Naročilnica Trendi, Purchase Receipt Trends,Nakup Prejem Trendi, Purchase Register,Nakup Register, Quotation Trends,Trendi ponudb, -Quoted Item Comparison,Citirano Točka Primerjava, Received Items To Be Billed,Prejete Postavke placevali, Qty to Order,Količina naročiti, Requested Items To Be Transferred,Zahtevane blago prenaša, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Element, na katerega se sk Therapy Session overlaps with {0},Seja terapije se prekriva z {0}, Therapy Sessions Overlapping,Terapijske seje se prekrivajo, Therapy Plans,Načrti terapije, +"Item Code, warehouse, quantity are required on row {0}","Koda artikla, skladišče, količina so obvezni v vrstici {0}", +Get Items from Material Requests against this Supplier,Pridobite izdelke iz materialnih zahtevkov pri tem dobavitelju, +Enable European Access,Omogoči evropski dostop, +Creating Purchase Order ...,Ustvarjanje naročilnice ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Izberite dobavitelja med privzetimi dobavitelji spodnjih elementov. Po izbiri bo naročilnica narejena samo za izdelke, ki pripadajo izbranemu dobavitelju.", +Row #{}: You must select {} serial numbers for item {}.,Vrstica # {}: Izbrati morate {} serijske številke za izdelek {}., diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv index 18d634e6c6..05aefa3602 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -4238,7 +4238,6 @@ Download as JSON,Shkarkoni si JSON, End date can not be less than start date,Data e mbarimit nuk mund të jetë më e shkurtër se data fillestare, For Default Supplier (Optional),Për Furnizuesin e Parazgjedhur (fakultativ), From date cannot be greater than To date,Nga Data nuk mund të jetë më i madh se deri më sot, -Get items from,Të marrë sendet nga, Group by,Grupi Nga, In stock,Në gjendje, Item name,Item Emri, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Rendit Blerje Trendet, Purchase Receipt Trends,Trendet Receipt Blerje, Purchase Register,Blerje Regjistrohu, Quotation Trends,Kuotimit Trendet, -Quoted Item Comparison,Cituar Item Krahasimi, Received Items To Be Billed,Items marra Për të faturohet, Qty to Order,Qty të Rendit, Requested Items To Be Transferred,Items kërkuar të transferohet, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Artikulli i referuar nga {0 Therapy Session overlaps with {0},Sesioni i terapisë mbivendoset me {0}, Therapy Sessions Overlapping,Seancat e Terapisë Mbivendosen, Therapy Plans,Planet e Terapisë, +"Item Code, warehouse, quantity are required on row {0}","Kodi i artikullit, depoja, sasia kërkohen në rreshtin {0}", +Get Items from Material Requests against this Supplier,Merrni Artikuj nga Kërkesat Materiale kundër këtij Furnizuesi, +Enable European Access,Aktivizo Aksesin Evropian, +Creating Purchase Order ...,Krijimi i urdhrit të blerjes ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Zgjidhni një furnitor nga furnitorët e parazgjedhur të artikujve më poshtë. Gjatë zgjedhjes, një Urdhër Blerje do të bëhet vetëm kundër artikujve që i përkasin vetëm Furnizuesit të zgjedhur.", +Row #{}: You must select {} serial numbers for item {}.,Rreshti # {}: Duhet të zgjidhni {} numrat rendorë për artikullin {}., diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv index 44c49467b6..b507f74f09 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -4238,7 +4238,6 @@ Download as JSON,Преузми као ЈСОН, End date can not be less than start date,"Дата окончания не может быть меньше , чем Дата начала", For Default Supplier (Optional),За подразумевани добављач, From date cannot be greater than To date,Од датума не може бити већа него до сада, -Get items from,Гет ставке из, Group by,Група По, In stock,На лагеру, Item name,Назив, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Куповина Трендови Ордер, Purchase Receipt Trends,Куповина Трендови Пријем, Purchase Register,Куповина Регистрација, Quotation Trends,Котировочные тенденции, -Quoted Item Comparison,Цитирано артикла Поређење, Received Items To Be Billed,Примљени артикала буду наплаћени, Qty to Order,Количина по поруџбини, Requested Items To Be Transferred,Тражени Артикли ће се пренети, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Ставка на коју Therapy Session overlaps with {0},Сесија терапије се преклапа са {0}, Therapy Sessions Overlapping,Преклапање сесија терапије, Therapy Plans,Планови терапије, +"Item Code, warehouse, quantity are required on row {0}","Шифра артикла, складиште, количина су обавезни у реду {0}", +Get Items from Material Requests against this Supplier,Узмите предмете од материјалних захтева против овог добављача, +Enable European Access,Омогућити европски приступ, +Creating Purchase Order ...,Креирање налога за куповину ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Изаберите добављача од задатих добављача доле наведених ставки. Након одабира, наруџбеница ће се извршити само за производе који припадају изабраном добављачу.", +Row #{}: You must select {} serial numbers for item {}.,Ред # {}: Морате одабрати {} серијске бројеве за ставку {}., diff --git a/erpnext/translations/sr_sp.csv b/erpnext/translations/sr_sp.csv index 3304dfcf26..5e7ae79781 100644 --- a/erpnext/translations/sr_sp.csv +++ b/erpnext/translations/sr_sp.csv @@ -649,7 +649,6 @@ No,Ne, Yes,Da, Chart of Accounts,Kontni plan, Customer database.,Korisnička baza podataka, -Get items from,Dodaj stavke iz, Item name,Naziv artikla, No employee found,Zaposleni nije pronađen, Open Projects ,Otvoreni projekti, diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv index 608bb46577..57e02792f4 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -4238,7 +4238,6 @@ Download as JSON,Ladda ner som JSON, End date can not be less than start date,Slutdatum kan inte vara mindre än startdatum, For Default Supplier (Optional),För standardleverantör (tillval), From date cannot be greater than To date,Från datum kan inte vara större än till datum, -Get items from,Få objekt från, Group by,Gruppera efter, In stock,I lager, Item name,Produktnamn, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Inköpsorder Trender, Purchase Receipt Trends,Kvitto Trender, Purchase Register,Inköpsregistret, Quotation Trends,Offert Trender, -Quoted Item Comparison,Citerade föremål Jämförelse, Received Items To Be Billed,Mottagna objekt som ska faktureras, Qty to Order,Antal till Ordern, Requested Items To Be Transferred,Efterfrågade artiklar som ska överföras, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Varan som {0} - {1} referer Therapy Session overlaps with {0},Terapisessionen överlappar med {0}, Therapy Sessions Overlapping,Terapisessioner överlappar varandra, Therapy Plans,Terapiplaner, +"Item Code, warehouse, quantity are required on row {0}","Artikelkod, lager, kvantitet krävs på rad {0}", +Get Items from Material Requests against this Supplier,Få objekt från materialförfrågningar mot denna leverantör, +Enable European Access,Aktivera europeisk åtkomst, +Creating Purchase Order ...,Skapar inköpsorder ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",Välj en leverantör bland standardleverantörerna av artiklarna nedan. Vid val kommer en inköpsorder endast göras mot föremål som tillhör vald leverantör., +Row #{}: You must select {} serial numbers for item {}.,Rad nr {}: Du måste välja {} serienummer för artikeln {}., diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv index bffc1a8d47..3595727666 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -4238,7 +4238,6 @@ Download as JSON,Pakua kama JSON, End date can not be less than start date,Tarehe ya Mwisho haiwezi kuwa chini ya Tarehe ya Mwanzo, For Default Supplier (Optional),Kwa Default Supplier (hiari), From date cannot be greater than To date,Kutoka Tarehe haiwezi kuwa kubwa kuliko Tarehe, -Get items from,Pata vitu kutoka, Group by,Kikundi Kwa, In stock,Katika hisa, Item name,Jina la Kipengee, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Mwelekeo wa Utaratibu wa Ununuzi, Purchase Receipt Trends,Ununuzi Mwelekeo wa Receipt, Purchase Register,Daftari ya Ununuzi, Quotation Trends,Mwelekeo wa Nukuu, -Quoted Item Comparison,Ilipendekeza Kulinganishwa kwa Bidhaa, Received Items To Be Billed,Vipokee Vipokee vya Kulipwa, Qty to Order,Uchina kwa Amri, Requested Items To Be Transferred,Vitu Vilivyoombwa Ili Kuhamishwa, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Bidhaa iliyorejelewa na {0} Therapy Session overlaps with {0},Kipindi cha Tiba hupishana na {0}, Therapy Sessions Overlapping,Vikao vya Tiba vinaingiliana, Therapy Plans,Mipango ya Tiba, +"Item Code, warehouse, quantity are required on row {0}","Nambari ya Bidhaa, ghala, idadi inahitajika kwenye safu mlalo {0}", +Get Items from Material Requests against this Supplier,Pata Vitu kutoka kwa Maombi ya Nyenzo dhidi ya Muuzaji huyu, +Enable European Access,Washa Ufikiaji wa Uropa, +Creating Purchase Order ...,Inaunda Agizo la Ununuzi ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Chagua Muuzaji kutoka kwa Wasambazaji Default wa vitu hapa chini. Wakati wa kuchagua, Agizo la Ununuzi litafanywa dhidi ya vitu vya Muuzaji aliyechaguliwa tu.", +Row #{}: You must select {} serial numbers for item {}.,Mstari # {}: Lazima uchague {} nambari za serial za kipengee {}., diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv index b57668d8cf..100f0e9300 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON ஆக பதிவிறக்கவும், End date can not be less than start date,முடிவு தேதி தொடங்கும் நாள் விட குறைவாக இருக்க முடியாது, For Default Supplier (Optional),இயல்புநிலை வழங்குநருக்கு (விரும்பினால்), From date cannot be greater than To date,தேதி முதல் இன்று வரை விட முடியாது, -Get items from,இருந்து பொருட்களை பெற, Group by,குழு மூலம், In stock,கையிருப்பில், Item name,பொருள் பெயர், @@ -8549,7 +8548,6 @@ Purchase Order Trends,ஆர்டர் போக்குகள் வா Purchase Receipt Trends,ரிசிப்ட் போக்குகள் வாங்குவதற்கு, Purchase Register,பதிவு வாங்குவதற்கு, Quotation Trends,மேற்கோள் போக்குகள், -Quoted Item Comparison,மேற்கோள் காட்டப்பட்டது பொருள் ஒப்பீட்டு, Received Items To Be Billed,கட்டணம் பெறப்படும் பொருட்கள், Qty to Order,அளவு ஒழுங்கிற்கு, Requested Items To Be Transferred,மாற்றப்படுவதற்கு கோரப்பட்ட விடயங்கள், @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1 by ஆல் க Therapy Session overlaps with {0},சிகிச்சை அமர்வு {0 with உடன் ஒன்றுடன் ஒன்று, Therapy Sessions Overlapping,சிகிச்சை அமர்வுகள் ஒன்றுடன் ஒன்று, Therapy Plans,சிகிச்சை திட்டங்கள், +"Item Code, warehouse, quantity are required on row {0}","Code 0 row வரிசையில் உருப்படி குறியீடு, கிடங்கு, அளவு தேவை", +Get Items from Material Requests against this Supplier,இந்த சப்ளையருக்கு எதிரான பொருள் கோரிக்கைகளிலிருந்து பொருட்களைப் பெறுங்கள், +Enable European Access,ஐரோப்பிய அணுகலை இயக்கு, +Creating Purchase Order ...,கொள்முதல் ஆணையை உருவாக்குதல் ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","கீழே உள்ள பொருட்களின் இயல்புநிலை சப்ளையர்களிடமிருந்து ஒரு சப்ளையரைத் தேர்ந்தெடுக்கவும். தேர்ந்தெடுக்கும் போது, தேர்ந்தெடுக்கப்பட்ட சப்ளையருக்கு மட்டுமே சொந்தமான பொருட்களுக்கு எதிராக கொள்முதல் ஆணை செய்யப்படும்.", +Row #{}: You must select {} serial numbers for item {}.,வரிசை # {}: நீங்கள் item item உருப்படிக்கு {} வரிசை எண்களைத் தேர்ந்தெடுக்க வேண்டும்., diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv index e5ba32c2f5..047d07731e 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON గా డౌన్‌లోడ్ చేయండి, End date can not be less than start date,ముగింపు తేదీ ప్రారంభ తేదీ కంటే తక్కువ ఉండకూడదు, For Default Supplier (Optional),డిఫాల్ట్ సరఫరాదారు (ఐచ్ఛిక) కోసం, From date cannot be greater than To date,తేదీ నుండి తేదీ వరకు ఎక్కువ ఉండకూడదు, -Get items from,నుండి అంశాలను పొందండి, Group by,గ్రూప్ ద్వారా, In stock,అందుబాటులో ఉంది, Item name,అంశం పేరు, @@ -8549,7 +8548,6 @@ Purchase Order Trends,ఆర్డర్ ట్రెండ్లులో క Purchase Receipt Trends,కొనుగోలు రసీదులు ట్రెండ్లులో, Purchase Register,కొనుగోలు నమోదు, Quotation Trends,కొటేషన్ ట్రెండ్లులో, -Quoted Item Comparison,ఉల్లేఖించిన అంశం పోలిక, Received Items To Be Billed,స్వీకరించిన అంశాలు బిల్ టు, Qty to Order,ఆర్డర్ చేయటం అంశాల, Requested Items To Be Transferred,అభ్యర్థించిన అంశాలు బదిలీ, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1 by చే సూ Therapy Session overlaps with {0},థెరపీ సెషన్ {0 with తో అతివ్యాప్తి చెందుతుంది, Therapy Sessions Overlapping,థెరపీ సెషన్స్ అతివ్యాప్తి, Therapy Plans,చికిత్స ప్రణాళికలు, +"Item Code, warehouse, quantity are required on row {0}","Code 0 row వరుసలో అంశం కోడ్, గిడ్డంగి, పరిమాణం అవసరం", +Get Items from Material Requests against this Supplier,ఈ సరఫరాదారుకు వ్యతిరేకంగా మెటీరియల్ అభ్యర్థనల నుండి అంశాలను పొందండి, +Enable European Access,యూరోపియన్ ప్రాప్యతను ప్రారంభించండి, +Creating Purchase Order ...,కొనుగోలు క్రమాన్ని సృష్టిస్తోంది ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","దిగువ వస్తువుల డిఫాల్ట్ సరఫరాదారుల నుండి సరఫరాదారుని ఎంచుకోండి. ఎంపికపై, ఎంచుకున్న సరఫరాదారుకు చెందిన వస్తువులపై మాత్రమే కొనుగోలు ఆర్డర్ చేయబడుతుంది.", +Row #{}: You must select {} serial numbers for item {}.,అడ్డు వరుస # {}: మీరు తప్పక item} అంశం కోసం {} క్రమ సంఖ్యలను ఎంచుకోవాలి., diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv index 90c3a9a6ad..71233ec8fa 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -4238,7 +4238,6 @@ Download as JSON,ดาวน์โหลดเป็น JSON, End date can not be less than start date,วันที่สิ้นสุด ไม่สามารถ จะน้อยกว่า วันเริ่มต้น, For Default Supplier (Optional),สำหรับผู้จัดจำหน่ายเริ่มต้น (ตัวเลือก), From date cannot be greater than To date,จากวันที่ไม่สามารถมากกว่าวันที่, -Get items from,รับรายการจาก, Group by,กลุ่มตาม, In stock,มีสินค้าในสต๊อก, Item name,ชื่อรายการ, @@ -8549,7 +8548,6 @@ Purchase Order Trends,แนวโน้มการสั่งซื้อ, Purchase Receipt Trends,ซื้อแนวโน้มใบเสร็จรับเงิน, Purchase Register,สั่งซื้อสมัครสมาชิก, Quotation Trends,ใบเสนอราคา แนวโน้ม, -Quoted Item Comparison,เปรียบเทียบรายการที่ยกมา, Received Items To Be Billed,รายการที่ได้รับจะถูกเรียกเก็บเงิน, Qty to Order,จำนวนการสั่งซื้อสินค้า, Requested Items To Be Transferred,รายการที่ได้รับการร้องขอจะถูกถ่ายโอน, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,รายการที่ Therapy Session overlaps with {0},เซสชันการบำบัดทับซ้อนกับ {0}, Therapy Sessions Overlapping,การบำบัดที่ทับซ้อนกัน, Therapy Plans,แผนการบำบัด, +"Item Code, warehouse, quantity are required on row {0}",ต้องระบุรหัสสินค้าคลังสินค้าปริมาณในแถว {0}, +Get Items from Material Requests against this Supplier,รับรายการจากคำขอวัสดุกับซัพพลายเออร์นี้, +Enable European Access,เปิดใช้งาน European Access, +Creating Purchase Order ...,กำลังสร้างใบสั่งซื้อ ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",เลือกซัพพลายเออร์จากซัพพลายเออร์เริ่มต้นของรายการด้านล่าง ในการเลือกใบสั่งซื้อจะทำกับสินค้าที่เป็นของซัพพลายเออร์ที่เลือกเท่านั้น, +Row #{}: You must select {} serial numbers for item {}.,แถว # {}: คุณต้องเลือก {} หมายเลขซีเรียลสำหรับรายการ {}, diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv index ebb754a217..9e7ba4d142 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -4238,7 +4238,6 @@ Download as JSON,JSON olarak indir, End date can not be less than start date,"Bitiş Tarihi, Başlangıç Tarihinden daha az olamaz", For Default Supplier (Optional),Varsayılan Tedarikçi için (İsteğe bağlı), From date cannot be greater than To date,Tarihten itibaren tarihe kadardan ileride olamaz, -Get items from,Öğeleri alın, Group by,Grup tarafından, In stock,Stokta var, Item name,Ürün Adı, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Satın Alma Sipariş Analizi, Purchase Receipt Trends,Satın Alma Teslim Alma Analizi, Purchase Register,Satın alma kaydı, Quotation Trends,Teklif Trendleri, -Quoted Item Comparison,Kote Ürün Karşılaştırma, Received Items To Be Billed,Faturalanacak Alınan Malzemeler, Qty to Order,Sipariş Miktarı, Requested Items To Be Transferred,Transfer edilmesi istenen Ürünler, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} tarafından refer Therapy Session overlaps with {0},"Terapi Oturumu, {0} ile çakışıyor", Therapy Sessions Overlapping,Çakışan Terapi Seansları, Therapy Plans,Tedavi Planları, +"Item Code, warehouse, quantity are required on row {0}","{0}. Satırda Öğe Kodu, depo, miktar gerekli", +Get Items from Material Requests against this Supplier,Bu Tedarikçiye Karşı Malzeme Taleplerinden Ürün Alın, +Enable European Access,Avrupa Erişimini Etkinleştir, +Creating Purchase Order ...,Satın Alma Siparişi Oluşturuluyor ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Aşağıdaki öğelerin Varsayılan Tedarikçilerinden bir Tedarikçi seçin. Seçim üzerine, yalnızca seçilen Tedarikçiye ait ürünler için bir Satın Alma Siparişi verilecektir.", +Row #{}: You must select {} serial numbers for item {}.,Satır # {}: {} öğesi için {} seri numaralarını seçmelisiniz., diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv index b43346c414..53e2df518c 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -4238,7 +4238,6 @@ Download as JSON,Завантажити як JSON, End date can not be less than start date,"Дата завершення не може бути меншою, ніж Дата початку", For Default Supplier (Optional),Для постачальника за замовчуванням (необов'язково), From date cannot be greater than To date,"Від дати не може бути більше, ніж Дата", -Get items from,Отримати елементи з, Group by,Групувати за, In stock,В наявності, Item name,Назва виробу, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Динаміка Замовлень на придбанн Purchase Receipt Trends,Тренд прихідних накладних, Purchase Register,Реєстр закупівель, Quotation Trends,Тренд пропозицій, -Quoted Item Comparison,Цитується Порівняння товару, Received Items To Be Billed,"Отримані позиції, на які не виставлені рахунки", Qty to Order,К-сть для замовлення, Requested Items To Be Transferred,"Номенклатура, що ми замовили але не отримали", @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,"Позиція, на як Therapy Session overlaps with {0},Сеанс терапії накладається на {0}, Therapy Sessions Overlapping,"Сеанси терапії, що перекриваються", Therapy Plans,Плани терапії, +"Item Code, warehouse, quantity are required on row {0}","Код товару, склад, кількість вказуються в рядку {0}", +Get Items from Material Requests against this Supplier,Отримуйте предмети від матеріальних запитів у цього постачальника, +Enable European Access,Увімкнути європейський доступ, +Creating Purchase Order ...,Створення замовлення на придбання ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Виберіть постачальника з постачальників за замовчуванням із наведених нижче пунктів. При виборі замовлення на замовлення буде зроблено лише щодо товарів, що належать обраному Постачальнику.", +Row #{}: You must select {} serial numbers for item {}.,Рядок № {}: Ви повинні вибрати {} серійні номери для товару {}., diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv index 7d9cacd272..aaaef5895f 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -4238,7 +4238,6 @@ Download as JSON,Json کی طرح ڈاؤن لوڈ کریں۔, End date can not be less than start date,ختم ہونے کی تاریخ شروع کرنے کی تاریخ کے مقابلے میں کم نہیں ہو سکتا, For Default Supplier (Optional),ڈیفالٹ سپلائر کے لئے (اختیاری), From date cannot be greater than To date,تاریخ سے تاریخ سے زیادہ نہیں ہوسکتی ہے, -Get items from,سے اشیاء حاصل, Group by,گروپ سے, In stock,اسٹاک میں, Item name,نام شے, @@ -8549,7 +8548,6 @@ Purchase Order Trends,آرڈر رجحانات خریدیں, Purchase Receipt Trends,خریداری کی رسید رجحانات, Purchase Register,خریداری رجسٹر, Quotation Trends,کوٹیشن رجحانات, -Quoted Item Comparison,نقل آئٹم موازنہ, Received Items To Be Billed,موصول ہونے والی اشیاء بل بھیجا جائے کرنے کے لئے, Qty to Order,آرڈر کی مقدار, Requested Items To Be Transferred,درخواست کی اشیاء منتقل کیا جائے, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,جس شے کا حوالہ { Therapy Session overlaps with {0},تھراپی سیشن {0 with کے ساتھ اوورلیپ ہوتا ہے, Therapy Sessions Overlapping,تھراپی سیشن اوور لیپنگ, Therapy Plans,تھراپی کے منصوبے, +"Item Code, warehouse, quantity are required on row {0}",آئٹم کوڈ ، گودام ، مقدار قطار میں ضروری ہے {0}, +Get Items from Material Requests against this Supplier,اس سپلائر کے خلاف مادی درخواستوں سے اشیا حاصل کریں, +Enable European Access,یورپی رسائی کو فعال کریں, +Creating Purchase Order ...,خریداری کا آرڈر تشکیل دے رہا ہے…, +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",ذیل میں آئٹمز کے ڈیفالٹ سپلائرز میں سے ایک سپلائر منتخب کریں۔ انتخاب پر ، خریداری کا آرڈر صرف منتخب کردہ سپلائر سے متعلقہ اشیاء کے خلاف کیا جائے گا۔, +Row #{}: You must select {} serial numbers for item {}.,قطار # {}: آپ کو آئٹم for for کے لئے}} سیریل نمبرز منتخب کرنا ہوں گے۔, diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv index f05b582624..c983797aae 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -4238,7 +4238,6 @@ Download as JSON,Json sifatida yuklab oling, End date can not be less than start date,Tugash sanasi Boshlanish sanasidan past bo'lishi mumkin emas, For Default Supplier (Optional),Standart yetkazib beruvchi (ixtiyoriy), From date cannot be greater than To date,Sana Sana Sana uchun katta bo'lishi mumkin emas, -Get items from,Elementlarni oling, Group by,Guruh tomonidan, In stock,Omborda mavjud; sotuvda mavjud, Item name,Mavzu nomi, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Buyurtma tendentsiyalarini sotib olish, Purchase Receipt Trends,Qabul rejasi xaridlari, Purchase Register,Xarid qilish Register, Quotation Trends,Iqtiboslar tendentsiyalari, -Quoted Item Comparison,Qisqartirilgan ob'ektni solishtirish, Received Items To Be Billed,Qabul qilinadigan buyumlar, Qty to Order,Buyurtma miqdori, Requested Items To Be Transferred,Talab qilingan narsalarni yuborish, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0} - {1} tomonidan havola Therapy Session overlaps with {0},Terapiya mashg'uloti {0} bilan takrorlanadi, Therapy Sessions Overlapping,Terapiya mashg'ulotlari bir-birini qoplaydi, Therapy Plans,Terapiya rejalari, +"Item Code, warehouse, quantity are required on row {0}","Mahsulot kodi, ombor, miqdori {0} qatorida talab qilinadi", +Get Items from Material Requests against this Supplier,Ushbu etkazib beruvchiga qarshi material talablaridan narsalarni oling, +Enable European Access,Evropaga kirishni yoqish, +Creating Purchase Order ...,Xarid buyurtmasi yaratilmoqda ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Quyidagi mahsulotlarning standart etkazib beruvchilardan etkazib beruvchini tanlang. Tanlovda, faqat tanlangan etkazib beruvchiga tegishli buyumlarga qarshi Sotib olish to'g'risida buyurtma beriladi.", +Row #{}: You must select {} serial numbers for item {}.,Qator # {}: Siz {} element uchun seriya raqamlarini {} tanlashingiz kerak., diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv index a76c9fdec6..03ff2ccc38 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -4238,7 +4238,6 @@ Download as JSON,Tải xuống dưới dạng JSON, End date can not be less than start date,Ngày kết thúc không thể nhỏ hơn Bắt đầu ngày, For Default Supplier (Optional),Đối với nhà cung cấp mặc định (Tùy chọn), From date cannot be greater than To date,"""Từ ngày"" không có thể lớn hơn ""Đến ngày""", -Get items from,Lấy dữ liệu từ, Group by,Nhóm theo, In stock,Trong kho, Item name,Tên hàng, @@ -8549,7 +8548,6 @@ Purchase Order Trends,Xu hướng mua hàng, Purchase Receipt Trends,Xu hướng của biên lai nhận hàng, Purchase Register,Đăng ký mua, Quotation Trends,Các Xu hướng dự kê giá, -Quoted Item Comparison,So sánh mẫu hàng đã được báo giá, Received Items To Be Billed,Những mẫu hàng nhận được để lập hóa đơn, Qty to Order,Số lượng đặt hàng, Requested Items To Be Transferred,Mục yêu cầu được chuyển giao, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,Mặt hàng được tham c Therapy Session overlaps with {0},Phiên trị liệu trùng lặp với {0}, Therapy Sessions Overlapping,Các phiên trị liệu chồng chéo, Therapy Plans,Kế hoạch trị liệu, +"Item Code, warehouse, quantity are required on row {0}","Mã hàng, kho, số lượng là bắt buộc trên hàng {0}", +Get Items from Material Requests against this Supplier,Nhận các mặt hàng từ các Yêu cầu Vật liệu đối với Nhà cung cấp này, +Enable European Access,Bật quyền truy cập Châu Âu, +Creating Purchase Order ...,Tạo Đơn đặt hàng ..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Chọn Nhà cung cấp từ các Nhà cung cấp mặc định của các mục dưới đây. Khi lựa chọn, Đơn đặt hàng sẽ được thực hiện đối với các mặt hàng chỉ thuộc về Nhà cung cấp đã chọn.", +Row #{}: You must select {} serial numbers for item {}.,Hàng # {}: Bạn phải chọn {} số sê-ri cho mặt hàng {}., diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index 97c69fecb9..716f1f2f31 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -4238,7 +4238,6 @@ Download as JSON,下载为JSON, End date can not be less than start date,结束日期不能小于开始日期, For Default Supplier (Optional),对于默认供应商(可选), From date cannot be greater than To date,从日期不能大于到日期, -Get items from,从...获取物料, Group by,分组基于, In stock,有现货, Item name,物料名称, @@ -8549,7 +8548,6 @@ Purchase Order Trends,采购订单趋势, Purchase Receipt Trends,采购收货趋势, Purchase Register,采购台帐, Quotation Trends,报价趋势, -Quoted Item Comparison,项目报价比较, Received Items To Be Billed,待开费用清单已收货物料, Qty to Order,待下单数量, Requested Items To Be Transferred,已申请待移转物料, @@ -9834,3 +9832,9 @@ The item referenced by {0} - {1} is already invoiced,{0}-{1}引用的商品已 Therapy Session overlaps with {0},治疗会话与{0}重叠, Therapy Sessions Overlapping,治疗会议重叠, Therapy Plans,治疗计划, +"Item Code, warehouse, quantity are required on row {0}",在第{0}行中需要提供物料代码,仓库,数量, +Get Items from Material Requests against this Supplier,从针对此供应商的物料请求中获取物料, +Enable European Access,启用欧洲访问, +Creating Purchase Order ...,创建采购订单..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",从以下各项的默认供应商中选择供应商。选择后,将针对仅属于所选供应商的项目下达采购订单。, +Row #{}: You must select {} serial numbers for item {}.,行号{}:您必须为项目{}选择{}序列号。, diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv index 2de0df0d55..1cc7d8773e 100644 --- a/erpnext/translations/zh_tw.csv +++ b/erpnext/translations/zh_tw.csv @@ -3967,7 +3967,6 @@ Download as JSON,下載為JSON, End date can not be less than start date,結束日期不能小於開始日期, For Default Supplier (Optional),對於默認供應商(可選), From date cannot be greater than To date,起始日期不能大於結束日期, -Get items from,取得項目來源, Group by,集團通過, In stock,有現貨, Item name,項目名稱, @@ -7967,7 +7966,6 @@ Purchase Order Trends,採購訂單趨勢, Purchase Receipt Trends,採購入庫趨勢, Purchase Register,購買註冊, Quotation Trends,報價趨勢, -Quoted Item Comparison,項目報價比較, Received Items To Be Billed,待付款的收受品項, Qty to Order,訂購數量, Requested Items To Be Transferred,將要轉倉的需求項目, @@ -9136,3 +9134,9 @@ The item referenced by {0} - {1} is already invoiced,{0}-{1}引用的商品已 Therapy Session overlaps with {0},治療會話與{0}重疊, Therapy Sessions Overlapping,治療會議重疊, Therapy Plans,治療計劃, +"Item Code, warehouse, quantity are required on row {0}",在第{0}行中需要提供物料代碼,倉庫,數量, +Get Items from Material Requests against this Supplier,從針對此供應商的物料請求中獲取物料, +Enable European Access,啟用歐洲訪問, +Creating Purchase Order ...,創建採購訂單..., +"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.",從以下各項的默認供應商中選擇供應商。選擇後,將針對僅屬於所選供應商的項目下達採購訂單。, +Row #{}: You must select {} serial numbers for item {}.,行號{}:您必須為項目{}選擇{}序列號。, From 30d58cc3d741f11c7c5a6e57c0464941289943ce Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 9 Nov 2020 18:41:03 +0530 Subject: [PATCH 111/154] feat: Consider Holiday List in Student Leave Application and Attendance (#23388) * feat: Consider holiday list in Student Attendance and Leave Application * feat: Show Holidays as 'H' in Student Monthly Attendance Sheet * fix: check if date is a holiday in attendance reports * test: skip attendance record creation for holidays * fix: holiday list validation * fix: clean up after test * fix: codacy * fix: show date in user format * fix: remove ununsed imports * fix: sider * fix: test Co-authored-by: Nabin Hait --- .../student_attendance/student_attendance.py | 20 +++++- .../student_attendance_tool.py | 8 +-- .../student_leave_application.json | 9 ++- .../student_leave_application.py | 30 +++++++- .../test_student_leave_application.py | 71 +++++++++++++++---- .../absent_student_report.py | 9 ++- .../student_batch_wise_attendance.py | 8 ++- .../student_monthly_attendance_sheet.py | 26 ++++++- 8 files changed, 158 insertions(+), 23 deletions(-) diff --git a/erpnext/education/doctype/student_attendance/student_attendance.py b/erpnext/education/doctype/student_attendance/student_attendance.py index 72a8f55c66..2e9e6cf8d6 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance.py +++ b/erpnext/education/doctype/student_attendance/student_attendance.py @@ -6,8 +6,10 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import get_link_to_form, getdate +from frappe.utils import get_link_to_form, getdate, formatdate +from erpnext import get_default_company from erpnext.education.api import get_student_group_students +from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday class StudentAttendance(Document): def validate(self): @@ -17,6 +19,7 @@ class StudentAttendance(Document): self.set_student_group() self.validate_student() self.validate_duplication() + self.validate_is_holiday() def set_date(self): if self.course_schedule: @@ -78,3 +81,18 @@ class StudentAttendance(Document): record = get_link_to_form('Student Attendance', attendance_record) frappe.throw(_('Student Attendance record {0} already exists against the Student {1}') .format(record, frappe.bold(self.student)), title=_('Duplicate Entry')) + + def validate_is_holiday(self): + holiday_list = get_holiday_list() + if is_holiday(holiday_list, self.date): + frappe.throw(_('Attendance cannot be marked for {0} as it is a holiday.').format( + frappe.bold(formatdate(self.date)))) + +def get_holiday_list(company=None): + if not company: + company = get_default_company() or frappe.get_all('Company')[0].name + + holiday_list = frappe.get_cached_value('Company', company, 'default_holiday_list') + if not holiday_list: + frappe.throw(_('Please set a default Holiday List for Company {0}').format(frappe.bold(get_default_company()))) + return holiday_list diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py index be2644077a..028db91881 100644 --- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py +++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py @@ -20,10 +20,10 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , \ filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") - if not student_list: - student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , + if not student_list: + student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") - + if course_schedule: student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ course_schedule= %s''', (course_schedule), as_dict=1) @@ -32,7 +32,7 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour student_group= %s and date= %s and \ (course_schedule is Null or course_schedule='')''', (student_group, date), as_dict=1) - + for attendance in student_attendance_list: for student in student_list: if student.student == attendance.student: diff --git a/erpnext/education/doctype/student_leave_application/student_leave_application.json b/erpnext/education/doctype/student_leave_application/student_leave_application.json index ad5397629b..31b3da2fbd 100644 --- a/erpnext/education/doctype/student_leave_application/student_leave_application.json +++ b/erpnext/education/doctype/student_leave_application/student_leave_application.json @@ -11,6 +11,7 @@ "column_break_3", "from_date", "to_date", + "total_leave_days", "section_break_5", "attendance_based_on", "student_group", @@ -110,11 +111,17 @@ { "fieldname": "column_break_11", "fieldtype": "Column Break" + }, + { + "fieldname": "total_leave_days", + "fieldtype": "Float", + "label": "Total Leave Days", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-07-08 13:22:38.329002", + "modified": "2020-09-21 18:10:24.440669", "modified_by": "Administrator", "module": "Education", "name": "Student Leave Application", diff --git a/erpnext/education/doctype/student_leave_application/student_leave_application.py b/erpnext/education/doctype/student_leave_application/student_leave_application.py index c8841c999a..ef670124c3 100644 --- a/erpnext/education/doctype/student_leave_application/student_leave_application.py +++ b/erpnext/education/doctype/student_leave_application/student_leave_application.py @@ -6,11 +6,14 @@ from __future__ import unicode_literals import frappe from frappe import _ from datetime import timedelta -from frappe.utils import get_link_to_form, getdate +from frappe.utils import get_link_to_form, getdate, date_diff, flt +from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday +from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list from frappe.model.document import Document class StudentLeaveApplication(Document): def validate(self): + self.validate_holiday_list() self.validate_duplicate() self.validate_from_to_dates('from_date', 'to_date') @@ -39,10 +42,19 @@ class StudentLeaveApplication(Document): frappe.throw(_('Leave application {0} already exists against the student {1}') .format(link, frappe.bold(self.student)), title=_('Duplicate Entry')) + def validate_holiday_list(self): + holiday_list = get_holiday_list() + self.total_leave_days = get_number_of_leave_days(self.from_date, self.to_date, holiday_list) + def update_attendance(self): + holiday_list = get_holiday_list() + for dt in daterange(getdate(self.from_date), getdate(self.to_date)): date = dt.strftime('%Y-%m-%d') + if is_holiday(holiday_list, date): + continue + attendance = frappe.db.exists('Student Attendance', { 'student': self.student, 'date': date, @@ -89,3 +101,19 @@ class StudentLeaveApplication(Document): def daterange(start_date, end_date): for n in range(int ((end_date - start_date).days)+1): yield start_date + timedelta(n) + +def get_number_of_leave_days(from_date, to_date, holiday_list): + number_of_days = date_diff(to_date, from_date) + 1 + + holidays = frappe.db.sql(""" + SELECT + COUNT(DISTINCT holiday_date) + FROM `tabHoliday` h1,`tabHoliday List` h2 + WHERE + h1.parent = h2.name and + h1.holiday_date between %s and %s and + h2.name = %s""", (from_date, to_date, holiday_list))[0][0] + + number_of_days = flt(number_of_days) - flt(holidays) + + return number_of_days diff --git a/erpnext/education/doctype/student_leave_application/test_student_leave_application.py b/erpnext/education/doctype/student_leave_application/test_student_leave_application.py index e9b568ad70..fcdd42825f 100644 --- a/erpnext/education/doctype/student_leave_application/test_student_leave_application.py +++ b/erpnext/education/doctype/student_leave_application/test_student_leave_application.py @@ -5,13 +5,15 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import getdate, add_days +from frappe.utils import getdate, add_days, add_months +from erpnext import get_default_company from erpnext.education.doctype.student_group.test_student_group import get_random_group from erpnext.education.doctype.student.test_student import create_student class TestStudentLeaveApplication(unittest.TestCase): def setUp(self): frappe.db.sql("""delete from `tabStudent Leave Application`""") + create_holiday_list() def test_attendance_record_creation(self): leave_application = create_leave_application() @@ -35,20 +37,45 @@ class TestStudentLeaveApplication(unittest.TestCase): attendance_status = frappe.db.get_value('Student Attendance', {'leave_application': leave_application.name}, 'docstatus') self.assertTrue(attendance_status, 2) + def test_holiday(self): + today = getdate() + leave_application = create_leave_application(from_date=today, to_date= add_days(today, 1), submit=0) -def create_leave_application(from_date=None, to_date=None, mark_as_present=0): + # holiday list validation + company = get_default_company() or frappe.get_all('Company')[0].name + frappe.db.set_value('Company', company, 'default_holiday_list', '') + self.assertRaises(frappe.ValidationError, leave_application.save) + + frappe.db.set_value('Company', company, 'default_holiday_list', 'Test Holiday List for Student') + leave_application.save() + + leave_application.reload() + self.assertEqual(leave_application.total_leave_days, 1) + + # check no attendance record created for a holiday + leave_application.submit() + self.assertIsNone(frappe.db.exists('Student Attendance', {'leave_application': leave_application.name, 'date': add_days(today, 1)})) + + def tearDown(self): + company = get_default_company() or frappe.get_all('Company')[0].name + frappe.db.set_value('Company', company, 'default_holiday_list', '_Test Holiday List') + + +def create_leave_application(from_date=None, to_date=None, mark_as_present=0, submit=1): student = get_student() - leave_application = frappe.get_doc({ - 'doctype': 'Student Leave Application', - 'student': student.name, - 'attendance_based_on': 'Student Group', - 'student_group': get_random_group().name, - 'from_date': from_date if from_date else getdate(), - 'to_date': from_date if from_date else getdate(), - 'mark_as_present': mark_as_present - }).insert() - leave_application.submit() + leave_application = frappe.new_doc('Student Leave Application') + leave_application.student = student.name + leave_application.attendance_based_on = 'Student Group' + leave_application.student_group = get_random_group().name + leave_application.from_date = from_date if from_date else getdate() + leave_application.to_date = from_date if from_date else getdate() + leave_application.mark_as_present = mark_as_present + + if submit: + leave_application.insert() + leave_application.submit() + return leave_application def create_student_attendance(date=None, status=None): @@ -67,4 +94,22 @@ def get_student(): email='test_student@gmail.com', first_name='Test', last_name='Student' - )) \ No newline at end of file + )) + +def create_holiday_list(): + holiday_list = 'Test Holiday List for Student' + today = getdate() + if not frappe.db.exists('Holiday List', holiday_list): + frappe.get_doc(dict( + doctype = 'Holiday List', + holiday_list_name = holiday_list, + from_date = add_months(today, -6), + to_date = add_months(today, 6), + holidays = [ + dict(holiday_date=add_days(today, 1), description = 'Test') + ] + )).insert() + + company = get_default_company() or frappe.get_all('Company')[0].name + frappe.db.set_value('Company', company, 'default_holiday_list', holiday_list) + return holiday_list \ No newline at end of file diff --git a/erpnext/education/report/absent_student_report/absent_student_report.py b/erpnext/education/report/absent_student_report/absent_student_report.py index 4e57cc6c22..c3487ccaff 100644 --- a/erpnext/education/report/absent_student_report/absent_student_report.py +++ b/erpnext/education/report/absent_student_report/absent_student_report.py @@ -3,8 +3,10 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cstr, cint, getdate +from frappe.utils import formatdate from frappe import msgprint, _ +from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list +from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday def execute(filters=None): if not filters: filters = {} @@ -15,6 +17,11 @@ def execute(filters=None): columns = get_columns(filters) date = filters.get("date") + holiday_list = get_holiday_list() + if is_holiday(holiday_list, filters.get("date")): + msgprint(_("No attendance has been marked for {0} as it is a Holiday").format(frappe.bold(formatdate(filters.get("date"))))) + + absent_students = get_absent_students(date) leave_applicants = get_leave_applications(date) if absent_students: diff --git a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py index c65d233ccc..7793dcf395 100644 --- a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py +++ b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py @@ -3,8 +3,10 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cstr, cint, getdate +from frappe.utils import formatdate from frappe import msgprint, _ +from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list +from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday def execute(filters=None): if not filters: filters = {} @@ -12,6 +14,10 @@ def execute(filters=None): if not filters.get("date"): msgprint(_("Please select date"), raise_exception=1) + holiday_list = get_holiday_list() + if is_holiday(holiday_list, filters.get("date")): + msgprint(_("No attendance has been marked for {0} as it is a Holiday").format(frappe.bold(formatdate(filters.get("date"))))) + columns = get_columns(filters) active_student_group = get_active_student_group() diff --git a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py index d820bfbb21..04dc8c0e56 100644 --- a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py +++ b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py @@ -7,6 +7,8 @@ from frappe.utils import cstr, cint, getdate, get_first_day, get_last_day, date_ from frappe import msgprint, _ from calendar import monthrange from erpnext.education.api import get_student_group_students +from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list +from erpnext.support.doctype.issue.issue import get_holidays def execute(filters=None): if not filters: filters = {} @@ -19,26 +21,32 @@ def execute(filters=None): students_list = get_students_list(students) att_map = get_attendance_list(from_date, to_date, filters.get("student_group"), students_list) data = [] + for stud in students: row = [stud.student, stud.student_name] student_status = frappe.db.get_value("Student", stud.student, "enabled") date = from_date total_p = total_a = 0.0 + for day in range(total_days_in_month): status="None" + if att_map.get(stud.student): status = att_map.get(stud.student).get(date, "None") elif not student_status: status = "Inactive" else: status = "None" - status_map = {"Present": "P", "Absent": "A", "None": "", "Inactive":"-"} + + status_map = {"Present": "P", "Absent": "A", "None": "", "Inactive":"-", "Holiday":"H"} row.append(status_map[status]) + if status == "Present": total_p += 1 elif status == "Absent": total_a += 1 date = add_days(date, 1) + row += [total_p, total_a] data.append(row) return columns, data @@ -63,14 +71,19 @@ def get_attendance_list(from_date, to_date, student_group, students_list): and date between %s and %s order by student, date''', (student_group, from_date, to_date), as_dict=1) + att_map = {} students_with_leave_application = get_students_with_leave_application(from_date, to_date, students_list) for d in attendance_list: att_map.setdefault(d.student, frappe._dict()).setdefault(d.date, "") + if students_with_leave_application.get(d.date) and d.student in students_with_leave_application.get(d.date): att_map[d.student][d.date] = "Present" else: att_map[d.student][d.date] = d.status + + att_map = mark_holidays(att_map, from_date, to_date, students_list) + return att_map def get_students_with_leave_application(from_date, to_date, students_list): @@ -108,3 +121,14 @@ def get_attendance_years(): if not year_list: year_list = [getdate().year] return "\n".join(str(year) for year in year_list) + +def mark_holidays(att_map, from_date, to_date, students_list): + holiday_list = get_holiday_list() + holidays = get_holidays(holiday_list) + + for dt in daterange(getdate(from_date), getdate(to_date)): + if dt in holidays: + for student in students_list: + att_map.setdefault(student, frappe._dict()).setdefault(dt, "Holiday") + + return att_map From d038a80c43ef8e0c345f5d9fc77ce38c8201cdd4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 9 Nov 2020 19:10:43 +0530 Subject: [PATCH 112/154] chore: Make eslint stricter (#23869) Error for - Invalid tab style - No space between unary operations - Space between blocks Warn for - Comma Spacing - Block Spacing - Object Key Spacing - Keyword Spacing --- .eslintrc | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index 757aa3caaf..d6f0f49363 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": 9, "sourceType": "module" }, "extends": "eslint:recommended", @@ -15,6 +15,14 @@ "tab", { "SwitchCase": 1 } ], + "brace-style": [ + "error", + "1tbs" + ], + "space-unary-ops": [ + "error", + { "words": true } + ], "linebreak-style": [ "error", "unix" @@ -44,12 +52,10 @@ "no-control-regex": [ "off" ], - "spaced-comment": [ - "warn" - ], - "no-trailing-spaces": [ - "warn" - ] + "space-before-blocks": "warn", + "keyword-spacing": "warn", + "comma-spacing": "warn", + "key-spacing": "warn" }, "root": true, "globals": { From bc64f5dfbbf497ba20de47bcfcc6cb84d38bfb18 Mon Sep 17 00:00:00 2001 From: UrvashiKishnani <41088003+UrvashiKishnani@users.noreply.github.com> Date: Mon, 9 Nov 2020 18:44:06 +0400 Subject: [PATCH 113/154] fix: spelling error in filter label (#23867) --- erpnext/public/js/salary_slip_deductions_report_filters.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/salary_slip_deductions_report_filters.js b/erpnext/public/js/salary_slip_deductions_report_filters.js index 2b30e65075..1ca36600c3 100644 --- a/erpnext/public/js/salary_slip_deductions_report_filters.js +++ b/erpnext/public/js/salary_slip_deductions_report_filters.js @@ -45,7 +45,7 @@ erpnext.salary_slip_deductions_report_filters = { }, { fieldname: "branch", - label: __("Barnch"), + label: __("Branch"), fieldtype: "Link", options: "Branch", } @@ -63,4 +63,4 @@ erpnext.salary_slip_deductions_report_filters = { } }); } -} \ No newline at end of file +} From 8cfb210dc4ced9153a352896b726debf7b476159 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 9 Nov 2020 20:14:53 +0530 Subject: [PATCH 114/154] fix: Ignore doctypes on company transaction deleted (#23864) --- erpnext/setup/doctype/company/delete_company_transactions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index c94831ef93..566f20cfa1 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -27,7 +27,8 @@ def delete_company_transactions(company_name): if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", "Party Account", "Employee", "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template", "POS Profile", "BOM", - "Company", "Bank Account"): + "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", + "Item Default"): delete_for_doctype(doctype, company_name) # reset company values From f38a1c305eb184ea5b00a727760fdac2dca83e8d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 9 Nov 2020 20:16:28 +0530 Subject: [PATCH 115/154] refactor: show form buttons only if permissions exist (#23851) * refactor: show form buttons only if permissions exist * style: add semicolon * fix: typo in conditions --- erpnext/setup/doctype/company/company.js | 48 +++++++++++++++--------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index f882db60c5..cbf67b4cd6 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -90,29 +90,41 @@ frappe.ui.form.on("Company", { frm.toggle_enable("default_currency", (frm.doc.__onload && !frm.doc.__onload.transactions_exist)); - frm.add_custom_button(__('Create Tax Template'), function() { - frm.trigger("make_default_tax_template"); - }); + if (frm.has_perm('write')) { + frm.add_custom_button(__('Create Tax Template'), function() { + frm.trigger("make_default_tax_template"); + }); + } - frm.add_custom_button(__('Cost Centers'), function() { - frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}) - }, __("View")); + if (frappe.perm.has_perm("Cost Center", 0, 'read')) { + frm.add_custom_button(__('Cost Centers'), function() { + frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); + }, __("View")); + } - frm.add_custom_button(__('Chart of Accounts'), function() { - frappe.set_route('Tree', 'Account', {'company': frm.doc.name}) - }, __("View")); + if (frappe.perm.has_perm("Account", 0, 'read')) { + frm.add_custom_button(__('Chart of Accounts'), function() { + frappe.set_route('Tree', 'Account', {'company': frm.doc.name}); + }, __("View")); + } - frm.add_custom_button(__('Sales Tax Template'), function() { - frappe.set_route('List', 'Sales Taxes and Charges Template', {'company': frm.doc.name}); - }, __("View")); + if (frappe.perm.has_perm("Sales Taxes and Charges Template", 0, 'read')) { + frm.add_custom_button(__('Sales Tax Template'), function() { + frappe.set_route('List', 'Sales Taxes and Charges Template', {'company': frm.doc.name}); + }, __("View")); + } - frm.add_custom_button(__('Purchase Tax Template'), function() { - frappe.set_route('List', 'Purchase Taxes and Charges Template', {'company': frm.doc.name}); - }, __("View")); + if (frappe.perm.has_perm("Purchase Taxes and Charges Template", 0, 'read')) { + frm.add_custom_button(__('Purchase Tax Template'), function() { + frappe.set_route('List', 'Purchase Taxes and Charges Template', {'company': frm.doc.name}); + }, __("View")); + } - frm.add_custom_button(__('Default Tax Template'), function() { - frm.trigger("make_default_tax_template"); - }, __('Create')); + if (frm.has_perm('write')) { + frm.add_custom_button(__('Default Tax Template'), function() { + frm.trigger("make_default_tax_template"); + }, __('Create')); + } } erpnext.company.set_chart_of_accounts_options(frm.doc); From 4f880e74547348b9e918779b8c7310cefcae8bec Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 9 Nov 2020 15:47:56 +0100 Subject: [PATCH 116/154] refactor: zip_and_download (#23844) --- .../regional/germany/utils/datev/datev_csv.py | 16 +++++++++------- erpnext/regional/report/datev/datev.py | 8 +++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index fb7ca71e79..cf07a1c824 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -155,20 +155,22 @@ def get_header(filters, csv_class): return header -def download_csv_files_as_zip(csv_data_list): +def zip_and_download(zip_filename, csv_files): """ Put CSV files in a zip archive and send that to the client. Params: - csv_data_list -- list of dicts [{'file_name': 'EXTF_Buchunsstapel.zip', 'csv_data': get_datev_csv()}] + zip_filename Name of the zip file + csv_files list of dicts [{'file_name': 'my_file.csv', 'csv_data': 'comma,separated,values'}] """ zip_buffer = BytesIO() - datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) - for csv_file in csv_data_list: - datev_zip.writestr(csv_file.get('file_name'), csv_file.get('csv_data')) - datev_zip.close() + zip_file = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) + for csv_file in csv_files: + zip_file.writestr(csv_file.get('file_name'), csv_file.get('csv_data')) + + zip_file.close() frappe.response['filecontent'] = zip_buffer.getvalue() - frappe.response['filename'] = 'DATEV.zip' + frappe.response['filename'] = zip_filename frappe.response['type'] = 'binary' diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 738806321c..dbae230f1e 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -11,10 +11,11 @@ from __future__ import unicode_literals import json import frappe -from frappe import _ from six import string_types + +from frappe import _ from erpnext.accounts.utils import get_fiscal_year -from erpnext.regional.germany.utils.datev.datev_csv import download_csv_files_as_zip, get_datev_csv +from erpnext.regional.germany.utils.datev.datev_csv import zip_and_download, get_datev_csv from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames COLUMNS = [ @@ -344,7 +345,8 @@ def download_datev_csv(filters): customers = get_customers(filters) suppliers = get_suppliers(filters) - download_csv_files_as_zip([ + zip_name = '{} DATEV.zip'.format(frappe.utils.datetime.date.today()) + zip_and_download(zip_name, [ { 'file_name': 'EXTF_Buchungsstapel.csv', 'csv_data': get_datev_csv(transactions, filters, csv_class=Transactions) From 91fdb7aa694f890896586f95821b3f318fc6b612 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 9 Nov 2020 20:20:05 +0530 Subject: [PATCH 117/154] feat: added column cost_center to receivable reports (#23835) --- .../report/accounts_receivable/accounts_receivable.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 044fc1d3ab..51fc7ec49a 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -160,6 +160,8 @@ class ReceivablePayableReport(object): else: # advance / unlinked payment or other adjustment row.paid -= gle_balance + if gle.cost_center: + row.cost_center = str(gle.cost_center) def update_sub_total_row(self, row, party): total_row = self.total_row_map.get(party) @@ -210,7 +212,6 @@ class ReceivablePayableReport(object): for key, row in self.voucher_balance.items(): row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision) row.invoice_grand_total = row.invoiced - if abs(row.outstanding) > 1.0/10 ** self.currency_precision: # non-zero oustanding, we must consider this row @@ -577,7 +578,7 @@ class ReceivablePayableReport(object): self.gl_entries = frappe.db.sql(""" select - name, posting_date, account, party_type, party, voucher_type, voucher_no, + name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, against_voucher_type, against_voucher, account_currency, remarks, {0} from `tabGL Entry` @@ -741,6 +742,7 @@ class ReceivablePayableReport(object): self.add_column(_("Customer Contact"), fieldname='customer_primary_contact', fieldtype='Link', options='Contact') + self.add_column(label=_('Cost Center'), fieldname='cost_center', fieldtype='Data') self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data') self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link', options='voucher_type', width=180) From 57ce0462d9a17b74c4aeb6e2d880e46e2ada6b5b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 9 Nov 2020 19:04:46 +0100 Subject: [PATCH 118/154] fix: remove check for exempt_from_sales_tax --- erpnext/regional/germany/accounts_controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py index 193c8e14a3..5b2b31f204 100644 --- a/erpnext/regional/germany/accounts_controller.py +++ b/erpnext/regional/germany/accounts_controller.py @@ -15,8 +15,7 @@ REQUIRED_FIELDS = { }, { "field_name": "taxes", - "regulation": "§ 14 Abs. 4 Nr. 8 UStG", - "condition": "not exempt_from_sales_tax" + "regulation": "§ 14 Abs. 4 Nr. 8 UStG" }, { "field_name": "customer_address", From 1924019531a6ed01d1140b83bd9fc094c114f047 Mon Sep 17 00:00:00 2001 From: Afshan Date: Tue, 10 Nov 2020 13:19:21 +0530 Subject: [PATCH 119/154] fix: refactor --- erpnext/controllers/selling_controller.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e5405b2e43..2a5617c168 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -386,14 +386,12 @@ class SellingController(StockController): po_nos = [] self.get_po_nos('Sales Order', 'against_sales_order', po_nos) self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos) - self.po_no = ', '.join(list(set(po_nos))) + self.po_no = ', '.join(list(set((x.strip() for x in ','.join(po_nos).split(','))))) def get_po_nos(self, ref_doctype, ref_fieldname, po_nos): doc_list = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)])) if doc_list: - po_no_list = frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) - if po_no_list and po_no_list[0].get('po_no'): - po_nos += [d.po_no for d in po_no_list if d.po_no] + po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')] def set_gross_profit(self): if self.doctype in ["Sales Order", "Quotation"]: From 1e564bc02d4ce292a913865bb7ab434e2042476d Mon Sep 17 00:00:00 2001 From: Afshan Date: Tue, 10 Nov 2020 13:23:59 +0530 Subject: [PATCH 120/154] fix: refactor --- erpnext/controllers/selling_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 2a5617c168..7504746e07 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -380,13 +380,13 @@ class SellingController(StockController): po_nos = [] self.get_po_nos('Sales Order', 'sales_order', po_nos) self.get_po_nos('Delivery Note', 'delivery_note', po_nos) - self.po_no = ', '.join(list(set(po_nos))) + self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) def set_pos_for_delivery_note(self): po_nos = [] self.get_po_nos('Sales Order', 'against_sales_order', po_nos) self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos) - self.po_no = ', '.join(list(set((x.strip() for x in ','.join(po_nos).split(','))))) + self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) def get_po_nos(self, ref_doctype, ref_fieldname, po_nos): doc_list = list(set([d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)])) From 101bad3ea1d53371a5e1f24559feddb6c70988b5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 10 Nov 2020 17:53:50 +0530 Subject: [PATCH 121/154] fix: Remarks for penalty GL Entry --- .../doctype/loan_repayment/loan_repayment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index a8887d7b24..415ba993c7 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -216,7 +216,7 @@ class LoanRepayment(AccountsController): "debit_in_account_currency": self.penalty_amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Repayment against loan:") + self.against_loan, + "remarks": _("Penalty against loan:") + self.against_loan, "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, @@ -232,7 +232,7 @@ class LoanRepayment(AccountsController): "credit_in_account_currency": self.penalty_amount, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Repayment against loan:") + self.against_loan, + "remarks": _("Penalty against loan:") + self.against_loan, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) @@ -247,7 +247,7 @@ class LoanRepayment(AccountsController): "debit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Against Loan:") + self.against_loan, + "remarks": _("Repayment against Loan: ") + self.against_loan, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) @@ -263,7 +263,7 @@ class LoanRepayment(AccountsController): "credit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": _("Against Loan:") + self.against_loan, + "remarks": _("Repayment against Loan: ") + self.against_loan, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date) }) From d08c91673af3c21f77b402e6d1ee53bf9548344a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 10 Nov 2020 18:04:30 +0530 Subject: [PATCH 122/154] fix: removed unnecessary filter options --- .../doctype/production_plan/production_plan.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 63df5f379d..7daf7069f3 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -308,14 +308,14 @@ "fieldname": "sales_order_status", "fieldtype": "Select", "label": "Sales Order Status", - "options": "\nDraft\nOn Hold\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed" + "options": "\nTo Deliver and Bill\nTo Bill\nTo Deliver" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-03 15:19:18.270529", + "modified": "2020-11-10 18:01:54.991970", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", From b648b9cf93dcbc47a5f33fa49acbc601e14ff614 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 10 Nov 2020 18:50:02 +0530 Subject: [PATCH 123/154] fix: code review changes --- erpnext/public/js/controllers/accounts.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 1bceec0547..9c746ab50b 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -154,11 +154,8 @@ cur_frm.cscript.account_head = function(doc, cdt, cdn) { callback: function(r) { if(d.charge_type!=="Actual"){ frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); - frappe.model.set_value(cdt, cdn, "description", r.message.account_name); - } - else if(d.charge_type == 'Actual'){ - frappe.model.set_value(cdt, cdn, "description", r.message.account_name); } + frappe.model.set_value(cdt, cdn, "description", r.message.account_name); } }) } From 7e4d115e36d9026f8fe5ad60c6d80e56c19c826b Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 11 Nov 2020 09:52:27 +0530 Subject: [PATCH 124/154] fix: sider issues fixed --- erpnext/public/js/controllers/accounts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 9c746ab50b..29f35958e1 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -146,13 +146,13 @@ cur_frm.cscript.account_head = function(doc, cdt, cdn) { if(!d.charge_type && d.account_head){ frappe.msgprint(__("Please select Charge Type first")); frappe.model.set_value(cdt, cdn, "account_head", ""); - } else if(d.account_head) { + } else if (d.account_head) { frappe.call({ type:"GET", method: "erpnext.controllers.accounts_controller.get_tax_rate", args: {"account_head":d.account_head}, callback: function(r) { - if(d.charge_type!=="Actual"){ + if (d.charge_type!=="Actual") { frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0); } frappe.model.set_value(cdt, cdn, "description", r.message.account_name); From 780b6b466b138bb758d7aef00ea58c2a476f8e55 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 11 Nov 2020 11:44:12 +0530 Subject: [PATCH 125/154] fix(hooks): include js/css with full path --- erpnext/hooks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index dbb6c0d92e..90ae6442e4 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -15,10 +15,10 @@ app_logo_url = '/assets/erpnext/images/erp-icon.svg' develop_version = '13.x.x-develop' -app_include_js = "assets/js/erpnext.min.js" -app_include_css = "assets/css/erpnext.css" -web_include_js = "assets/js/erpnext-web.min.js" -web_include_css = "assets/css/erpnext-web.css" +app_include_js = "/assets/js/erpnext.min.js" +app_include_css = "/assets/css/erpnext.css" +web_include_js = "/assets/js/erpnext-web.min.js" +web_include_css = "/assets/css/erpnext-web.css" doctype_js = { "Address": "public/js/address.js", From 6c95bc5e11dfe6e31ce2624520b30676b17428b2 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 11 Nov 2020 15:13:38 +0530 Subject: [PATCH 126/154] fix: payroll attendance error --- .../doctype/payroll_entry/payroll_entry.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 30ea432678..49c204ab44 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -344,9 +344,13 @@ class PayrollEntry(Document): employees_to_mark_attendance = [] days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 for employee_detail in self.employees: - days_holiday = self.get_count_holidays_of_employee(employee_detail.employee) - days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee) - days_in_payroll = date_diff(self.end_date, self.start_date) + 1 + employee_joining_date = frappe.db.get_value("Employee", employee_detail.employee, 'date_of_joining') + start_date = self.start_date + if employee_joining_date > getdate(self.start_date): + start_date = employee_joining_date + days_holiday = self.get_count_holidays_of_employee(employee_detail.employee, start_date) + days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee, start_date) + days_in_payroll = date_diff(self.end_date, start_date) + 1 if days_in_payroll > days_holiday + days_attendance_marked: employees_to_mark_attendance.append({ "employee": employee_detail.employee, @@ -354,22 +358,22 @@ class PayrollEntry(Document): }) return employees_to_mark_attendance - def get_count_holidays_of_employee(self, employee): + def get_count_holidays_of_employee(self, employee, start_date): holiday_list = get_holiday_list_for_employee(employee) holidays = 0 if holiday_list: days = frappe.db.sql("""select count(*) from tabHoliday where parent=%s and holiday_date between %s and %s""", (holiday_list, - self.start_date, self.end_date)) + start_date, self.end_date)) if days and days[0][0]: holidays = days[0][0] return holidays - def get_count_employee_attendance(self, employee): + def get_count_employee_attendance(self, employee, start_date): marked_days = 0 attendances = frappe.db.sql("""select count(*) from tabAttendance where employee=%s and docstatus=1 and attendance_date between %s and %s""", - (employee, self.start_date, self.end_date)) + (employee, start_date, self.end_date)) if attendances and attendances[0][0]: marked_days = attendances[0][0] return marked_days From a91b68c8689073296815033d12a94063977de20b Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 11 Nov 2020 16:34:43 +0530 Subject: [PATCH 127/154] feat: add communication channel to communication medium (#23793) Co-authored-by: Saqib Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../communication_medium/communication_medium.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/communication/doctype/communication_medium/communication_medium.json b/erpnext/communication/doctype/communication_medium/communication_medium.json index f009b38877..1e1fe3bf49 100644 --- a/erpnext/communication/doctype/communication_medium/communication_medium.json +++ b/erpnext/communication/doctype/communication_medium/communication_medium.json @@ -1,12 +1,14 @@ { + "actions": [], "autoname": "Prompt", "creation": "2019-06-05 11:48:30.572795", "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "communication_channel", "communication_medium_type", - "catch_all", "column_break_3", + "catch_all", "provider", "disabled", "timeslots_section", @@ -54,9 +56,16 @@ "fieldtype": "Table", "label": "Timeslots", "options": "Communication Medium Timeslot" + }, + { + "fieldname": "communication_channel", + "fieldtype": "Select", + "label": "Communication Channel", + "options": "\nExotel" } ], - "modified": "2019-06-05 11:49:30.769006", + "links": [], + "modified": "2020-10-27 16:22:08.068542", "modified_by": "Administrator", "module": "Communication", "name": "Communication Medium", From 0ea0a7495c5de280cfb97cb8de31012d0aef7081 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Thu, 12 Nov 2020 11:10:59 +0530 Subject: [PATCH 128/154] fix: making company address read-only in delivery note (#23890) --- erpnext/stock/doctype/delivery_note/delivery_note.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index ea385c8b2a..3c5129b1ab 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -413,7 +413,8 @@ { "fieldname": "company_address_display", "fieldtype": "Small Text", - "label": "Company Address" + "label": "Company Address", + "read_only": 1 }, { "collapsible": 1, @@ -1255,7 +1256,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2020-08-03 23:18:47.739997", + "modified": "2020-11-11 14:57:16.388139", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 2f5d896f8ce86a7f4127ae7fc7a3718a2526cc30 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 12 Nov 2020 16:57:42 +0530 Subject: [PATCH 129/154] fix: added wrong absent days calculation in salary slip --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 4ccf56435d..cecb8cde7c 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -240,7 +240,6 @@ class SalarySlip(TransactionBase): self.absent_days += unmarked_days #will be treated as absent self.payment_days -= unmarked_days if include_holidays_in_total_working_days: - self.absent_days -= len(holidays) for holiday in holidays: if not frappe.db.exists("Attendance", {"employee": self.employee, "attendance_date": holiday, "docstatus": 1 }): self.payment_days += 1 From 37dc369af9b3015bb93012bc0b94ef7c32e262f2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 13 Nov 2020 16:53:35 +0530 Subject: [PATCH 130/154] feat: Sync old shopify orders (#23841) * feat: Sync old shopify orders * fix: Old orders syncing by date * fix: Remove unnecessary code * fix: Remove unintentional changes Co-authored-by: Saurabh --- .../connectors/shopify_connection.py | 97 ++++++++++++++++--- .../shopify_settings/shopify_settings.json | 78 ++++++++++++++- .../shopify_settings/shopify_settings.py | 12 ++- .../shopify_settings/test_shopify_settings.py | 4 +- erpnext/hooks.py | 1 + erpnext/patches.txt | 3 +- .../v13_0/update_custom_fields_for_shopify.py | 10 ++ 7 files changed, 185 insertions(+), 20 deletions(-) create mode 100644 erpnext/patches/v13_0/update_custom_fields_for_shopify.py diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index d59f909298..8aa7453bd6 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -2,12 +2,13 @@ from __future__ import unicode_literals import frappe from frappe import _ import json -from frappe.utils import cstr, cint, nowdate, flt +from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime from erpnext.erpnext_integrations.utils import validate_webhooks_request from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data +from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header @frappe.whitelist(allow_guest=True) @validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret') @@ -18,7 +19,7 @@ def store_request_data(order=None, event=None): dump_request_data(order, event) -def sync_sales_order(order, request_id=None): +def sync_sales_order(order, request_id=None, old_order_sync=False): frappe.set_user('Administrator') shopify_settings = frappe.get_doc("Shopify Settings") frappe.flags.request_id = request_id @@ -27,7 +28,7 @@ def sync_sales_order(order, request_id=None): try: validate_customer(order, shopify_settings) validate_item(order, shopify_settings) - create_order(order, shopify_settings) + create_order(order, shopify_settings, old_order_sync=old_order_sync) except Exception as e: make_shopify_log(status="Error", exception=e) @@ -77,13 +78,13 @@ def validate_item(order, shopify_settings): if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"): sync_item_from_shopify(shopify_settings, item) -def create_order(order, shopify_settings, company=None): +def create_order(order, shopify_settings, old_order_sync=False, company=None): so = create_sales_order(order, shopify_settings, company) if so: if order.get("financial_status") == "paid": - create_sales_invoice(order, shopify_settings, so) + create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync) - if order.get("fulfillments"): + if order.get("fulfillments") and not old_order_sync: create_delivery_note(order, shopify_settings, so) def create_sales_order(shopify_order, shopify_settings, company=None): @@ -92,7 +93,7 @@ def create_sales_order(shopify_order, shopify_settings, company=None): so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name") if not so: - items = get_order_items(shopify_order.get("line_items"), shopify_settings) + items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at'))) if not items: message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master' @@ -106,8 +107,10 @@ def create_sales_order(shopify_order, shopify_settings, company=None): "doctype": "Sales Order", "naming_series": shopify_settings.sales_order_series or "SO-Shopify-", "shopify_order_id": shopify_order.get("id"), + "shopify_order_number": shopify_order.get("name"), "customer": customer or shopify_settings.default_customer, - "delivery_date": nowdate(), + "transaction_date": getdate(shopify_order.get("created_at")) or nowdate(), + "delivery_date": getdate(shopify_order.get("created_at")) or nowdate(), "company": shopify_settings.company, "selling_price_list": shopify_settings.price_list, "ignore_pricing_rule": 1, @@ -132,12 +135,20 @@ def create_sales_order(shopify_order, shopify_settings, company=None): frappe.db.commit() return so -def create_sales_invoice(shopify_order, shopify_settings, so): +def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False): if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\ and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice): + if old_order_sync: + posting_date = getdate(shopify_order.get('created_at')) + else: + posting_date = nowdate() + si = make_sales_invoice(so.name, ignore_permissions=True) si.shopify_order_id = shopify_order.get("id") + si.shopify_order_number = shopify_order.get("name") + si.set_posting_time = 1 + si.posting_date = posting_date si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-" si.flags.ignore_mandatory = True set_cost_center(si.items, shopify_settings.cost_center) @@ -169,6 +180,9 @@ def create_delivery_note(shopify_order, shopify_settings, so): dn = make_delivery_note(so.name) dn.shopify_order_id = fulfillment.get("order_id") + dn.shopify_order_number = shopify_order.get("name") + dn.set_posting_time = 1 + dn.posting_date = getdate(fulfillment.get("created_at")) dn.shopify_fulfillment_id = fulfillment.get("id") dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-" dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings) @@ -187,7 +201,7 @@ def get_discounted_amount(order): discounted_amount += flt(discount.get("amount")) return discounted_amount -def get_order_items(order_items, shopify_settings): +def get_order_items(order_items, shopify_settings, delivery_date): items = [] all_product_exists = True product_not_exists = [] @@ -205,7 +219,7 @@ def get_order_items(order_items, shopify_settings): "item_code": item_code, "item_name": shopify_item.get("name"), "rate": shopify_item.get("price"), - "delivery_date": nowdate(), + "delivery_date": delivery_date, "qty": shopify_item.get("quantity"), "stock_uom": shopify_item.get("uom") or _("Nos"), "warehouse": shopify_settings.warehouse @@ -265,3 +279,64 @@ def get_tax_account_head(tax): frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title"))) return tax_account + +@frappe.whitelist(allow_guest=True) +def sync_old_orders(): + frappe.set_user('Administrator') + shopify_settings = frappe.get_doc('Shopify Settings') + + if not shopify_settings.sync_missing_orders: + return + + url = get_url(shopify_settings) + session = get_request_session() + + try: + res = session.get(url, headers=get_header(shopify_settings)) + res.raise_for_status() + orders = res.json()["orders"] + + for order in orders: + if is_sync_complete(shopify_settings, order): + stop_sync(shopify_settings) + return + + sync_sales_order(order=order, old_order_sync=True) + last_order_id = order.get('id') + + if last_order_id: + shopify_settings.load_from_db() + shopify_settings.last_order_id = last_order_id + shopify_settings.save() + frappe.db.commit() + + except Exception as e: + raise e + +def stop_sync(shopify_settings): + shopify_settings.sync_missing_orders = 0 + shopify_settings.last_order_id = '' + shopify_settings.save() + frappe.db.commit() + +def get_url(shopify_settings): + last_order_id = shopify_settings.last_order_id + + if not last_order_id: + if shopify_settings.sync_based_on == 'Date': + url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format( + get_datetime(shopify_settings.from_date)), shopify_settings) + else: + url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format( + shopify_settings.from_order_id), shopify_settings) + else: + url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) + + return url + +def is_sync_complete(shopify_settings, order): + if shopify_settings.sync_based_on == 'Date': + return getdate(shopify_settings.to_date) < getdate(order.get('created_at')) + else: + return cstr(order.get('id')) == cstr(shopify_settings.to_order_id) + diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json index 2e10751f96..20ec06373e 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json @@ -1,7 +1,9 @@ { + "actions": [], "creation": "2015-05-18 05:21:07.270859", "doctype": "DocType", "document_type": "System", + "engine": "InnoDB", "field_order": [ "status_html", "enable_shopify", @@ -40,7 +42,16 @@ "sales_invoice_series", "section_break_22", "html_16", - "taxes" + "taxes", + "syncing_details_section", + "sync_missing_orders", + "sync_based_on", + "column_break_41", + "from_date", + "to_date", + "from_order_id", + "to_order_id", + "last_order_id" ], "fields": [ { @@ -255,10 +266,71 @@ "fieldtype": "Table", "label": "Shopify Tax Account", "options": "Shopify Tax Account" + }, + { + "collapsible": 1, + "fieldname": "syncing_details_section", + "fieldtype": "Section Break", + "label": "Syncing Missing Orders" + }, + { + "depends_on": "eval:doc.sync_missing_orders", + "fieldname": "last_order_id", + "fieldtype": "Data", + "label": "Last Order Id", + "read_only": 1 + }, + { + "fieldname": "column_break_41", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "On checking this Order from the ", + "fieldname": "sync_missing_orders", + "fieldtype": "Check", + "label": "Sync Missing Old Shopify Orders" + }, + { + "depends_on": "eval:doc.sync_missing_orders", + "fieldname": "sync_based_on", + "fieldtype": "Select", + "label": "Sync Based On", + "mandatory_depends_on": "eval:doc.sync_missing_orders", + "options": "\nDate\nShopify Order Id" + }, + { + "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders", + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders" + }, + { + "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders", + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders" + }, + { + "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders", + "fieldname": "from_order_id", + "fieldtype": "Data", + "label": "From Order Id", + "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders" + }, + { + "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders", + "fieldname": "to_order_id", + "fieldtype": "Data", + "label": "To Order Id", + "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders" } ], "issingle": 1, - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2020-11-05 20:44:03.664891", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Shopify Settings", @@ -277,4 +349,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index 25ffd28109..cbdf90681d 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -87,7 +87,7 @@ def get_shopify_url(path, settings): def get_header(settings): header = {'Content-Type': 'application/json'} - return header; + return header @frappe.whitelist() def get_series(): @@ -121,17 +121,23 @@ def setup_custom_fields(): ], "Sales Order": [ dict(fieldname='shopify_order_id', label='Shopify Order Id', - fieldtype='Data', insert_after='title', read_only=1, print_hide=1) + fieldtype='Data', insert_after='title', read_only=1, print_hide=1), + dict(fieldname='shopify_order_number', label='Shopify Order Number', + fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1) ], "Delivery Note":[ dict(fieldname='shopify_order_id', label='Shopify Order Id', fieldtype='Data', insert_after='title', read_only=1, print_hide=1), + dict(fieldname='shopify_order_number', label='Shopify Order Number', + fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1), dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id', fieldtype='Data', insert_after='title', read_only=1, print_hide=1) ], "Sales Invoice": [ dict(fieldname='shopify_order_id', label='Shopify Order Id', - fieldtype='Data', insert_after='title', read_only=1, print_hide=1) + fieldtype='Data', insert_after='title', read_only=1, print_hide=1), + dict(fieldname='shopify_order_number', label='Shopify Order Number', + fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1) ] } 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 64ef3dc085..30fa23cfb4 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py @@ -58,7 +58,7 @@ class ShopifySettings(unittest.TestCase): }).save(ignore_permissions=True) self.shopify_settings = shopify_settings - + def test_order(self): ### Create Customer ### with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer: @@ -75,7 +75,7 @@ class ShopifySettings(unittest.TestCase): with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order: shopify_order = json.load(shopify_order) - create_order(shopify_order.get("order"), self.shopify_settings, "_Test Company") + create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company") sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))}) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 90ae6442e4..78ef66585f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -307,6 +307,7 @@ scheduler_events = { "erpnext.projects.doctype.project.project.collect_project_status", "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", ], "daily": [ "erpnext.stock.reorder_item.reorder_item", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 34dbdd0bd5..25be884117 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -733,4 +733,5 @@ erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail erpnext.patches.v13_0.update_reason_for_resignation_in_employee -execute:frappe.delete_doc("Report", "Quoted Item Comparison") \ No newline at end of file +erpnext.patches.v13_0.update_custom_fields_for_shopify +execute:frappe.delete_doc("Report", "Quoted Item Comparison") diff --git a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py new file mode 100644 index 0000000000..f1d2ea2d74 --- /dev/null +++ b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py @@ -0,0 +1,10 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields + +def execute(): + if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'): + setup_custom_fields() From 1a4cafb97d0d6ab63033d18292cedfffaa747130 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 13 Nov 2020 17:04:05 +0530 Subject: [PATCH 131/154] fix: change query to frappe.get_all --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 49c204ab44..a3d12c35c0 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -371,9 +371,12 @@ class PayrollEntry(Document): def get_count_employee_attendance(self, employee, start_date): marked_days = 0 - attendances = frappe.db.sql("""select count(*) from tabAttendance where - employee=%s and docstatus=1 and attendance_date between %s and %s""", - (employee, start_date, self.end_date)) + attendances = frappe.get_all("Attendance", + fields = ["count(*)"], + filters = { + "employee": employee, + "attendance_date": ('between', [start_date, self.end_date]) + }, as_list=1) if attendances and attendances[0][0]: marked_days = attendances[0][0] return marked_days From 4abc5657ab352d0d2fd91fbd86fe430ba26deff2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 13 Nov 2020 22:17:07 +0530 Subject: [PATCH 132/154] fix: not able to save bom --- erpnext/manufacturing/doctype/bom/bom.py | 31 +++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2ab1b98707..8888a96768 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -76,6 +76,7 @@ class BOM(WebsiteGenerator): self.set_routing_operations() self.validate_operations() self.calculate_cost() + self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, save=False) def get_context(self, context): @@ -84,8 +85,6 @@ class BOM(WebsiteGenerator): def on_update(self): frappe.cache().hdel('bom_children', self.name) self.check_recursion() - self.update_stock_qty() - self.update_exploded_items() def on_submit(self): self.manage_default_bom() @@ -237,7 +236,8 @@ class BOM(WebsiteGenerator): self.calculate_cost() if save: self.db_update() - self.update_exploded_items() + + self.update_exploded_items(save=save) # update parent BOMs if self.total_cost != existing_bom_cost and update_parent: @@ -318,8 +318,6 @@ class BOM(WebsiteGenerator): m.uom = m.stock_uom m.qty = m.stock_qty - m.db_update() - def validate_uom_is_interger(self): from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty", "BOM Item") @@ -372,15 +370,6 @@ class BOM(WebsiteGenerator): if raise_exception: frappe.throw(_("BOM recursion: {0} cannot be parent or child of {1}").format(self.name, self.name)) - def update_cost_and_exploded_items(self, bom_list=[]): - bom_list = self.traverse_tree(bom_list) - for bom in bom_list: - bom_obj = frappe.get_doc("BOM", bom) - bom_obj.check_recursion(bom_list=bom_list) - bom_obj.update_exploded_items() - - return bom_list - def traverse_tree(self, bom_list=None): def _get_children(bom_no): children = frappe.cache().hget('bom_children', bom_no) @@ -472,10 +461,10 @@ class BOM(WebsiteGenerator): d.rate = rate d.amount = (d.stock_qty or d.qty) * rate - def update_exploded_items(self): + def update_exploded_items(self, save=True): """ Update Flat BOM, following will be correct data""" self.get_exploded_items() - self.add_exploded_items() + self.add_exploded_items(save=save) def get_exploded_items(self): """ Get all raw materials including items from child bom""" @@ -544,11 +533,13 @@ class BOM(WebsiteGenerator): 'sourced_by_supplier': d.get('sourced_by_supplier', 0) })) - def add_exploded_items(self): + def add_exploded_items(self, save=True): "Add items to Flat BOM table" - frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name) self.set('exploded_items', []) + if save: + frappe.db.sql("""delete from `tabBOM Explosion Item` where parent=%s""", self.name) + for d in sorted(self.cur_exploded_items, key=itemgetter(0)): ch = self.append('exploded_items', {}) for i in self.cur_exploded_items[d].keys(): @@ -556,7 +547,9 @@ class BOM(WebsiteGenerator): ch.amount = flt(ch.stock_qty) * flt(ch.rate) ch.qty_consumed_per_unit = flt(ch.stock_qty) / flt(self.quantity) ch.docstatus = self.docstatus - ch.db_insert() + + if save: + ch.db_insert() def validate_bom_links(self): if not self.is_active: From 188ecd7b39f1812f05c60f0e5c70453775b0411f Mon Sep 17 00:00:00 2001 From: Marica Date: Sun, 15 Nov 2020 09:17:42 +0530 Subject: [PATCH 133/154] chore: PO cleanup (#23774) * chore: Subcontracting section and items section enhancement - Set target warehouse moved to Items section - Added subcontraction label to supply RM section * chore: PO & PO Item Form Cleanup * chore: PO Get Items from cleanup * fix: Taxes and Charges field visibility * chore: Cleanup * fix: Translation styling --- .../doctype/purchase_order/purchase_order.js | 39 ++++++--- .../purchase_order/purchase_order.json | 81 ++++++++++++------- .../purchase_order_item.json | 56 ++++++------- 3 files changed, 106 insertions(+), 70 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2f52a9e035..47483c9d1c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -90,6 +90,11 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( this.frm.set_df_property("drop_ship", "hidden", !is_drop_ship); if(doc.docstatus == 1) { + this.frm.fields_dict.items_section.wrapper.addClass("hide-border"); + if(!this.frm.doc.set_warehouse) { + this.frm.fields_dict.items_section.wrapper.removeClass("hide-border"); + } + if(!in_list(["Closed", "Delivered"], doc.status)) { if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { this.frm.add_custom_button(__('Update Items'), () => { @@ -126,16 +131,25 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( if(doc.status != "Closed") { if (doc.status != "On Hold") { if(flt(doc.per_received) < 100 && allow_receipt) { - cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create')); + cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create')); if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) { cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer")); } } if(flt(doc.per_billed) < 100) - cur_frm.add_custom_button(__('Invoice'), + cur_frm.add_custom_button(__('Purchase Invoice'), this.make_purchase_invoice, __('Create')); + if(flt(doc.per_billed)==0 && doc.status != "Delivered") { + cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create')); + } + + if(flt(doc.per_billed)==0) { + this.frm.add_custom_button(__('Payment Request'), + function() { me.make_payment_request() }, __('Create')); + } + if(!doc.auto_repeat) { cur_frm.add_custom_button(__('Subscription'), function() { erpnext.utils.make_subscription(doc.doctype, doc.name) @@ -156,13 +170,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }); } } - if(flt(doc.per_billed)==0) { - this.frm.add_custom_button(__('Payment Request'), - function() { me.make_payment_request() }, __('Create')); - } - if(flt(doc.per_billed)==0 && doc.status != "Delivered") { - cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create')); - } + cur_frm.page.set_inner_btn_group_as_primary(__('Create')); } } else if(doc.docstatus===0) { @@ -358,12 +366,16 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", source_doctype: "Material Request", target: me.frm, - setters: {}, + setters: { + schedule_date: undefined, + status: undefined + }, get_query_filters: { material_request_type: "Purchase", docstatus: 1, status: ["!=", "Stopped"], per_ordered: ["<", 99.99], + company: me.frm.doc.company } }) }, __("Get Items From")); @@ -375,16 +387,17 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( source_doctype: "Supplier Quotation", target: me.frm, setters: { - supplier: me.frm.doc.supplier + supplier: me.frm.doc.supplier, + valid_till: undefined }, get_query_filters: { docstatus: 1, - status: ["!=", "Stopped"], + status: ["not in", ["Stopped", "Expired"]], } }) }, __("Get Items From")); - this.frm.add_custom_button(__('Update rate as per last purchase'), + this.frm.add_custom_button(__('Update Rate as per Last Purchase'), function() { frappe.call({ "method": "get_last_purchase_rate", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 2747c7c54d..4b865a98e9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -30,8 +30,8 @@ "customer_contact_email", "section_addresses", "supplier_address", - "contact_person", "address_display", + "contact_person", "contact_display", "contact_mobile", "contact_email", @@ -49,12 +49,14 @@ "plc_conversion_rate", "ignore_pricing_rule", "sec_warehouse", - "set_warehouse", - "col_break_warehouse", "is_subcontracted", + "col_break_warehouse", "supplier_warehouse", - "items_section", + "before_items_section", "scan_barcode", + "items_col_break", + "set_warehouse", + "items_section", "items", "sb_last_purchase", "total_qty", @@ -108,18 +110,13 @@ "payment_terms_template", "payment_schedule", "tracking_section", - "per_billed", + "status", "column_break_75", + "per_billed", "per_received", "terms_section_break", "tc_name", "terms", - "more_info", - "status", - "ref_sq", - "column_break_74", - "party_account_currency", - "inter_company_order_reference", "column_break5", "letter_head", "select_print_heading", @@ -131,7 +128,12 @@ "to_date", "column_break_97", "auto_repeat", - "update_auto_repeat_reference" + "update_auto_repeat_reference", + "more_info", + "ref_sq", + "column_break_74", + "party_account_currency", + "inter_company_order_reference" ], "fields": [ { @@ -313,34 +315,34 @@ { "fieldname": "supplier_address", "fieldtype": "Link", - "label": "Select Supplier Address", + "label": "Supplier Address", "options": "Address", "print_hide": 1 }, { "fieldname": "contact_person", "fieldtype": "Link", - "label": "Contact Person", + "label": "Supplier Contact", "options": "Contact", "print_hide": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", - "label": "Address", + "label": "Supplier Address Details", "read_only": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "in_global_search": 1, - "label": "Contact", + "label": "Contact Name", "read_only": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", - "label": "Mobile No", + "label": "Contact Mobile No", "read_only": 1 }, { @@ -358,14 +360,14 @@ { "fieldname": "shipping_address", "fieldtype": "Link", - "label": "Select Shipping Address", + "label": "Company Shipping Address", "options": "Address", "print_hide": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", - "label": "Shipping Address", + "label": "Shipping Address Details", "print_hide": 1, "read_only": 1 }, @@ -433,7 +435,8 @@ }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Subcontracting" }, { "description": "Sets 'Warehouse' in each row of the Items table.", @@ -466,6 +469,7 @@ { "fieldname": "items_section", "fieldtype": "Section Break", + "hide_border": 1, "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -598,7 +602,8 @@ }, { "fieldname": "section_break_52", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "taxes", @@ -626,10 +631,12 @@ { "fieldname": "totals", "fieldtype": "Section Break", + "label": "Taxes and Charges", "oldfieldtype": "Section Break", "options": "fa fa-money" }, { + "depends_on": "base_taxes_and_charges_added", "fieldname": "base_taxes_and_charges_added", "fieldtype": "Currency", "label": "Taxes and Charges Added (Company Currency)", @@ -640,6 +647,7 @@ "read_only": 1 }, { + "depends_on": "base_taxes_and_charges_deducted", "fieldname": "base_taxes_and_charges_deducted", "fieldtype": "Currency", "label": "Taxes and Charges Deducted (Company Currency)", @@ -650,6 +658,7 @@ "read_only": 1 }, { + "depends_on": "base_total_taxes_and_charges", "fieldname": "base_total_taxes_and_charges", "fieldtype": "Currency", "label": "Total Taxes and Charges (Company Currency)", @@ -665,6 +674,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "taxes_and_charges_added", "fieldname": "taxes_and_charges_added", "fieldtype": "Currency", "label": "Taxes and Charges Added", @@ -675,6 +685,7 @@ "read_only": 1 }, { + "depends_on": "taxes_and_charges_deducted", "fieldname": "taxes_and_charges_deducted", "fieldtype": "Currency", "label": "Taxes and Charges Deducted", @@ -685,6 +696,7 @@ "read_only": 1 }, { + "depends_on": "total_taxes_and_charges", "fieldname": "total_taxes_and_charges", "fieldtype": "Currency", "label": "Total Taxes and Charges", @@ -694,7 +706,7 @@ }, { "collapsible": 1, - "collapsible_depends_on": "discount_amount", + "collapsible_depends_on": "apply_discount_on", "fieldname": "discount_section", "fieldtype": "Section Break", "label": "Additional Discount" @@ -734,7 +746,8 @@ }, { "fieldname": "totals_section", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Totals" }, { "fieldname": "base_grand_total", @@ -902,12 +915,12 @@ }, { "fieldname": "ref_sq", - "fieldtype": "Data", - "hidden": 1, - "label": "Ref SQ", + "fieldtype": "Link", + "label": "Supplier Quotation", "no_copy": 1, "oldfieldname": "ref_sq", "oldfieldtype": "Data", + "options": "Supplier Quotation", "print_hide": 1, "read_only": 1 }, @@ -1061,7 +1074,7 @@ "collapsible": 1, "fieldname": "tracking_section", "fieldtype": "Section Break", - "label": "Tracking" + "label": "Order Status" }, { "fieldname": "column_break_75", @@ -1070,21 +1083,29 @@ { "fieldname": "billing_address", "fieldtype": "Link", - "label": "Select Billing Address", + "label": "Company Billing Address", "options": "Address" }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", - "label": "Billing Address", + "label": "Billing Address Details", "read_only": 1 + }, + { + "fieldname": "before_items_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "items_col_break", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-10-07 14:31:57.661221", + "modified": "2020-10-30 11:39:37.388249", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 7a52c28a0e..10db240a44 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -24,6 +24,7 @@ "col_break2", "uom", "conversion_factor", + "stock_qty", "sec_break1", "price_list_rate", "discount_percentage", @@ -46,11 +47,8 @@ "column_break_32", "base_net_rate", "base_net_amount", - "billed_amt", "warehouse_and_reference", "warehouse", - "delivered_by_supplier", - "project", "material_request", "material_request_item", "sales_order", @@ -58,36 +56,37 @@ "supplier_quotation", "supplier_quotation_item", "col_break5", + "delivered_by_supplier", "against_blanket_order", "blanket_order", "blanket_order_rate", "item_group", "brand", - "bom", - "include_exploded_items", "section_break_56", - "stock_qty", - "column_break_60", "received_qty", "returned_qty", - "manufacture_details", - "manufacturer", - "column_break_14", - "manufacturer_part_no", - "more_info_section_break", - "is_fixed_asset", - "item_tax_rate", + "column_break_60", + "billed_amt", "accounting_details", "expense_account", - "column_break_68", + "manufacture_details", + "manufacturer", + "manufacturer_part_no", + "column_break_14", + "bom", + "include_exploded_items", "item_weight_details", "weight_per_unit", "total_weight", "column_break_40", "weight_uom", "accounting_dimensions_section", - "cost_center", + "project", "dimension_col_break", + "cost_center", + "more_info_section_break", + "is_fixed_asset", + "item_tax_rate", "section_break_72", "page_break" ], @@ -346,6 +345,7 @@ }, { "default": "0", + "depends_on": "is_free_item", "fieldname": "is_free_item", "fieldtype": "Check", "label": "Is Free Item", @@ -508,9 +508,10 @@ }, { "default": "0", + "depends_on": "delivered_by_supplier", "fieldname": "delivered_by_supplier", "fieldtype": "Check", - "label": "To be delivered to customer", + "label": "To be Delivered to Customer", "print_hide": 1, "read_only": 1 }, @@ -558,6 +559,7 @@ "read_only": 1 }, { + "depends_on": "eval:parent.is_subcontracted == 'Yes'", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", @@ -574,21 +576,21 @@ }, { "fieldname": "section_break_56", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Billed, Received & Returned" }, { "fieldname": "stock_qty", "fieldtype": "Float", - "label": "Qty as per Stock UOM", + "label": "Qty in Stock UOM", "no_copy": 1, - "oldfieldname": "stock_qty", - "oldfieldtype": "Currency", "print_hide": 1, "print_width": "100px", "read_only": 1, "width": "100px" }, { + "depends_on": "received_qty", "fieldname": "received_qty", "fieldtype": "Float", "label": "Received Qty", @@ -612,9 +614,10 @@ "fieldtype": "Column Break" }, { + "depends_on": "billed_amt", "fieldname": "billed_amt", "fieldtype": "Currency", - "label": "Billed Amt", + "label": "Billed Amount", "no_copy": 1, "options": "currency", "print_hide": 1, @@ -633,6 +636,7 @@ "report_hide": 1 }, { + "collapsible": 1, "fieldname": "accounting_details", "fieldtype": "Section Break", "label": "Accounting Details" @@ -644,10 +648,6 @@ "options": "Account", "print_hide": 1 }, - { - "fieldname": "column_break_68", - "fieldtype": "Column Break" - }, { "fieldname": "cost_center", "fieldtype": "Link", @@ -715,6 +715,7 @@ }, { "default": "0", + "depends_on": "is_fixed_asset", "fetch_from": "item_code.is_fixed_asset", "fieldname": "is_fixed_asset", "fieldtype": "Check", @@ -728,9 +729,10 @@ } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-04-21 11:55:58.643393", + "modified": "2020-10-30 11:59:47.670951", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", From 4bf09dd02bacd1a4f5eb44bc8bccf53a5c9e6652 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 15 Nov 2020 05:02:24 +0100 Subject: [PATCH 134/154] fix: fiscal year can be shorter than 12 months (#23838) * fix: replace unnecessary SQL query * feat: checkbox "Is Short Year" Also, set checkbox and dates only once. * fix: toggle no longer necessary Date fields use set_only_once. * feat: short years can be less than 12 months * fix: if short year, don't add 12 months to start date * test: short year * fix: move short fiscal year to test records --- .../doctype/fiscal_year/fiscal_year.js | 14 +- .../doctype/fiscal_year/fiscal_year.json | 435 +++++------------- .../doctype/fiscal_year/fiscal_year.py | 19 +- .../doctype/fiscal_year/test_fiscal_year.py | 1 + .../doctype/fiscal_year/test_records.json | 7 + 5 files changed, 131 insertions(+), 345 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.js b/erpnext/accounts/doctype/fiscal_year/fiscal_year.js index 152e17dbc8..bc77dac1cd 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.js +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.js @@ -9,11 +9,7 @@ frappe.ui.form.on('Fiscal Year', { } }, refresh: function (frm) { - let doc = frm.doc; - frm.toggle_enable('year_start_date', doc.__islocal); - frm.toggle_enable('year_end_date', doc.__islocal); - - if (!doc.__islocal && (doc.name != frappe.sys_defaults.fiscal_year)) { + if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) { frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm)); frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'")); } else { @@ -24,8 +20,10 @@ frappe.ui.form.on('Fiscal Year', { return frm.call('set_as_default'); }, year_start_date: function(frm) { - let year_end_date = - frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1); - frm.set_value("year_end_date", year_end_date); + if (!frm.doc.is_short_year) { + let year_end_date = + frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1); + frm.set_value("year_end_date", year_end_date); + } }, }); diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json index 4ca9f6b96f..5ab91f2506 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json @@ -1,347 +1,126 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "field:year", - "beta": 0, - "creation": "2013-01-22 16:50:25", - "custom": 0, - "description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "actions": [], + "allow_import": 1, + "autoname": "field:year", + "creation": "2013-01-22 16:50:25", + "description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "year", + "disabled", + "is_short_year", + "year_start_date", + "year_end_date", + "companies", + "auto_created" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "For e.g. 2012, 2012-13", - "fieldname": "year", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Year Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "year", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "description": "For e.g. 2012, 2012-13", + "fieldname": "year", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Year Name", + "oldfieldname": "year", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "year_start_date", - "fieldtype": "Date", - "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": "Year Start Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "year_start_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "year_start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Year Start Date", + "no_copy": 1, + "oldfieldname": "year_start_date", + "oldfieldtype": "Date", + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "year_end_date", - "fieldtype": "Date", - "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": "Year End Date", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "year_end_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Year End Date", + "no_copy": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "companies", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Companies", - "length": 0, - "no_copy": 0, - "options": "Fiscal Year Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "companies", + "fieldtype": "Table", + "label": "Companies", + "options": "Fiscal Year Company" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "auto_created", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Created", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "auto_created", + "fieldtype": "Check", + "hidden": 1, + "label": "Auto Created", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "description": "Less than 12 months.", + "fieldname": "is_short_year", + "fieldtype": "Check", + "label": "Is Short Year", + "set_only_once": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-calendar", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-04-25 14:21:41.273354", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Fiscal Year", - "owner": "Administrator", + ], + "icon": "fa fa-calendar", + "idx": 1, + "links": [], + "modified": "2020-11-05 12:16:53.081573", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Fiscal Year", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "read": 1, + "role": "Sales User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "read": 1, + "role": "Purchase User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "read": 1, + "role": "Accounts User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "read": 1, + "role": "Stock User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Employee", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Employee" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "name", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "show_name_in_global_search": 1, + "sort_field": "name", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index d80bc7fad1..da6a3fd2ef 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -36,6 +36,11 @@ class FiscalYear(Document): frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved.")) def validate_dates(self): + if self.is_short_year: + # Fiscal Year can be shorter than one year, in some jurisdictions + # under certain circumstances. For example, in the USA and Germany. + return + if getdate(self.year_start_date) > getdate(self.year_end_date): frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"), FiscalYearIncorrectDate) @@ -116,12 +121,8 @@ def auto_create_fiscal_year(): pass def get_from_and_to_date(fiscal_year): - from_and_to_date_tuple = frappe.db.sql("""select year_start_date, year_end_date - from `tabFiscal Year` where name=%s""", (fiscal_year))[0] - - from_and_to_date = { - "from_date": from_and_to_date_tuple[0], - "to_date": from_and_to_date_tuple[1] - } - - return from_and_to_date + fields = [ + "year_start_date as from_date", + "year_end_date as to_date" + ] + return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1) diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py index f7b7782766..cec4f4492d 100644 --- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py @@ -11,6 +11,7 @@ test_records = frappe.get_test_records('Fiscal Year') test_ignore = ["Company"] class TestFiscalYear(unittest.TestCase): + def test_extra_year(self): if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"): frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000") diff --git a/erpnext/accounts/doctype/fiscal_year/test_records.json b/erpnext/accounts/doctype/fiscal_year/test_records.json index d5723ca62b..44052535cb 100644 --- a/erpnext/accounts/doctype/fiscal_year/test_records.json +++ b/erpnext/accounts/doctype/fiscal_year/test_records.json @@ -1,4 +1,11 @@ [ + { + "doctype": "Fiscal Year", + "year": "_Test Short Fiscal Year 2011", + "is_short_year": 1, + "year_end_date": "2011-04-01", + "year_start_date": "2011-12-31" + }, { "doctype": "Fiscal Year", "year": "_Test Fiscal Year 2012", From 8ff0d0cff60dd38a25341f83978807aaf0216167 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 15 Nov 2020 05:04:05 +0100 Subject: [PATCH 135/154] feat: make account number length configurable (#23845) --- .../datev_settings/datev_settings.json | 22 ++++++++++++++----- .../regional/germany/utils/datev/datev_csv.py | 2 +- erpnext/regional/report/datev/datev.py | 2 ++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.json b/erpnext/regional/doctype/datev_settings/datev_settings.json index 39486dfc12..713e8e34ef 100644 --- a/erpnext/regional/doctype/datev_settings/datev_settings.json +++ b/erpnext/regional/doctype/datev_settings/datev_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "field:client", "creation": "2019-08-13 23:56:34.259906", "doctype": "DocType", @@ -6,6 +7,7 @@ "engine": "InnoDB", "field_order": [ "client", + "account_number_length", "column_break_2", "client_number", "section_break_4", @@ -28,8 +30,8 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Client ID", - "reqd": 1, - "length": 5 + "length": 5, + "reqd": 1 }, { "fieldname": "consultant", @@ -43,8 +45,8 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Consultant ID", - "reqd": 1, - "length": 7 + "length": 7, + "reqd": 1 }, { "fieldname": "column_break_2", @@ -57,9 +59,17 @@ { "fieldname": "column_break_6", "fieldtype": "Column Break" + }, + { + "default": "4", + "fieldname": "account_number_length", + "fieldtype": "Int", + "label": "Account Number Length", + "reqd": 1 } ], - "modified": "2019-08-14 00:03:26.616460", + "links": [], + "modified": "2020-11-05 17:52:11.674329", "modified_by": "Administrator", "module": "Regional", "name": "DATEV Settings", @@ -104,4 +114,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index cf07a1c824..f138a807bc 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -106,7 +106,7 @@ def get_header(filters, csv_class): # M = Start of the fiscal year (Wirtschaftsjahresbeginn) frappe.utils.formatdate(filters.get('fiscal_year_start'), 'yyyyMMdd'), # N = Length of account numbers (Sachkontenlänge) - datev_settings.get('account_number_length', '4'), + str(filters.get('account_number_length', 4)), # O = Transaction batch start date (YYYYMMDD) frappe.utils.formatdate(filters.get('from_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # P = Transaction batch end date (YYYYMMDD) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index dbae230f1e..3f4cb981cc 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -340,6 +340,8 @@ def download_datev_csv(filters): coa = frappe.get_value('Company', company, 'chart_of_accounts') filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '') + filters['account_number_length'] = frappe.get_value('DATEV Settings', company, 'account_number_length') + transactions = get_transactions(filters) account_names = get_account_names(filters) customers = get_customers(filters) From 1c8c1d78e9f7c05adfd9bd8e5723fa36d8568f4d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 15 Nov 2020 09:40:44 +0530 Subject: [PATCH 136/154] fix: default cost center in item master not set in stock entry (#23877) Co-authored-by: Marica --- .../doctype/work_order/test_work_order.py | 5 ++++ .../stock/doctype/stock_entry/stock_entry.py | 5 ++-- erpnext/stock/get_item_details.py | 23 ++++++++++++++++--- 3 files changed, 27 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 5f8a13428c..e53927918e 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -443,6 +443,11 @@ class TestWorkOrder(unittest.TestCase): ste1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1)) self.assertEqual(len(ste1.items), 3) + def test_cost_center_for_manufacture(self): + wo_order = make_wo_order_test_record() + ste = make_stock_entry(wo_order.name, "Material Transfer for Manufacture", wo_order.qty) + self.assertEquals(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") + def test_operation_time_with_batch_size(self): fg_item = "Test Batch Size Item For BOM" rm1 = "Test Batch Size Item RM 1 For BOM" diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 768526705c..e3159b95c3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1227,8 +1227,6 @@ class StockEntry(StockController): return item_dict def add_to_stock_entry_detail(self, item_dict, bom_no=None): - cost_center = frappe.db.get_value("Company", self.company, 'cost_center') - for d in item_dict: stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom") @@ -1239,9 +1237,10 @@ class StockEntry(StockController): se_child.uom = item_dict[d]["uom"] if item_dict[d].get("uom") else stock_uom se_child.stock_uom = stock_uom se_child.qty = flt(item_dict[d]["qty"], se_child.precision("qty")) - se_child.cost_center = item_dict[d].get("cost_center") or cost_center se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0) se_child.subcontracted_item = item_dict[d].get("main_item_code") + se_child.cost_center = (item_dict[d].get("cost_center") or + get_default_cost_center(item_dict[d], company = self.company)) for field in ["idx", "po_detail", "original_item", "expense_account", "description", "item_name"]: diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 8d8dcb74c3..08f7a83b89 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -559,23 +559,40 @@ def get_default_deferred_account(args, item, fieldname=None): else: return None -def get_default_cost_center(args, item, item_group, brand, company=None): +def get_default_cost_center(args, item=None, item_group=None, brand=None, company=None): cost_center = None + + if not company and args.get("company"): + company = args.get("company") + if args.get('project'): cost_center = frappe.db.get_value("Project", args.get("project"), "cost_center", cache=True) - if not cost_center: + if not cost_center and (item and item_group and brand): if args.get('customer'): cost_center = item.get('selling_cost_center') or item_group.get('selling_cost_center') or brand.get('selling_cost_center') else: cost_center = item.get('buying_cost_center') or item_group.get('buying_cost_center') or brand.get('buying_cost_center') - cost_center = cost_center or args.get("cost_center") + elif not cost_center and args.get("item_code") and company: + for method in ["get_item_defaults", "get_item_group_defaults", "get_brand_defaults"]: + path = "erpnext.stock.get_item_details.{0}".format(method) + data = frappe.get_attr(path)(args.get("item_code"), company) + + if data and (data.selling_cost_center or data.buying_cost_center): + return data.selling_cost_center or data.buying_cost_center + + if not cost_center and args.get("cost_center"): + cost_center = args.get("cost_center") if (company and cost_center and frappe.get_cached_value("Cost Center", cost_center, "company") != company): return None + if not cost_center and company: + cost_center = frappe.get_cached_value("Company", + company, "cost_center") + return cost_center def get_default_supplier(args, item, item_group, brand): From c5966d3d70ae8f4a1596b127c60d36de5543ee87 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Sun, 15 Nov 2020 09:44:36 +0530 Subject: [PATCH 137/154] fix: make contract template editable (#23891) --- erpnext/crm/doctype/contract_template/contract_template.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/contract_template/contract_template.json b/erpnext/crm/doctype/contract_template/contract_template.json index ef9974f863..5e4582f8d3 100644 --- a/erpnext/crm/doctype/contract_template/contract_template.json +++ b/erpnext/crm/doctype/contract_template/contract_template.json @@ -23,8 +23,7 @@ { "fieldname": "contract_terms", "fieldtype": "Text Editor", - "label": "Contract Terms and Conditions", - "read_only": 1 + "label": "Contract Terms and Conditions" }, { "fieldname": "sb_fulfilment", @@ -45,7 +44,7 @@ } ], "links": [], - "modified": "2020-06-03 00:24:58.179816", + "modified": "2020-11-11 17:49:44.879363", "modified_by": "Administrator", "module": "CRM", "name": "Contract Template", From 7178df8567a633b45957ef2c4cee43c99829ee11 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 15 Nov 2020 05:15:10 +0100 Subject: [PATCH 138/154] fix: improve UX of DATEV report (#23892) * feat(DATEV Settings): button to show report * feat(desk): add DATEV to Reports under Accounting * fix(report DATEV): last calendar month as default * fix: let user create DATEV Settings (Instead of showing an error message.) --- .../desk_page/accounting/accounting.json | 4 ++-- .../doctype/datev_settings/datev_settings.js | 6 +++--- erpnext/regional/report/datev/datev.js | 18 ++++++++++++++++-- erpnext/regional/report/datev/datev.py | 19 +++++++++++++------ 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 9172792411..85c9209205 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -23,7 +23,7 @@ { "hidden": 0, "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"DATEV Export\",\n \"name\": \"DATEV\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -98,7 +98,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-11-06 13:05:58.650150", + "modified": "2020-11-11 18:35:11.542909", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.js b/erpnext/regional/doctype/datev_settings/datev_settings.js index 69747b0b89..f04705929f 100644 --- a/erpnext/regional/doctype/datev_settings/datev_settings.js +++ b/erpnext/regional/doctype/datev_settings/datev_settings.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('DATEV Settings', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.add_custom_button('Show Report', () => frappe.set_route('query-report', 'DATEV'), "fa fa-table"); + } }); diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js index 55f12cf373..4124e3df19 100644 --- a/erpnext/regional/report/datev/datev.js +++ b/erpnext/regional/report/datev/datev.js @@ -11,14 +11,14 @@ frappe.query_reports["DATEV"] = { { "fieldname": "from_date", "label": __("From Date"), - "default": frappe.datetime.month_start(), + "default": moment().subtract(1, 'month').startOf('month').format(), "fieldtype": "Date", "reqd": 1 }, { "fieldname": "to_date", "label": __("To Date"), - "default": frappe.datetime.now_date(), + "default": moment().subtract(1, 'month').endOf('month').format(), "fieldtype": "Date", "reqd": 1 }, @@ -30,9 +30,23 @@ frappe.query_reports["DATEV"] = { } ], onload: function(query_report) { + let company = frappe.query_report.get_filter_value('company'); + frappe.db.exists('DATEV Settings', company).then((settings_exist) => { + if (!settings_exist) { + frappe.confirm(__('DATEV Settings for your Company are missing. Would you like to create them now?'), + () => frappe.new_doc('DATEV Settings', {'company': company}) + ); + } + }); + query_report.page.add_menu_item(__("Download DATEV File"), () => { const filters = JSON.stringify(query_report.get_values()); window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`); }); + + query_report.page.add_menu_item(__("Change DATEV Settings"), () => { + let company = frappe.query_report.get_filter_value('company'); // read company from filters again – it might have changed by now. + frappe.set_route('Form', 'DATEV Settings', company); + }); } }; diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 3f4cb981cc..1e39c57786 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -94,8 +94,11 @@ COLUMNS = [ def execute(filters=None): """Entry point for frappe.""" - validate(filters) - return COLUMNS, get_transactions(filters, as_dict=0) + data = [] + if filters and validate(filters): + data = get_transactions(filters, as_dict=0) + + return COLUMNS, data def validate(filters): @@ -114,10 +117,14 @@ def validate(filters): validate_fiscal_year(from_date, to_date, company) - try: - frappe.get_doc('DATEV Settings', filters.get('company')) - except frappe.DoesNotExistError: - frappe.throw(_('Please create DATEV Settings for Company {}.').format(filters.get('company'))) + if not frappe.db.exists('DATEV Settings', filters.get('company')): + frappe.log_error(_('Please create {} for Company {}.').format( + '{}'.format(_('DATEV Settings')), + frappe.bold(filters.get('company')) + )) + return False + + return True def validate_fiscal_year(from_date, to_date, company): From 69be37f88d95d8be4ff64f468f0a7adfef59bd19 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Sun, 15 Nov 2020 11:14:35 +0530 Subject: [PATCH 139/154] fix: Error handling in Upload Attendance (#23907) --- erpnext/hr/doctype/upload_attendance/upload_attendance.js | 8 ++++---- erpnext/hr/doctype/upload_attendance/upload_attendance.py | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.js b/erpnext/hr/doctype/upload_attendance/upload_attendance.js index 9df2948a15..29aa85484a 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.js +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.js @@ -24,10 +24,10 @@ erpnext.hr.AttendanceControlPanel = frappe.ui.form.Controller.extend({ } window.location.href = repl(frappe.request.url + '?cmd=%(cmd)s&from_date=%(from_date)s&to_date=%(to_date)s', { - cmd: "erpnext.hr.doctype.upload_attendance.upload_attendance.get_template", - from_date: this.frm.doc.att_fr_date, - to_date: this.frm.doc.att_to_date, - }); + cmd: "erpnext.hr.doctype.upload_attendance.upload_attendance.get_template", + from_date: this.frm.doc.att_fr_date, + to_date: this.frm.doc.att_to_date, + }); }, show_upload() { diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py index edf05e827b..674c8e3eb4 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py @@ -28,7 +28,12 @@ def get_template(): w = UnicodeWriter() w = add_header(w) - w = add_data(w, args) + try: + w = add_data(w, args) + except Exception as e: + frappe.clear_messages() + frappe.respond_as_web_page("Holiday List Missing", html=e) + return # write out response as a type csv frappe.response['result'] = cstr(w.getvalue()) From b8d0b546a7851fd24c9815a9e797a7717adab2b0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 16 Nov 2020 12:58:38 +0530 Subject: [PATCH 140/154] feat(UAE VAT Format): use company address for emir --- erpnext/regional/united_arab_emirates/setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 62156e46be..6f520f6ad6 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -56,7 +56,8 @@ def make_custom_fields(): fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), dict(fieldname='vat_emirate', label='VAT Emirate', insert_after='permit_no', fieldtype='Select', - options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain'), + options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain', + fetch_from='company_address.emirate'), dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)', insert_after='vat_emirate', fieldtype='Currency', print_hide=1, default='0'), ] @@ -100,6 +101,10 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Data', insert_after='supplier_name'), ], + 'Address': [ + dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', + options='Abu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') + ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, 'Purchase Order': purchase_invoice_fields + invoice_fields, 'Purchase Receipt': purchase_invoice_fields + invoice_fields, From 2e1787300d1b881d638e9786c52d6f43f85f780a Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 16 Nov 2020 17:36:27 +0530 Subject: [PATCH 141/154] feat(UAE VAT 201): emirate according to branch --- .../doctype/sales_invoice/sales_invoice.js | 23 +++++++++++++++++++ .../regional/united_arab_emirates/setup.py | 13 ++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 502e65ed8d..803312072c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -446,6 +446,29 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.frm.refresh_field("outstanding_amount"); this.frm.refresh_field("paid_amount"); this.frm.refresh_field("base_paid_amount"); + }, + + branch: function(){ + const me = this + if (this.frm.doc.branch) { + frappe.call({ + async: false, + method: "frappe.client.get_value", + args: { + "doctype": "Branch", + "filters": { + 'name': this.frm.doc.branch + }, + "fieldname": ['emirate'] + }, + callback: function (res) { + if (res.message && res.message['emirate']) { + me.frm.set_value("vat_emirate", res.message['emirate']) + me.frm.refresh_field("vat_emirate") + } + } + }) + } } }); diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 6f520f6ad6..2e546deb82 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -38,8 +38,9 @@ def make_custom_fields(): dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Read Only', insert_after='supplier_name', fetch_from='supplier.supplier_name_in_arabic', print_hide=1), - dict(fieldname='recoverable_standard_rated_expenses', label='Recoverable Standard Rated Expenses (AED)', - insert_after='permit_no', fieldtype='Currency', print_hide=1, default='0'), + dict(fieldname='recoverable_standard_rated_expenses', print_hide=1, default='0', + label='Recoverable Standard Rated Expenses (AED)', insert_after='permit_no', + fieldtype='Currency', ), dict(fieldname='reverse_charge', label='Reverse Charge Applicable', fieldtype='Select', insert_after='recoverable_standard_rated_expenses', print_hide=1, options='Y\nN', default='N'), @@ -55,6 +56,8 @@ def make_custom_fields(): dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), + dict(fieldname='branch', label='Branch', options='Branch', + fieldtype='Link', insert_after='company', print_hide=1), dict(fieldname='vat_emirate', label='VAT Emirate', insert_after='permit_no', fieldtype='Select', options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain', fetch_from='company_address.emirate'), @@ -103,7 +106,11 @@ def make_custom_fields(): ], 'Address': [ dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', - options='Abu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') + options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') + ], + 'Branch': [ + dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', + options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, 'Purchase Order': purchase_invoice_fields + invoice_fields, From d2477e6cc5cd0acc9353e9dc8a0b23a729da6ff3 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Mon, 16 Nov 2020 18:47:05 +0530 Subject: [PATCH 142/154] fix(UAE VAT 201): remove branch parts --- .../doctype/sales_invoice/sales_invoice.js | 23 ------------------- .../regional/united_arab_emirates/setup.py | 6 ----- 2 files changed, 29 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 803312072c..502e65ed8d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -446,29 +446,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.frm.refresh_field("outstanding_amount"); this.frm.refresh_field("paid_amount"); this.frm.refresh_field("base_paid_amount"); - }, - - branch: function(){ - const me = this - if (this.frm.doc.branch) { - frappe.call({ - async: false, - method: "frappe.client.get_value", - args: { - "doctype": "Branch", - "filters": { - 'name': this.frm.doc.branch - }, - "fieldname": ['emirate'] - }, - callback: function (res) { - if (res.message && res.message['emirate']) { - me.frm.set_value("vat_emirate", res.message['emirate']) - me.frm.refresh_field("vat_emirate") - } - } - }) - } } }); diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 2e546deb82..013ae5cf73 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -56,8 +56,6 @@ def make_custom_fields(): dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', fieldtype='Read Only', insert_after='customer_name', fetch_from='customer.customer_name_in_arabic', print_hide=1), - dict(fieldname='branch', label='Branch', options='Branch', - fieldtype='Link', insert_after='company', print_hide=1), dict(fieldname='vat_emirate', label='VAT Emirate', insert_after='permit_no', fieldtype='Select', options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain', fetch_from='company_address.emirate'), @@ -108,10 +106,6 @@ def make_custom_fields(): dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') ], - 'Branch': [ - dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state', - options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain') - ], 'Purchase Invoice': purchase_invoice_fields + invoice_fields, 'Purchase Order': purchase_invoice_fields + invoice_fields, 'Purchase Receipt': purchase_invoice_fields + invoice_fields, From 5e8b00207b248433861d10945ef91d0595403bd0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:58:09 +0530 Subject: [PATCH 143/154] fix: Typo (Enchashment > Encashment) (#23919) --- erpnext/patches/v12_0/generate_leave_ledger_entries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 7afde373c3..fe072d7eb9 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -51,7 +51,7 @@ def generate_encashment_leave_ledger_entries(): for encashment in leave_encashments: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}): - frappe.get_doc("Leave Enchashment", encashment).create_leave_ledger_entry() + frappe.get_doc("Leave Encashment", encashment).create_leave_ledger_entry() def generate_expiry_allocation_ledger_entries(): ''' fix ledger entries for missing leave allocation transaction ''' From 23614b650e40e46c20952b64dd256cd0781a455a Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 17 Nov 2020 11:12:31 +0530 Subject: [PATCH 144/154] fix: Don't copy terms and discount from SO to PO (#23903) * fix: Dont copy terms, discount and required by from SO to PO * fix: Let delivery date and required by date get mapped --- .../selling/doctype/sales_order/sales_order.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index ec1c82339b..04d85e575c 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -844,7 +844,8 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t "contact_email", "contact_person", "taxes_and_charges", - "shipping_address" + "shipping_address", + "terms" ], "validation": { "docstatus": ["=", 1] @@ -863,7 +864,10 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t "field_no_map": [ "rate", "price_list_rate", - "item_tax_template" + "item_tax_template", + "discount_percentage", + "discount_amount", + "pricing_rules" ], "postprocess": update_item, "condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.supplier == supplier and doc.item_code in items_to_map @@ -917,7 +921,8 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "contact_email", "contact_person", "taxes_and_charges", - "shipping_address" + "shipping_address", + "terms" ], "validation": { "docstatus": ["=", 1] @@ -937,7 +942,10 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None): "rate", "price_list_rate", "item_tax_template", - "supplier" + "discount_percentage", + "discount_amount", + "supplier", + "pricing_rules" ], "postprocess": update_item, "condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.item_code in items_to_map From c72b69322361a3291947b346b4cdca11c4dca8a6 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:08:31 +0530 Subject: [PATCH 145/154] fix: Handle the "no leave_allocation found" case (#23922) * fix: Handle the "no leave_allocation found" case * fix: format of error msg --- erpnext/hr/doctype/leave_encashment/leave_encashment.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 8913c648c5..c1dcc97b1a 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -32,7 +32,7 @@ class LeaveEncashment(Document): additional_salary.employee = self.employee earning_component = frappe.get_value("Leave Type", self.leave_type, "earning_component") if not earning_component: - frappe.throw(_("Please set Earning Component for Leave type: {0}.".format(self.leave_type))) + frappe.throw(_("Please set Earning Component for Leave type: {0}.").format(self.leave_type)) additional_salary.salary_component = earning_component additional_salary.payroll_date = self.encashment_date additional_salary.amount = self.encashment_amount @@ -98,7 +98,11 @@ class LeaveEncashment(Document): create_leave_ledger_entry(self, args, submit) # create reverse entry for expired leaves - to_date = self.get_leave_allocation().get('to_date') + leave_allocation = self.get_leave_allocation() + if not leave_allocation: + return + + to_date = leave_allocation.get('to_date') if to_date < getdate(nowdate()): args = frappe._dict( leaves=self.encashable_days, From 7aa60d1dea89419d3cc8db7340806a40281cb4ac Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 17 Nov 2020 12:10:27 +0530 Subject: [PATCH 146/154] fix: stock ageing report not working (#23923) --- erpnext/stock/report/stock_ageing/stock_ageing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 3dc806fb43..8aaf7abcbe 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -20,7 +20,8 @@ def execute(filters=None): fifo_queue = sorted(filter(_func, item_dict["fifo_queue"]), key=_func) details = item_dict["details"] - if not fifo_queue and (not item_dict.get("total_qty")): continue + + if not fifo_queue: continue average_age = get_average_age(fifo_queue, to_date) earliest_age = date_diff(to_date, fifo_queue[0][1]) From a77f2ba0def67756a4f3b91fe02847615df39e20 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 17 Nov 2020 15:34:05 +0530 Subject: [PATCH 147/154] fix: validation for membership --- erpnext/non_profit/doctype/membership/membership.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 4c85cb60e8..7d15abaa3b 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -70,7 +70,7 @@ class Membership(Document): settings = frappe.get_doc("Membership Settings") if not member.customer: - frappe.throw(_("No customer linked to member {}", [member.name])) + frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member))) if not settings.debit_account: frappe.throw(_("You need to set Debit Account in Membership Settings")) From 327143731465510840c3b8c6fe3e12187807ca03 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 17 Nov 2020 17:52:59 +0530 Subject: [PATCH 148/154] fix: Handle for custom field IFSC code in Bank remittance report. (#23905) * fix: Handel for custom field IFSC code * fix: changes_requested --- .../report/bank_remittance/bank_remittance.py | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.py b/erpnext/payroll/report/bank_remittance/bank_remittance.py index 4b052bf5c4..500543ceb0 100644 --- a/erpnext/payroll/report/bank_remittance/bank_remittance.py +++ b/erpnext/payroll/report/bank_remittance/bank_remittance.py @@ -47,33 +47,39 @@ def execute(filters=None): "fieldtype": "Int", "fieldname": "employee_account_no", "width": 50 - }, - { + } + ] + + if frappe.db.has_column('Employee', 'ifsc_code'): + columns.append({ "label": _("IFSC Code"), "fieldtype": "Data", "fieldname": "bank_code", "width": 100 - }, - { - "label": _("Currency"), - "fieldtype": "Data", - "fieldname": "currency", - "width": 50 - }, - { - "label": _("Net Salary Amount"), - "fieldtype": "Currency", - "options": "currency", - "fieldname": "amount", - "width": 100 - } - ] + }) + + columns += [{ + "label": _("Currency"), + "fieldtype": "Data", + "fieldname": "currency", + "width": 50 + }, + { + "label": _("Net Salary Amount"), + "fieldtype": "Currency", + "options": "currency", + "fieldname": "amount", + "width": 100 + }] + data = [] accounts = get_bank_accounts() payroll_entries = get_payroll_entries(accounts, filters) salary_slips = get_salary_slips(payroll_entries) - get_emp_bank_ifsc_code(salary_slips) + + if frappe.db.has_column('Employee', 'ifsc_code'): + get_emp_bank_ifsc_code(salary_slips) for salary in salary_slips: if salary.bank_name and salary.bank_account_no and salary.debit_acc_no and salary.status in ["Submitted", "Paid"]: From 69232f8dd1a3061fa650f8cbac185d5933f0bf6b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Nov 2020 22:00:41 +0530 Subject: [PATCH 149/154] fix: Loan application link on creating loan --- .../loan_management/doctype/loan_application/loan_application.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index bac6e638d7..e59db4c12d 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -127,6 +127,7 @@ def create_loan(source_name, target_doc=None, submit=0): target_doc.loan_account = account_details.loan_account target_doc.interest_income_account = account_details.interest_income_account target_doc.penalty_income_account = account_details.penalty_income_account + target_doc.loan_application = source_name doclist = get_mapped_doc("Loan Application", source_name, { From d82b76fe0ace6076f1087c9c0ba7552b7611cd1b Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 18 Nov 2020 11:56:49 +0530 Subject: [PATCH 150/154] fix: pos item search includes non stock items (#23914) * fix: pos item search includes non stock items * chore: add validation for non stock items --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 9 +++++++++ erpnext/selling/page/point_of_sale/point_of_sale.py | 1 + 2 files changed, 10 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index a7e20a0c32..d486ff6028 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -39,6 +39,7 @@ class POSInvoice(SalesInvoice): self.validate_serialised_or_batched_item() self.validate_stock_availablility() self.validate_return_items_qty() + self.validate_non_stock_items() self.set_status() self.set_account_for_mode_of_payment() self.validate_pos() @@ -174,6 +175,14 @@ class POSInvoice(SalesInvoice): _("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}") .format(d.idx, bold_serial_no, bold_return_against) ) + + def validate_non_stock_items(self): + for d in self.get("items"): + is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") + if not is_stock_item: + frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format( + d.idx, frappe.bold(d.item_code) + ), title=_("Invalid Item")) def validate_mode_of_payment(self): if len(self.payments) == 0: diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index a690050f79..062cba19e6 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -62,6 +62,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va `tabItem` item {bin_join_selection} WHERE item.disabled = 0 + AND item.is_stock_item = 1 AND item.has_variants = 0 AND item.is_sales_item = 1 AND item.is_fixed_asset = 0 From 188657d05a9f0199de898094bbe033e51d52b930 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 17:15:54 +0530 Subject: [PATCH 151/154] fix: email digest user not found --- erpnext/setup/doctype/email_digest/email_digest.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index b30bd7814b..cbb4c7c5de 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -48,12 +48,8 @@ class EmailDigest(Document): recipients = list(filter(lambda r: r in valid_users, self.recipient_list.split("\n"))) - original_user = frappe.session.user - if recipients: for user_id in recipients: - frappe.set_user(user_id) - frappe.set_user_lang(user_id) msg_for_this_recipient = self.get_msg_html() if msg_for_this_recipient: frappe.sendmail( @@ -64,9 +60,6 @@ class EmailDigest(Document): reference_name = self.name, unsubscribe_message = _("Unsubscribe from this Email Digest")) - frappe.set_user(original_user) - frappe.set_user_lang(original_user) - def get_msg_html(self): """Build email digest content""" frappe.flags.ignore_account_permission = True From bbe4933a523b737d4537827685de508cae8dac64 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 18 Nov 2020 20:58:59 +0530 Subject: [PATCH 152/154] fix(regional): set proper state code in ewaybill JSON when gst_category is SEZ --- erpnext/regional/india/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 6164e066cd..5458001e9d 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -516,6 +516,9 @@ def get_address_details(data, doc, company_address, billing_address): data.transType = 1 data.actualToStateCode = data.toStateCode shipping_address = billing_address + + if doc.gst_category == 'SEZ': + data.toStateCode = 99 return data From 6dafe1eabfaad5619257ca7062ea568d44fff2af Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 19 Nov 2020 08:12:58 +0530 Subject: [PATCH 153/154] feat: Formula based Quality Inspection (#23916) * feat: Formula based Quality Inspection * chore: Added Test for Formula Based QI reading --- .../item_quality_inspection_parameter.json | 131 +++++++----------- .../quality_inspection/quality_inspection.py | 31 ++++- .../test_quality_inspection.py | 66 +++++++-- .../quality_inspection_reading.json | 38 ++++- .../quality_inspection_template.py | 6 +- 5 files changed, 176 insertions(+), 96 deletions(-) diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index f1e1fd3679..888bc2de47 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -1,88 +1,57 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:28:01", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:28:01", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "specification", + "value", + "column_break_3", + "acceptance_formula" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "specification", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Parameter", - "length": 0, - "no_copy": 0, - "oldfieldname": "specification", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "specification", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Parameter", + "oldfieldname": "specification", + "oldfieldtype": "Data", + "print_width": "200px", + "reqd": 1, "width": "200px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "value", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Acceptance Criteria", - "length": 0, - "no_copy": 0, - "oldfieldname": "value", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "value", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Acceptance Criteria", + "oldfieldname": "value", + "oldfieldtype": "Data" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "description": "Simple Python formula based on numeric Readings.
Example 1: reading_1 > 0.2 and reading_1 < 0.5
\nExample 2: (reading_1 + reading_2) / 2 < 10", + "fieldname": "acceptance_formula", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Acceptance Criteria Formula" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:01.074316", - "modified_by": "Administrator", - "module": "Stock", - "name": "Item Quality Inspection Parameter", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2020-11-16 16:33:42.421842", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item Quality Inspection Parameter", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index c3bb514184..399a63a186 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -4,15 +4,20 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe import _ +from frappe.utils import flt from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \ import get_template_details -from frappe.model.mapper import get_mapped_doc class QualityInspection(Document): def validate(self): if not self.readings and self.item_code: self.get_item_specification_details() + if self.readings: + self.set_status_based_on_acceptance_formula() + def get_item_specification_details(self): if not self.quality_inspection_template: self.quality_inspection_template = frappe.db.get_value('Item', @@ -26,6 +31,7 @@ class QualityInspection(Document): child = self.append('readings', {}) child.specification = d.specification child.value = d.value + child.acceptance_formula = d.acceptance_formula child.status = "Accepted" def get_quality_inspection_template(self): @@ -58,6 +64,29 @@ class QualityInspection(Document): .format(parent_doc=self.reference_type, child_doc=doctype), (quality_inspection, self.modified, self.reference_name, self.item_code)) + def set_status_based_on_acceptance_formula(self): + for reading in self.readings: + if not reading.acceptance_formula: continue + + condition = reading.acceptance_formula + data = {} + for i in range(1, 11): + field = "reading_" + str(i) + data[field] = flt(reading.get(field)) or 0 + + try: + result = frappe.safe_eval(condition, None, data) + reading.status = "Accepted" if result else "Rejected" + except SyntaxError: + frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx), + title=_("Invalid Formula")) + except NameError as e: + field = frappe.bold(e.args[0].split()[1]) + frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.") + .format(reading.idx, field), + title=_("Invalid Formula")) + + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index bb535c1f6a..2c40009426 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -7,6 +7,7 @@ import unittest from frappe.utils import nowdate from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError # test_records = frappe.get_test_records('Quality Inspection') @@ -17,10 +18,12 @@ class TestQualityInspection(unittest.TestCase): frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1) def test_qa_for_delivery(self): + make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + self.assertRaises(QualityInspectionRequiredError, dn.submit) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected", submit=True) + qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected") dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) @@ -28,12 +31,51 @@ class TestQualityInspection(unittest.TestCase): dn.reload() dn.submit() + qa.cancel() + dn.reload() + dn.cancel() + def test_qa_not_submit(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, submit = False) + qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True) dn.items[0].quality_inspection = qa.name self.assertRaises(QualityInspectionNotSubmittedError, dn.submit) + qa.delete() + dn.delete() + + def test_formula_based_qi_readings(self): + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + readings = [{ + "specification": "Iron Content", + "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", + "reading_1": 0.4 + }, + { + "specification": "Calcium Content", + "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", + "reading_1": 0.7 + }, + { + "specification": "Mg Content", + "acceptance_formula": "(reading_1 + reading_2 + reading_3) / 3 < 0.9", + "reading_1": 0.5, + "reading_2": 0.7, + "reading_3": "random text" # check if random string input causes issues + }] + + qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, + readings=readings, do_not_save=True) + qa.save() + + # status must be auto set as per formula + self.assertEqual(qa.readings[0].status, "Accepted") + self.assertEqual(qa.readings[1].status, "Rejected") + self.assertEqual(qa.readings[2].status, "Accepted") + + qa.delete() + dn.delete() + def create_quality_inspection(**args): args = frappe._dict(args) qa = frappe.new_doc("Quality Inspection") @@ -44,12 +86,18 @@ def create_quality_inspection(**args): qa.item_code = args.item_code or "_Test Item with QA" qa.sample_size = 1 qa.inspected_by = frappe.session.user - qa.append("readings", { - "specification": "Size", - "status": args.status - }) - qa.save() - if args.submit: - qa.submit() + + readings = args.readings or {"specification": "Size", "status": args.status} + + if isinstance(readings, list): + for entry in readings: + qa.append("readings", entry) + else: + qa.append("readings", readings) + + if not args.do_not_save: + qa.save() + if not args.do_not_submit: + qa.submit() return qa diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index f9f8a71c02..c1976dd1fb 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -1,22 +1,29 @@ { + "actions": [], "autoname": "hash", "creation": "2013-02-22 01:27:43", "doctype": "DocType", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "specification", "value", + "status", + "column_break_4", + "acceptance_formula", + "section_break_3", "reading_1", "reading_2", "reading_3", + "column_break_10", "reading_4", "reading_5", "reading_6", + "column_break_14", "reading_7", "reading_8", "reading_9", - "reading_10", - "status" + "reading_10" ], "fields": [ { @@ -124,15 +131,40 @@ "oldfieldname": "status", "oldfieldtype": "Select", "options": "Accepted\nRejected" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "description": "Simple Python formula based on numeric Readings.
Example 1: reading_1 > 0.2 and reading_1 < 0.5
\nExample 2: (reading_1 + reading_2) / 2 < 10", + "fieldname": "acceptance_formula", + "fieldtype": "Code", + "label": "Acceptance Criteria Formula" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, - "modified": "2019-07-11 18:48:12.667404", + "links": [], + "modified": "2020-11-16 16:34:29.947856", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", "owner": "Administrator", "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py index 0d9a90312b..e2848469b8 100644 --- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py +++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py @@ -12,5 +12,7 @@ class QualityInspectionTemplate(Document): def get_template_details(template): if not template: return [] - return frappe.get_all('Item Quality Inspection Parameter', fields=["specification", "value"], - filters={'parenttype': 'Quality Inspection Template', 'parent': template}, order_by="idx") \ No newline at end of file + return frappe.get_all('Item Quality Inspection Parameter', + fields=["specification", "value", "acceptance_formula"], + filters={'parenttype': 'Quality Inspection Template', 'parent': template}, + order_by="idx") \ No newline at end of file From 14514f776b7134282c98d40392d89a714ddee68e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 19 Nov 2020 08:17:16 +0530 Subject: [PATCH 154/154] fix: Enable Allow Auto Repeat for standard doctypes having auto_repeat field (#23776) Co-authored-by: Shivam Mishra --- erpnext/accounts/doctype/journal_entry/journal_entry.json | 3 ++- erpnext/accounts/doctype/payment_entry/payment_entry.json | 3 ++- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 3 ++- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 6 ++++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 ++- erpnext/buying/doctype/purchase_order/purchase_order.json | 3 ++- .../doctype/supplier_quotation/supplier_quotation.json | 3 ++- erpnext/selling/doctype/quotation/quotation.json | 3 ++- erpnext/selling/doctype/sales_order/sales_order.json | 3 ++- erpnext/stock/doctype/delivery_note/delivery_note.json | 1 + .../stock/doctype/purchase_receipt/purchase_receipt.json | 3 ++- 11 files changed, 23 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 4573c50134..b7bbb74ce9 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-03-25 10:53:52", @@ -503,7 +504,7 @@ "idx": 176, "is_submittable": 1, "links": [], - "modified": "2020-06-02 18:15:46.955697", + "modified": "2020-10-30 13:56:01.121995", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 72149a665d..2e1f201e25 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2016-06-01 14:38:51.012597", @@ -587,7 +588,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-02 13:39:43.383705", + "modified": "2020-10-30 13:56:20.007336", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 1cff3c661d..5bc57b4a84 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2020-01-24 15:29:29.933693", @@ -1580,7 +1581,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2020-09-28 16:51:24.641755", + "modified": "2020-10-30 13:56:51.056083", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 8925b87b52..2df77a84c7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -1334,7 +1335,8 @@ "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, - "modified": "2020-09-21 12:22:09.164068", + "links": [], + "modified": "2020-10-30 13:57:18.266978", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -1396,4 +1398,4 @@ "timeline_field": "supplier", "title_field": "title", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index ae40153890..17fbe2def9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:05", @@ -1955,7 +1956,7 @@ "idx": 181, "is_submittable": 1, "links": [], - "modified": "2020-10-09 15:59:57.544736", + "modified": "2020-10-30 13:57:45.086303", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 4b865a98e9..71231f68c8 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -1105,7 +1106,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-10-30 11:39:37.388249", + "modified": "2020-10-30 13:58:14.697921", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 9a092ca5c3..b39c989073 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:45", @@ -807,7 +808,7 @@ "idx": 29, "is_submittable": 1, "links": [], - "modified": "2020-10-01 20:56:17.932007", + "modified": "2020-10-30 13:58:33.043971", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 5b85187ccb..3eba62bc19 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:08", @@ -932,7 +933,7 @@ "is_submittable": 1, "links": [], "max_attachments": 1, - "modified": "2020-07-26 17:46:19.951223", + "modified": "2020-10-30 13:58:59.212060", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 77c1787c26..3d64ac3780 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-06-18 12:39:59", @@ -1460,7 +1461,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-10-07 14:30:01.782617", + "modified": "2020-10-30 13:59:18.628077", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 3c5129b1ab..7393c8a70e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:09", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index ce54fc883f..13c8ceb759 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -1109,7 +1110,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-08-03 23:20:26.381024", + "modified": "2020-10-30 14:00:08.347534", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt",
{%= report_columns[0].label %}{%= report_columns[1].label %}