From a21f76f2a1c49889e9e6a3a7b8c7b9bc6567d529 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 21 Jul 2021 20:08:20 +0200 Subject: [PATCH 01/25] feat: add voucher-specific data to datev export --- .../regional/germany/utils/datev/datev_csv.py | 8 + erpnext/regional/report/datev/datev.py | 196 ++++++++++++++++-- 2 files changed, 187 insertions(+), 17 deletions(-) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index 122c15fd811..c5c2bc41f4d 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -33,6 +33,14 @@ def get_datev_csv(data, filters, csv_class): if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS: result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) + result['Beleginfo - Inhalt 6'] = pd.to_datetime(result['Beleginfo - Inhalt 6']) + result['Beleginfo - Inhalt 6'] = result['Beleginfo - Inhalt 6'].dt.strftime('%d%m%Y') + + result['Fälligkeit'] = pd.to_datetime(result['Fälligkeit']) + result['Fälligkeit'] = result['Fälligkeit'].dt.strftime('%d%m%y') + + result.sort_values(by='Belegdatum', inplace=True, kind='stable', ignore_index=True) + if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES: result['Sprach-ID'] = 'de-DE' diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index a5ca7eee5d4..5df24c2bb35 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -43,6 +43,12 @@ COLUMNS = [ "fieldtype": "Data", "width": 100 }, + { + "label": "BU-Schlüssel", + "fieldname": "BU-Schlüssel", + "fieldtype": "Data", + "width": 100 + }, { "label": "Belegdatum", "fieldname": "Belegdatum", @@ -114,6 +120,36 @@ COLUMNS = [ "fieldname": "Beleginfo - Inhalt 4", "fieldtype": "Data", "width": 150 + }, + { + "label": "Beleginfo - Art 5", + "fieldname": "Beleginfo - Art 5", + "fieldtype": "Data", + "width": 150 + }, + { + "label": "Beleginfo - Inhalt 5", + "fieldname": "Beleginfo - Inhalt 5", + "fieldtype": "Data", + "width": 100 + }, + { + "label": "Beleginfo - Art 6", + "fieldname": "Beleginfo - Art 6", + "fieldtype": "Data", + "width": 150 + }, + { + "label": "Beleginfo - Inhalt 6", + "fieldname": "Beleginfo - Inhalt 6", + "fieldtype": "Date", + "width": 100 + }, + { + "label": "Fälligkeit", + "fieldname": "Fälligkeit", + "fieldtype": "Date", + "width": 100 } ] @@ -161,6 +197,126 @@ def validate_fiscal_year(from_date, to_date, company): def get_transactions(filters, as_dict=1): + def run(params_method, filters): + extra_fields, extra_joins, extra_filters = params_method(filters) + return run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=as_dict) + + type_map = { + # specific query methods for some voucher types + "Payment Entry": get_payment_entry_params, + "Sales Invoice": get_sales_invoice_params, + "Purchase Invoice": get_purchase_invoice_params + } + + only_voucher_type = filters.get("voucher_type") + transactions = [] + + for voucher_type, get_voucher_params in type_map.items(): + if only_voucher_type and only_voucher_type != voucher_type: + continue + + transactions.extend(run(params_method=get_voucher_params, filters=filters)) + + if not only_voucher_type or only_voucher_type not in type_map: + # generic query method for all other voucher types + filters["exclude_voucher_types"] = type_map.keys() + transactions.extend(run(params_method=get_generic_params, filters=filters)) + + if as_dict: + sort_by = lambda row: row["Belegdatum"] + else: + sort_by = lambda row: row[5] + + return sorted(transactions, key=sort_by) + + +def get_payment_entry_params(filters): + extra_fields = """ + , 'Zahlungsreferenz' as 'Beleginfo - Art 5' + , pe.reference_no as 'Beleginfo - Inhalt 5' + , 'Buchungstag' as 'Beleginfo - Art 6' + , pe.reference_date as 'Beleginfo - Inhalt 6' + , '' as 'Fälligkeit' + """ + + extra_joins = """ + LEFT JOIN `tabPayment Entry` pe + ON gl.voucher_no = pe.name + """ + + extra_filters = """ + AND gl.voucher_type = 'Payment Entry' + """ + + return extra_fields, extra_joins, extra_filters + + +def get_sales_invoice_params(filters): + extra_fields = """ + , '' as 'Beleginfo - Art 5' + , '' as 'Beleginfo - Inhalt 5' + , '' as 'Beleginfo - Art 6' + , '' as 'Beleginfo - Inhalt 6' + , si.due_date as 'Fälligkeit' + """ + + extra_joins = """ + LEFT JOIN `tabSales Invoice` si + ON gl.voucher_no = si.name + """ + + extra_filters = """ + AND gl.voucher_type = 'Sales Invoice' + """ + + return extra_fields, extra_joins, extra_filters + + +def get_purchase_invoice_params(filters): + extra_fields = """ + , 'Lieferanten-Rechnungsnummer' as 'Beleginfo - Art 5' + , pi.bill_no as 'Beleginfo - Inhalt 5' + , 'Lieferanten-Rechnungsdatum' as 'Beleginfo - Art 6' + , pi.bill_date as 'Beleginfo - Inhalt 6' + , pi.due_date as 'Fälligkeit' + """ + + extra_joins = """ + LEFT JOIN `tabPurchase Invoice` pi + ON gl.voucher_no = pi.name + """ + + extra_filters = """ + AND gl.voucher_type = 'Purchase Invoice' + """ + + return extra_fields, extra_joins, extra_filters + + +def get_generic_params(filters): + # produce empty fields so all rows will have the same length + extra_fields = """ + , '' as 'Beleginfo - Art 5' + , '' as 'Beleginfo - Inhalt 5' + , '' as 'Beleginfo - Art 6' + , '' as 'Beleginfo - Inhalt 6' + , '' as 'Fälligkeit' + """ + extra_joins = "" + + if filters.get("exclude_voucher_types"): + # exclude voucher types that are queried by a dedicated method + exclude = "({})".format(', '.join("'{}'".format(key) for key in filters.get("exclude_voucher_types"))) + extra_filters = "AND gl.voucher_type NOT IN {}".format(exclude) + + # if voucher type filter is set, allow only this type + if filters.get("voucher_type"): + extra_filters += " AND gl.voucher_type = %(voucher_type)s" + + return extra_fields, extra_joins, extra_filters + + +def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1): """ Get a list of accounting entries. @@ -171,8 +327,7 @@ def get_transactions(filters, as_dict=1): filters -- dict of filters to be passed to the sql query as_dict -- return as list of dicts [0,1] """ - filter_by_voucher = 'AND gl.voucher_type = %(voucher_type)s' if filters.get('voucher_type') else '' - gl_entries = frappe.db.sql(""" + query = """ SELECT /* either debit or credit amount; always positive */ @@ -187,6 +342,9 @@ def get_transactions(filters, as_dict=1): /* against number or, if empty, party against number */ %(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)', + /* disable automatic VAT deduction */ + '40' as 'BU-Schlüssel', + gl.posting_date as 'Belegdatum', gl.voucher_no as 'Belegfeld 1', LEFT(gl.remarks, 60) as 'Buchungstext', @@ -199,30 +357,34 @@ def get_transactions(filters, as_dict=1): case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4', par.debtor_creditor_number as 'Beleginfo - Inhalt 4' + {extra_fields} + FROM `tabGL Entry` gl /* Kontonummer */ - left join `tabAccount` acc - on gl.account = acc.name + LEFT JOIN `tabAccount` acc + ON gl.account = acc.name - left join `tabCustomer` cus - on gl.party_type = 'Customer' - and gl.party = cus.name + LEFT JOIN `tabParty Account` par + ON par.parent = gl.party + AND par.parenttype = gl.party_type + AND par.company = %(company)s - left join `tabSupplier` sup - on gl.party_type = 'Supplier' - and gl.party = sup.name - - left join `tabParty Account` par - on par.parent = gl.party - and par.parenttype = gl.party_type - and par.company = %(company)s + {extra_joins} WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s - {} - ORDER BY 'Belegdatum', gl.voucher_no""".format(filter_by_voucher), filters, as_dict=as_dict) + + {extra_filters} + + ORDER BY 'Belegdatum', gl.voucher_no""".format( + extra_fields=extra_fields, + extra_joins=extra_joins, + extra_filters=extra_filters + ) + + gl_entries = frappe.db.sql(query, filters, as_dict=as_dict) return gl_entries From c6c2773e02a5b3f6d5a94079025bd642f8196127 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 3 Aug 2021 11:22:42 +0200 Subject: [PATCH 02/25] refactor: def instead of lambda --- erpnext/regional/report/datev/datev.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 5df24c2bb35..5dd71ea794a 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -200,6 +200,10 @@ def get_transactions(filters, as_dict=1): def run(params_method, filters): extra_fields, extra_joins, extra_filters = params_method(filters) return run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=as_dict) + + def sort_by(row): + # "Belegdatum" is in the fifth column when list format is used + return row["Belegdatum" if as_dict else 5] type_map = { # specific query methods for some voucher types @@ -222,11 +226,6 @@ def get_transactions(filters, as_dict=1): filters["exclude_voucher_types"] = type_map.keys() transactions.extend(run(params_method=get_generic_params, filters=filters)) - if as_dict: - sort_by = lambda row: row["Belegdatum"] - else: - sort_by = lambda row: row[5] - return sorted(transactions, key=sort_by) From 38898d33c6f88a6397091b2a51745f0f33dcc0bd Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 20 Aug 2021 12:24:13 +0530 Subject: [PATCH 03/25] fix: update scrap table item details; typo --- erpnext/manufacturing/doctype/bom/bom.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 0ba85078ead..eb1dfc8cae8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -148,6 +148,7 @@ class BOM(WebsiteGenerator): self.set_plc_conversion_rate() self.validate_uom_is_interger() self.set_bom_material_details() + self.set_bom_scrap_items_detail() self.validate_materials() self.set_routing_operations() self.validate_operations() @@ -200,7 +201,7 @@ class BOM(WebsiteGenerator): def set_bom_material_details(self): for item in self.get("items"): - self.validate_bom_currecny(item) + self.validate_bom_currency(item) ret = self.get_bom_material_detail({ "company": self.company, @@ -219,6 +220,19 @@ class BOM(WebsiteGenerator): if not item.get(r): item.set(r, ret[r]) + def set_bom_scrap_items_detail(self): + for item in self.get("scrap_items"): + args = { + "item_code": item.item_code, + "company": self.company, + "scrap_items": True, + "bom_no": '', + } + ret = self.get_bom_material_detail(args) + for key, value in ret.items(): + if not item.get(key): + item.set(key, value) + @frappe.whitelist() def get_bom_material_detail(self, args=None): """ Get raw material details like uom, desc and rate""" @@ -255,7 +269,7 @@ class BOM(WebsiteGenerator): return ret_item - def validate_bom_currecny(self, item): + def validate_bom_currency(self, item): if item.get('bom_no') and frappe.db.get_value('BOM', item.get('bom_no'), 'currency') != self.currency: frappe.throw(_("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}") .format(item.idx, item.bom_no, self.currency)) From c7508a034abf20b60176eeb3b23f54bf5dbd0307 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 24 Aug 2021 20:51:30 +0530 Subject: [PATCH 04/25] feat: allow draft pos invoices even if no stock available (#27078) (#27106) (cherry picked from commit f47cbae5e07ba7f0e48e5fe11b807632f206bd45) Co-authored-by: Saqib Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 759cad53d4a..034a217a26d 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -138,7 +138,7 @@ class POSInvoice(SalesInvoice): .format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable")) def validate_stock_availablility(self): - if self.is_return: + if self.is_return or self.docstatus != 1: return allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') From 8474961b798faee1f5033f62bfe4efacf290ac33 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 24 Aug 2021 21:51:53 +0530 Subject: [PATCH 05/25] perf: reduce number of queries to validate selling price (#26225) (#27119) * perf: reduce number of queries to validate selling price * fix: improved flow and formatting * fix: improve condition and use of `as_dict` Co-authored-by: Sagar Vora (cherry picked from commit 7c957d72b362fbe9f74b5bee7409c7fb0fa33773) Co-authored-by: Pruthvi Patel --- erpnext/controllers/selling_controller.py | 112 ++++++++++++++++------ 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index da2765deded..fc2cc97e0a5 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime -from frappe import _, throw +from frappe import _, bold, throw from erpnext.stock.get_item_details import get_bin_details from erpnext.stock.utils import get_incoming_rate from erpnext.stock.get_item_details import get_conversion_factor @@ -16,7 +16,6 @@ from erpnext.controllers.stock_controller import StockController from erpnext.controllers.sales_and_purchase_return import get_rate_for_return class SellingController(StockController): - def get_feed(self): return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total) @@ -169,39 +168,96 @@ class SellingController(StockController): def validate_selling_price(self): def throw_message(idx, item_name, rate, ref_rate_field): - bold_net_rate = frappe.bold("net rate") - msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""") - .format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate))) - msg += "

" - msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""") - .format(get_link_to_form("Selling Settings", "Selling Settings"))) - frappe.throw(msg, title=_("Invalid Selling Price")) + throw(_("""Row #{0}: Selling rate for item {1} is lower than its {2}. + Selling {3} should be atleast {4}.

Alternatively, + you can disable selling price validation in {5} to bypass + this validation.""").format( + idx, + bold(item_name), + bold(ref_rate_field), + bold("net rate"), + bold(rate), + get_link_to_form("Selling Settings", "Selling Settings"), + ), title=_("Invalid Selling Price")) - if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): - return - if hasattr(self, "is_return") and self.is_return: + if ( + self.get("is_return") + or not frappe.db.get_single_value("Selling Settings", "validate_selling_price") + ): return - for it in self.get("items"): - if not it.item_code: + is_internal_customer = self.get('is_internal_customer') + valuation_rate_map = {} + + for item in self.items: + if not item.item_code: continue - last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) - last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1) - if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom): - throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate") + last_purchase_rate, is_stock_item = frappe.get_cached_value( + "Item", item.item_code, ("last_purchase_rate", "is_stock_item") + ) - last_valuation_rate = frappe.db.sql(""" - SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s - AND warehouse = %s AND valuation_rate > 0 - ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1 - """, (it.item_code, it.warehouse)) - if last_valuation_rate: - last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1) - if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \ - and not self.get('is_internal_customer'): - throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate") + last_purchase_rate_in_sales_uom = ( + last_purchase_rate * (item.conversion_factor or 1) + ) + if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom): + throw_message( + item.idx, + item.item_name, + last_purchase_rate_in_sales_uom, + "last purchase rate" + ) + + if is_internal_customer or not is_stock_item: + continue + + valuation_rate_map[(item.item_code, item.warehouse)] = None + + if not valuation_rate_map: + return + + or_conditions = ( + f"""(item_code = {frappe.db.escape(valuation_rate[0])} + and warehouse = {frappe.db.escape(valuation_rate[1])})""" + for valuation_rate in valuation_rate_map + ) + + valuation_rates = frappe.db.sql(f""" + select + item_code, warehouse, valuation_rate + from + `tabBin` + where + ({" or ".join(or_conditions)}) + and valuation_rate > 0 + """, as_dict=True) + + for rate in valuation_rates: + valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate + + for item in self.items: + if not item.item_code: + continue + + last_valuation_rate = valuation_rate_map.get( + (item.item_code, item.warehouse) + ) + + if not last_valuation_rate: + continue + + last_valuation_rate_in_sales_uom = ( + last_valuation_rate * (item.conversion_factor or 1) + ) + + if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom): + throw_message( + item.idx, + item.item_name, + last_valuation_rate_in_sales_uom, + "valuation rate" + ) def get_item_list(self): il = [] From 74073ddc857cd228752579ecff7d3b6b359ada3f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 24 Aug 2021 21:56:49 +0530 Subject: [PATCH 06/25] fix: Ignore due date validations if payment terms are copied from orders/receipts (#27120) --- erpnext/controllers/accounts_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 219da37a687..b17d1868d99 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -164,7 +164,8 @@ class AccountsController(TransactionBase): self.set_due_date() self.set_payment_schedule() self.validate_payment_schedule_amount() - self.validate_due_date() + if not self.get('ignore_default_payment_terms_template'): + self.validate_due_date() self.validate_advance_entries() def validate_non_invoice_documents_schedule(self): From 82201175003a27679e839722bd9874ff092e02e9 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 24 Aug 2021 22:02:05 +0530 Subject: [PATCH 07/25] feat(regional): South Africa VAT Audit Report (#27017) * feat: SA VAT Report * fix: added party column and fixed permissions Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> Co-authored-by: Nabin Hait --- .../south_africa_vat_account/__init__.py | 0 .../south_africa_vat_account.json | 34 +++ .../south_africa_vat_account.py | 8 + erpnext/patches.txt | 1 + .../add_custom_field_for_south_africa.py | 14 + .../south_africa_vat_settings/__init__.py | 0 .../south_africa_vat_settings.js | 23 ++ .../south_africa_vat_settings.json | 76 +++++ .../south_africa_vat_settings.py | 8 + .../test_south_africa_vat_settings.py | 8 + .../report/vat_audit_report/__init__.py | 0 .../vat_audit_report/vat_audit_report.js | 31 ++ .../vat_audit_report/vat_audit_report.json | 22 ++ .../vat_audit_report/vat_audit_report.py | 269 ++++++++++++++++++ erpnext/regional/south_africa/__init__.py | 0 erpnext/regional/south_africa/setup.py | 50 ++++ 16 files changed, 544 insertions(+) create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/__init__.py create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py create mode 100644 erpnext/patches/v13_0/add_custom_field_for_south_africa.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/__init__.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py create mode 100644 erpnext/regional/report/vat_audit_report/__init__.py create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.js create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.json create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.py create mode 100644 erpnext/regional/south_africa/__init__.py create mode 100644 erpnext/regional/south_africa/setup.py diff --git a/erpnext/accounts/doctype/south_africa_vat_account/__init__.py b/erpnext/accounts/doctype/south_africa_vat_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json new file mode 100644 index 00000000000..fa1aa7da594 --- /dev/null +++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json @@ -0,0 +1,34 @@ +{ + "actions": [], + "autoname": "account", + "creation": "2021-07-08 22:04:24.634967", + "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": "2021-07-08 22:35:33.202911", + "modified_by": "Administrator", + "module": "Accounts", + "name": "South Africa VAT Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py new file mode 100644 index 00000000000..4bd8c65a046 --- /dev/null +++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SouthAfricaVATAccount(Document): + pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 733208e8649..86bd65a82ea 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -301,4 +301,5 @@ erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16 erpnext.patches.v13_0.update_tds_check_field #3 erpnext.patches.v13_0.update_recipient_email_digest erpnext.patches.v13_0.shopify_deprecation_warning +erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.rename_discharge_ordered_date_in_ip_record diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py new file mode 100644 index 00000000000..73ff1cad5b6 --- /dev/null +++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py @@ -0,0 +1,14 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from erpnext.regional.south_africa.setup import make_custom_fields, add_permissions + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'South Africa'}) + if not company: + return + + make_custom_fields() + add_permissions() diff --git a/erpnext/regional/doctype/south_africa_vat_settings/__init__.py b/erpnext/regional/doctype/south_africa_vat_settings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js new file mode 100644 index 00000000000..e37a61ac853 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js @@ -0,0 +1,23 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('South Africa VAT Settings', { + refresh: function(frm) { + frm.set_query("company", function() { + return { + filters: { + country: "South Africa", + } + }; + }); + frm.set_query("account", "vat_accounts", function() { + return { + filters: { + company: frm.doc.company, + account_type: "Tax", + is_group: 0 + } + }; + }); + } +}); diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json new file mode 100644 index 00000000000..8a51829c419 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json @@ -0,0 +1,76 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-08 22:34:33.668015", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "vat_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "vat_accounts", + "fieldtype": "Table", + "label": "VAT Accounts", + "options": "South Africa VAT Account", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-14 02:17:52.476762", + "modified_by": "Administrator", + "module": "Regional", + "name": "South Africa VAT Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Auditor", + "share": 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/south_africa_vat_settings/south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py new file mode 100644 index 00000000000..d74154bfe78 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SouthAfricaVATSettings(Document): + pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py new file mode 100644 index 00000000000..1c36652ad6e --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestSouthAfricaVATSettings(unittest.TestCase): + pass diff --git a/erpnext/regional/report/vat_audit_report/__init__.py b/erpnext/regional/report/vat_audit_report/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.js b/erpnext/regional/report/vat_audit_report/vat_audit_report.js new file mode 100644 index 00000000000..39ef9b563ac --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.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["VAT Audit Report"] = { + "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(), -2), + "width": "80" + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ] +}; diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json new file mode 100644 index 00000000000..a8be7bf64c0 --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-09 11:07:43.473518", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-09 11:07:43.473518", + "modified_by": "Administrator", + "module": "Regional", + "name": "VAT Audit Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "VAT Audit Report", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py new file mode 100644 index 00000000000..292605ef13d --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -0,0 +1,269 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from frappe import _ +from frappe.utils import formatdate + +def execute(filters=None): + return VATAuditReport(filters).run() + +class VATAuditReport(object): + + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + self.columns = [] + self.data = [] + self.doctypes = ["Purchase Invoice", "Sales Invoice"] + + def run(self): + self.get_sa_vat_accounts() + self.get_columns() + for doctype in self.doctypes: + self.select_columns = """ + name as voucher_no, + posting_date, remarks""" + columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \ + else ", customer as party, debit_to as account" + self.select_columns += columns + + self.get_invoice_data(doctype) + + if self.invoices: + self.get_invoice_items(doctype) + self.get_items_based_on_tax_rate(doctype) + self.get_data(doctype) + + return self.columns, self.data + + def get_sa_vat_accounts(self): + self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", + filters = {"parent": self.filters.company}, pluck="account") + if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: + frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) + + def get_invoice_data(self, doctype): + conditions = self.get_conditions() + self.invoices = frappe._dict() + + invoice_data = frappe.db.sql(""" + SELECT + {select_columns} + FROM + `tab{doctype}` + WHERE + docstatus = 1 {where_conditions} + and is_opening = "No" + ORDER BY + posting_date DESC + """.format(select_columns=self.select_columns, doctype=doctype, + where_conditions=conditions), self.filters, as_dict=1) + + for d in invoice_data: + self.invoices.setdefault(d.voucher_no, d) + + def get_invoice_items(self, doctype): + self.invoice_items = frappe._dict() + + items = frappe.db.sql(""" + SELECT + item_code, parent, taxable_value, base_net_amount, is_zero_rated + FROM + `tab%s Item` + WHERE + parent in (%s) + """ % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1) + for d in items: + if d.item_code not in self.invoice_items.get(d.parent, {}): + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, { + 'net_amount': 0.0}) + self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) + self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated + + def get_items_based_on_tax_rate(self, doctype): + self.items_based_on_tax_rate = frappe._dict() + self.item_tax_rate = frappe._dict() + self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ + else "Sales Taxes and Charges" + + self.tax_details = frappe.db.sql(""" + SELECT + parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount + FROM + `tab%s` + WHERE + parenttype = %s and docstatus = 1 + and parent in (%s) + ORDER BY + account_head + """ % (self.tax_doctype, "%s", ", ".join(["%s"]*len(self.invoices.keys()))), + tuple([doctype] + list(self.invoices.keys()))) + + for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: + if item_wise_tax_detail: + try: + if account in self.sa_vat_accounts: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + else: + continue + for item_code, taxes in item_wise_tax_detail.items(): + is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated") + #to skip items with non-zero tax rate in multiple rows + if taxes[0] == 0 and not is_zero_rated: + continue + tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes) + + if tax_rate is not None: + rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}) \ + .setdefault(tax_rate, []) + if item_code not in rate_based_dict: + rate_based_dict.append(item_code) + except ValueError: + continue + + def get_item_amount_map(self, parent, item_code, taxes): + net_amount = self.invoice_items.get(parent).get(item_code).get("net_amount") + tax_rate = taxes[0] + tax_amount = taxes[1] + gross_amount = net_amount + tax_amount + item_amount_map = self.item_tax_rate.setdefault(parent, {}) \ + .setdefault(item_code, []) + amount_dict = { + "tax_rate": tax_rate, + "gross_amount": gross_amount, + "tax_amount": tax_amount, + "net_amount": net_amount + } + item_amount_map.append(amount_dict) + + return tax_rate, item_amount_map + + def get_conditions(self): + 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 self.filters.get(opts[0]): + conditions += opts[1] + + return conditions + + def get_data(self, doctype): + consolidated_data = self.get_consolidated_data(doctype) + section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales") + + for rate, section in consolidated_data.items(): + rate = int(rate) + label = frappe.bold(section_name + "- " + "Rate" + " " + str(rate) + "%") + section_head = {"posting_date": label} + total_gross = total_tax = total_net = 0 + self.data.append(section_head) + for row in section.get("data"): + self.data.append(row) + total_gross += row["gross_amount"] + total_tax += row["tax_amount"] + total_net += row["net_amount"] + + total = { + "posting_date": frappe.bold(_("Total")), + "gross_amount": total_gross, + "tax_amount": total_tax, + "net_amount": total_net, + "bold":1 + } + self.data.append(total) + self.data.append({}) + + def get_consolidated_data(self, doctype): + consolidated_data_map={} + for inv, inv_data in self.invoices.items(): + if self.items_based_on_tax_rate.get(inv): + for rate, items in self.items_based_on_tax_rate.get(inv).items(): + consolidated_data_map.setdefault(rate, {"data": []}) + for item in items: + row = {} + item_details = self.item_tax_rate.get(inv).get(item) + row["account"] = inv_data.get("account") + row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy") + row["voucher_type"] = doctype + row["voucher_no"] = inv + row["party_type"] = "Customer" if doctype == "Sales Invoice" else "Supplier" + row["party"] = inv_data.get("party") + row["remarks"] = inv_data.get("remarks") + row["gross_amount"]= item_details[0].get("gross_amount") + row["tax_amount"]= item_details[0].get("tax_amount") + row["net_amount"]= item_details[0].get("net_amount") + consolidated_data_map[rate]["data"].append(row) + + return consolidated_data_map + + def get_columns(self): + self.columns = [ + { + "fieldname": "posting_date", + "label": "Posting Date", + "fieldtype": "Data", + "width": 200 + }, + { + "fieldname": "account", + "label": "Account", + "fieldtype": "Link", + "options": "Account", + "width": 150 + }, + { + "fieldname": "voucher_type", + "label": "Voucher Type", + "fieldtype": "Data", + "width": 140, + "hidden": 1 + }, + { + "fieldname": "voucher_no", + "label": "Reference", + "fieldtype": "Dynamic Link", + "options": "voucher_type", + "width": 150 + }, + { + "fieldname": "party_type", + "label": "Party Type", + "fieldtype": "Data", + "width": 140, + "hidden": 1 + }, + { + "fieldname": "party", + "label": "Party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "width": 150 + }, + { + "fieldname": "remarks", + "label": "Details", + "fieldtype": "Data", + "width": 150 + }, + { + "fieldname": "net_amount", + "label": "Net Amount", + "fieldtype": "Currency", + "width": 130 + }, + { + "fieldname": "tax_amount", + "label": "Tax Amount", + "fieldtype": "Currency", + "width": 130 + }, + { + "fieldname": "gross_amount", + "label": "Gross Amount", + "fieldtype": "Currency", + "width": 130 + }, + ] diff --git a/erpnext/regional/south_africa/__init__.py b/erpnext/regional/south_africa/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py new file mode 100644 index 00000000000..4657ff833dd --- /dev/null +++ b/erpnext/regional/south_africa/setup.py @@ -0,0 +1,50 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.permissions import add_permission, update_permission_property + +def setup(company=None, patch=True): + make_custom_fields() + add_permissions() + +def make_custom_fields(update=True): + 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) + custom_fields = { + 'Item': [ + dict(fieldname='is_zero_rated', label='Is Zero Rated', + fieldtype='Check', insert_after='item_group', + print_hide=1) + ], + 'Sales Invoice Item': is_zero_rated, + 'Purchase Invoice Item': is_zero_rated + } + + create_custom_fields(custom_fields, update=update) + +def add_permissions(): + """Add Permissions for South Africa VAT Settings and South Africa VAT Account + and VAT Audit Report""" + for doctype in ('South Africa VAT Settings', 'South Africa 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) + + + if not frappe.db.get_value('Custom Role', dict(report="VAT Audit Report")): + frappe.get_doc(dict( + doctype='Custom Role', + report="VAT Audit Report", + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager'), + dict(role='Auditor') + ] + )).insert() \ No newline at end of file From b3ffa0eb57a5c27ab7af59d8f9977e6219b65e04 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 24 Aug 2021 22:28:07 +0530 Subject: [PATCH 08/25] fix(minor): Update GSTR-1 json version (#27074) (#27121) (cherry picked from commit c30fb04e960c944cda3ea84865f2563bc50b29ed) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/regional/report/gstr_1/gstr_1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 4b7309440ce..9d4f9206f50 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -588,7 +588,7 @@ def get_json(filters, report_name, data): fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) - gst_json = {"version": "GST2.2.9", + gst_json = {"version": "GST3.0.4", "hash": "hash", "gstin": gstin, "fp": fp} res = {} @@ -765,7 +765,7 @@ def get_cdnr_reg_json(res, gstin): "ntty": invoice[0]["document_type"], "pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]), "rchrg": invoice[0]["reverse_charge"], - "inv_type": get_invoice_type_for_cdnr(invoice[0]) + "inv_typ": get_invoice_type_for_cdnr(invoice[0]) } inv_item["itms"] = [] From f13ae4de0b52eb2e853f6c3e86000830a656f74d Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 24 Aug 2021 22:28:29 +0530 Subject: [PATCH 09/25] fix: broken URL in supplier portal (#26823) (#27122) * fix: broken URL The quotations are supplier quotations, not sales quotation. * fix: remove erpnext from path (cherry picked from commit c7bad657b1f73aa2d1232b416752128afc5f77e8) Co-authored-by: Dany Robert --- erpnext/templates/includes/rfq/rfq_items.html | 2 +- erpnext/templates/pages/rfq.html | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/templates/includes/rfq/rfq_items.html b/erpnext/templates/includes/rfq/rfq_items.html index caa15f386b0..04cf922664b 100644 --- a/erpnext/templates/includes/rfq/rfq_items.html +++ b/erpnext/templates/includes/rfq/rfq_items.html @@ -1,4 +1,4 @@ -{% from "erpnext/templates/includes/rfq/rfq_macros.html" import item_name_and_description %} +{% from "templates/includes/rfq/rfq_macros.html" import item_name_and_description %} {% for d in doc.items %}
diff --git a/erpnext/templates/pages/rfq.html b/erpnext/templates/pages/rfq.html index 6e2edb6391b..6516482c230 100644 --- a/erpnext/templates/pages/rfq.html +++ b/erpnext/templates/pages/rfq.html @@ -86,7 +86,7 @@ {{d.transaction_date}}
- Link + Link {% endfor %} @@ -95,6 +95,4 @@ - - {% endblock %} From 5b411dc1f624de8f68f3ffe643ac087e560d7c0d Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 24 Aug 2021 23:18:35 +0530 Subject: [PATCH 10/25] fix: Updated timestamp for pos invoice json (#27110) (#27123) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> (cherry picked from commit fbc59772483cbe17c8ec58cbe806b38d01486ac6) Co-authored-by: Subin Tom <36098155+nemesis189@users.noreply.github.com> --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index b8195374002..19c6c8f3472 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1564,7 +1564,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2021-08-18 16:13:52.080543", + "modified": "2021-08-24 18:19:20.728433", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", From 2f71b740fd59ece329c039dbcf3e846c228329b3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 23 Aug 2021 14:27:55 +0530 Subject: [PATCH 11/25] fix: selected batch no changed on updation of qty --- erpnext/selling/sales_common.js | 4 ++++ erpnext/stock/get_item_details.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 04285735abd..26ac630a32b 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -390,6 +390,10 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }, _set_batch_number: function(doc) { + if (doc.batch_no) { + return + } + let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)}; if (doc.has_serial_no && doc.serial_no) { args['serial_no'] = doc.serial_no diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index a0fbcecc5de..c72073c6143 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -278,6 +278,10 @@ def get_basic_details(args, item, overwrite_warehouse=True): else: args.uom = item.stock_uom + if (args.get("batch_no") and + item.name != frappe.get_cached_value('Batch', args.get("batch_no"), 'item')): + args['batch_no'] = '' + out = frappe._dict({ "item_code": item.name, "item_name": item.item_name, From 1810b73113e49bb0284c7fa98b73d36e91164b0e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 23 Aug 2021 13:36:13 +0530 Subject: [PATCH 12/25] fix: stock ledger report not working if include uom selected in filter --- erpnext/stock/report/stock_ledger/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 8909f217f49..b6923e97c4f 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -23,6 +23,7 @@ def execute(filters=None): conversion_factors = [] if opening_row: data.append(opening_row) + conversion_factors.append(0) actual_qty = stock_value = 0 From fcb17f047dcea8fa44551d121ee38943f4706142 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 12:49:04 +0530 Subject: [PATCH 13/25] fix: validate party and party type only if both available (#27002) (#27133) * fix: validate party and party type only if both available * fix: indentation (cherry picked from commit 8366b6322e5fe9aae5a7c2d20f4061fd07f197fc) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- .../accounts/report/general_ledger/general_ledger.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 5d8d49d6a65..3723c8e0d23 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -78,13 +78,10 @@ def validate_filters(filters, account_details): def validate_party(filters): party_type, party = filters.get("party_type"), filters.get("party") - if party: - if not party_type: - frappe.throw(_("To filter based on Party, select Party Type first")) - else: - for d in party: - if not frappe.db.exists(party_type, d): - frappe.throw(_("Invalid {0}: {1}").format(party_type, d)) + if party and party_type: + for d in party: + if not frappe.db.exists(party_type, d): + frappe.throw(_("Invalid {0}: {1}").format(party_type, d)) def set_account_currency(filters): if filters.get("account") or (filters.get('party') and len(filters.party) == 1): From 4eb7c2a011f3abbe547c2a32978e7af6e1fd6d33 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 25 Aug 2021 16:54:45 +0530 Subject: [PATCH 14/25] fix: TDS calculation on net total (#27058) --- .../tax_withholding_category.py | 9 +++--- .../test_tax_withholding_category.py | 32 ++++++++++++++++++- 2 files changed, 36 insertions(+), 5 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 1536a237dec..0cb872c4b81 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -240,14 +240,15 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): tds_amount = 0 invoice_filters = { - 'name': ('in', vouchers), - 'docstatus': 1 + 'name': ('in', vouchers), + 'docstatus': 1, + 'apply_tds': 1 } field = 'sum(net_total)' - if not cint(tax_details.consider_party_ledger_amount): - invoice_filters.update({'apply_tds': 1}) + if cint(tax_details.consider_party_ledger_amount): + invoice_filters.pop('apply_tds', None) field = 'sum(grand_total)' supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.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 1c687e5cb15..0f921db678d 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 @@ -145,6 +145,36 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_tds_calculation_on_net_total(self): + frappe.db.set_value("Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS") + invoices = [] + + pi = create_purchase_invoice(supplier = "Test TDS Supplier4", rate = 20000, do_not_save=True) + pi.append('taxes', { + "category": "Total", + "charge_type": "Actual", + "account_head": '_Test Account VAT - _TC', + "cost_center": 'Main - _TC', + "tax_amount": 1000, + "description": "Test", + "add_deduct_tax": "Add" + + }) + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier4", rate = 20000) + pi1.submit() + invoices.append(pi1) + + self.assertEqual(pi1.taxes[0].tax_amount, 4000) + + #delete invoices to avoid clashing + for d in invoices: + d.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all("Purchase Invoice", { 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']], @@ -220,7 +250,7 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']: if frappe.db.exists('Supplier', name): continue From 7ac4916191c3b038ebd67f18c2f96dfc4f4ba112 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 16:59:03 +0530 Subject: [PATCH 15/25] feat: unreconcile on cancellation of bank transaction (#27109) (#27137) --- .../bank_transaction/bank_transaction.py | 31 +++++++++++++------ .../bank_transaction/bank_transaction_list.js | 6 ++-- .../bank_transaction/test_bank_transaction.py | 9 +++++- erpnext/controllers/status_updater.py | 3 +- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 31cfb2da1da..0544a469d60 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -21,6 +21,10 @@ class BankTransaction(StatusUpdater): self.update_allocations() self.clear_linked_payment_entries() self.set_status(update=True) + + def on_cancel(self): + self.clear_linked_payment_entries(for_cancel=True) + self.set_status(update=True) def update_allocations(self): if self.payment_entries: @@ -41,21 +45,30 @@ class BankTransaction(StatusUpdater): frappe.db.set_value(self.doctype, self.name, "status", "Reconciled") self.reload() - - def clear_linked_payment_entries(self): + + def clear_linked_payment_entries(self, for_cancel=False): for payment_entry in self.payment_entries: if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: - self.clear_simple_entry(payment_entry) + self.clear_simple_entry(payment_entry, for_cancel=for_cancel) elif payment_entry.payment_document == "Sales Invoice": - self.clear_sales_invoice(payment_entry) + self.clear_sales_invoice(payment_entry, for_cancel=for_cancel) - def clear_simple_entry(self, payment_entry): - frappe.db.set_value(payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", self.date) + def clear_simple_entry(self, payment_entry, for_cancel=False): + clearance_date = self.date if not for_cancel else None + frappe.db.set_value( + payment_entry.payment_document, payment_entry.payment_entry, + "clearance_date", clearance_date) - def clear_sales_invoice(self, payment_entry): - frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.payment_document, - parent=payment_entry.payment_entry), "clearance_date", self.date) + def clear_sales_invoice(self, payment_entry, for_cancel=False): + clearance_date = self.date if not for_cancel else None + frappe.db.set_value( + "Sales Invoice Payment", + dict( + parenttype=payment_entry.payment_document, + parent=payment_entry.payment_entry + ), + "clearance_date", clearance_date) def get_total_allocated_amount(payment_entry): return frappe.db.sql(""" diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js index bff41d5539b..2585ee9c923 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js @@ -4,10 +4,12 @@ frappe.listview_settings['Bank Transaction'] = { add_fields: ["unallocated_amount"], get_indicator: function(doc) { - if(flt(doc.unallocated_amount)>0) { - return [__("Unreconciled"), "orange", "unallocated_amount,>,0"]; + if(doc.docstatus == 2) { + return [__("Cancelled"), "red", "docstatus,=,2"]; } else if(flt(doc.unallocated_amount)<=0) { return [__("Reconciled"), "green", "unallocated_amount,=,0"]; + } else if(flt(doc.unallocated_amount)>0) { + return [__("Unreconciled"), "orange", "unallocated_amount,>,0"]; } } }; diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index ce149f96e6f..439d4891194 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -25,7 +25,8 @@ class TestBankTransaction(unittest.TestCase): def tearDownClass(cls): for bt in frappe.get_all("Bank Transaction"): doc = frappe.get_doc("Bank Transaction", bt.name) - doc.cancel() + if doc.docstatus == 1: + doc.cancel() doc.delete() # Delete directly in DB to avoid validation errors for countries not allowing deletion @@ -57,6 +58,12 @@ class TestBankTransaction(unittest.TestCase): clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") self.assertTrue(clearance_date is not None) + bank_transaction.reload() + bank_transaction.cancel() + + clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") + self.assertFalse(clearance_date) + # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount def test_debit_credit_output(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07")) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index b1f89b08d79..7b24e50b143 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -86,7 +86,8 @@ status_map = { ], "Bank Transaction": [ ["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"], - ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"] + ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"], + ["Cancelled", "eval:self.docstatus == 2"] ], "POS Opening Entry": [ ["Draft", None], From d97a87e28de1aa4b6b9465270ac4347dbf69f5aa Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 17:12:04 +0530 Subject: [PATCH 16/25] fix(healthcare): Made payment fields mandatory for new appointments (#27135) * fix(healthcare): Made payment fields mandatory for new appointments (#26608) * fix(healthcare): Made payment fields mandatory for new appointments * fix: sider issues * fix: Fix failing test * fix: Patient appointment invoicing Co-authored-by: Rucha Mahabal Co-authored-by: Syed Mujeer Hashmi (cherry picked from commit a65498dc610287640e76dc8a646e9ada0c29a44a) # Conflicts: # erpnext/healthcare/doctype/patient_appointment/patient_appointment.json # erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py * chore: Fix merge conflicts * chore: Fix failing tests Co-authored-by: Chillar Anand --- .../doctype/fee_validity/test_fee_validity.py | 4 +-- .../patient_appointment.js | 7 ++++++ .../patient_appointment.json | 3 ++- .../patient_appointment.py | 8 +----- .../test_patient_appointment.py | 25 ++++++++++++++++++- 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py index 54f388b370b..29b4c5c9b98 100644 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py @@ -29,10 +29,10 @@ class TestFeeValidity(unittest.TestCase): healthcare_settings.save(ignore_permissions=True) patient, practitioner = create_healthcare_docs() - # appointment should not be invoiced. Check Fee Validity created for new patient + # For first appointment, invoice is generated appointment = create_appointment(patient, practitioner, nowdate()) invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") - self.assertEqual(invoiced, 0) + self.assertEqual(invoiced, 1) # appointment should not be invoiced as it is within fee validity appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4)) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 8923e014452..49847d5bc8a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -241,6 +241,13 @@ frappe.ui.form.on('Patient Appointment', { frm.toggle_reqd('mode_of_payment', 0); frm.toggle_reqd('paid_amount', 0); frm.toggle_reqd('billing_item', 0); + } else if (data.message) { + frm.toggle_display('mode_of_payment', 1); + frm.toggle_display('paid_amount', 1); + frm.toggle_display('billing_item', 1); + frm.toggle_reqd('mode_of_payment', 1); + frm.toggle_reqd('paid_amount', 1); + frm.toggle_reqd('billing_item', 1); } else { // if automated appointment invoicing is disabled, hide fields frm.toggle_display('mode_of_payment', data.message ? 1 : 0); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 7654e0d249f..0267e65a212 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -134,6 +134,7 @@ "read_only": 1 }, { + "depends_on": "eval:doc.practitioner;", "fieldname": "section_break_12", "fieldtype": "Section Break", "label": "Appointment Details" @@ -400,4 +401,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 7d7e078647f..10f2d537891 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -163,8 +163,6 @@ def check_payment_fields_reqd(patient): fee_validity = frappe.db.exists('Fee Validity', {'patient': patient, 'status': 'Pending'}) if fee_validity: return {'fee_validity': fee_validity} - if check_is_new_patient(patient): - return False return True return False @@ -179,8 +177,6 @@ def invoice_appointment(appointment_doc): elif not fee_validity: if frappe.db.exists('Fee Validity Reference', {'appointment': appointment_doc.name}): return - if check_is_new_patient(appointment_doc.patient, appointment_doc.name): - return else: fee_validity = None @@ -224,9 +220,7 @@ def check_is_new_patient(patient, name=None): filters['name'] = ('!=', name) has_previous_appointment = frappe.db.exists('Patient Appointment', filters) - if has_previous_appointment: - return False - return True + return not has_previous_appointment def get_appointment_item(appointment_doc, item): diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 18dc5bd5cea..c0fe5f08854 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import unittest import frappe -from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter +from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter, check_payment_fields_reqd, check_is_new_patient from frappe.utils import nowdate, add_days, now_datetime from frappe.utils.make_random import get_random from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile + class TestPatientAppointment(unittest.TestCase): def setUp(self): frappe.db.sql("""delete from `tabPatient Appointment`""") @@ -179,6 +180,28 @@ class TestPatientAppointment(unittest.TestCase): mark_invoiced_inpatient_occupancy(ip_record1) discharge_patient(ip_record1, now_datetime()) + def test_payment_should_be_mandatory_for_new_patient_appointment(self): + frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) + frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) + frappe.db.set_value('Healthcare Settings', None, 'max_visits', 3) + frappe.db.set_value('Healthcare Settings', None, 'valid_days', 30) + + patient = create_patient() + assert check_is_new_patient(patient) + payment_required = check_payment_fields_reqd(patient) + assert payment_required is True + + def test_sales_invoice_should_be_generated_for_new_patient_appointment(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) + invoice_count = frappe.db.count('Sales Invoice') + + assert check_is_new_patient(patient) + create_appointment(patient, practitioner, nowdate()) + new_invoice_count = frappe.db.count('Sales Invoice') + + assert new_invoice_count == invoice_count + 1 + def test_overlap_appointment(self): from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError patient, practitioner = create_healthcare_docs(id=1) From 1be810479c7288a9e4bdaaf5db6568b50eddfb08 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 25 Aug 2021 17:45:55 +0530 Subject: [PATCH 17/25] refactor: update stock module onboarding (#25745) * refactor: update stock onboarding * refactor: add form tour for stock module onboarding * refactor: move trailing whitespace out of translate func * refactor: sider/semgrep * refactor: remove DN, PR; change wording, add/remove steps in tour * refactor: add watch video step for stock opening balance * refactor: reorder steps according to stock settings refactor * refactor: fix typo, remove target warehouse cause SE Type dependency * fix: semgrep, remove trailing and leading whitespaces * refactor: reduce steps, reword cards * fix: minor changes - remove Is Group from warehouse - change stock entry type - link to stock entry type - add posting date to stock reco - change report to Stock Projected Qty - highlight quality inspection action - remove allow neg highlight * refactor: use Form Tour doc instead of controller form tour note - keeping controller form tours as a fallback, new form tours seem to work only for Stock Settings * fix: rename form tours to doctype names, remove tours from js controllers * fix: re-order tour to circumvent glitchy save highlight --- erpnext/hooks.py | 1 + .../doctype/delivery_note/delivery_note.js | 20 +++++ erpnext/stock/doctype/item/item.js | 2 +- .../stock/doctype/stock_entry/stock_entry.js | 1 + .../stock_reconciliation.js | 1 + .../doctype/stock_settings/stock_settings.js | 33 ------- erpnext/stock/doctype/warehouse/warehouse.js | 1 + .../form_tour/stock_entry/stock_entry.json | 56 ++++++++++++ .../stock_reconciliation.json | 55 ++++++++++++ .../stock_settings/stock_settings.json | 89 +++++++++++++++++++ .../stock/form_tour/warehouse/warehouse.json | 54 +++++++++++ .../stock/module_onboarding/stock/stock.json | 16 ++-- .../create_a_purchase_receipt.json | 19 ---- .../create_a_stock_entry.json | 8 +- .../create_a_supplier/create_a_supplier.json | 7 +- .../create_a_warehouse.json | 21 +++++ .../create_an_item/create_an_item.json | 22 +++++ .../introduction_to_stock_entry.json | 5 +- .../setup_your_warehouse.json | 4 +- .../stock_opening_balance.json | 22 +++++ .../stock_settings/stock_settings.json | 8 +- .../view_stock_projected_qty.json | 24 +++++ .../view_warehouses/view_warehouses.json | 20 +++++ 23 files changed, 412 insertions(+), 77 deletions(-) create mode 100644 erpnext/stock/form_tour/stock_entry/stock_entry.json create mode 100644 erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json create mode 100644 erpnext/stock/form_tour/stock_settings/stock_settings.json create mode 100644 erpnext/stock/form_tour/warehouse/warehouse.json delete mode 100644 erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json create mode 100644 erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json create mode 100644 erpnext/stock/onboarding_step/create_an_item/create_an_item.json create mode 100644 erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json create mode 100644 erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json create mode 100644 erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c21b240a019..748bd088077 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -444,6 +444,7 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', + 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount', 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code' diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 74cb3fcb1f0..8632c9c1085 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -356,3 +356,23 @@ erpnext.stock.delivery_note.set_print_hide = function(doc, cdt, cdn){ dn_fields['taxes'].print_hide = 0; } } + + +frappe.tour['Delivery Note'] = [ + { + fieldname: "customer", + title: __("Customer"), + description: __("This field is used to set the 'Customer'.") + }, + { + fieldname: "items", + title: __("Items"), + description: __("This table is used to set details about the 'Item', 'Qty', 'Basic Rate', etc.") + " " + + __("Different 'Source Warehouse' and 'Target Warehouse' can be set for each row.") + }, + { + fieldname: "set_posting_time", + title: __("Edit Posting Date and Time"), + description: __("This option can be checked to edit the 'Posting Date' and 'Posting Time' fields.") + } +] diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 6c5ef8b4f03..cbd0231e66a 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -792,4 +792,4 @@ frappe.ui.form.on("UOM Conversion Detail", { }); } } -}) +}); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 67083930272..efbc12ce841 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1101,3 +1101,4 @@ function check_should_not_attach_bom_items(bom_no) { } $.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); + diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 349e59f31d1..99694690bbc 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -288,3 +288,4 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({ }); cur_frm.cscript = new erpnext.stock.StockReconciliation({frm: cur_frm}); + diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js index 48624e0f25e..6167becdaac 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.js +++ b/erpnext/stock/doctype/stock_settings/stock_settings.js @@ -16,36 +16,3 @@ frappe.ui.form.on('Stock Settings', { } }); -frappe.tour['Stock Settings'] = [ - { - fieldname: "item_naming_by", - title: __("Item Naming By"), - description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."), - }, - { - fieldname: "default_warehouse", - title: __("Default Warehouse"), - description: __("Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.") - }, - { - fieldname: "allow_negative_stock", - title: __("Allow Negative Stock"), - description: __("This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.") - - }, - { - fieldname: "valuation_method", - title: __("Valuation Method"), - description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "here" + __(" to know more about them.") - }, - { - fieldname: "show_barcode_field", - title: __("Show Barcode Field"), - description: __("Show 'Scan Barcode' field above every child table to insert Items with ease.") - }, - { - fieldname: "automatically_set_serial_nos_based_on_fifo", - title: __("Automatically Set Serial Nos based on FIFO"), - description: __("Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.") - } -]; diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 9243e1ed84f..4e1679c4116 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -86,3 +86,4 @@ function convert_to_group_or_ledger(frm){ }) } + diff --git a/erpnext/stock/form_tour/stock_entry/stock_entry.json b/erpnext/stock/form_tour/stock_entry/stock_entry.json new file mode 100644 index 00000000000..6363c6ad4dd --- /dev/null +++ b/erpnext/stock/form_tour/stock_entry/stock_entry.json @@ -0,0 +1,56 @@ +{ + "creation": "2021-08-24 14:44:22.292652", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-25 16:31:31.441194", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Entry", + "owner": "Administrator", + "reference_doctype": "Stock Entry", + "save_on_complete": 1, + "steps": [ + { + "description": "Select the type of Stock Entry to be made. For now, to receive stock into a warehouses select Material Receipt.", + "field": "", + "fieldname": "stock_entry_type", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Stock Entry Type", + "next_step_condition": "eval: doc.stock_entry_type === \"Material Receipt\"", + "parent_field": "", + "position": "Top", + "title": "Stock Entry Type" + }, + { + "description": "Select a target warehouse where the stock will be received.", + "field": "", + "fieldname": "to_warehouse", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Default Target Warehouse", + "next_step_condition": "eval: doc.to_warehouse", + "parent_field": "", + "position": "Top", + "title": "Default Target Warehouse" + }, + { + "description": "Select an item and entry quantity to be delivered.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Items", + "next_step_condition": "eval: doc.items[0]?.item_code", + "parent_field": "", + "position": "Top", + "title": "Items" + } + ], + "title": "Stock Entry" +} \ No newline at end of file diff --git a/erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json new file mode 100644 index 00000000000..5b7fd72c082 --- /dev/null +++ b/erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json @@ -0,0 +1,55 @@ +{ + "creation": "2021-08-24 14:44:46.770952", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-25 16:26:11.718664", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Reconciliation", + "owner": "Administrator", + "reference_doctype": "Stock Reconciliation", + "save_on_complete": 1, + "steps": [ + { + "description": "Set Purpose to Opening Stock to set the stock opening balance.", + "field": "", + "fieldname": "purpose", + "fieldtype": "Select", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Purpose", + "next_step_condition": "eval: doc.purpose === \"Opening Stock\"", + "parent_field": "", + "position": "Top", + "title": "Purpose" + }, + { + "description": "Select the items for which the opening stock has to be set.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Items", + "next_step_condition": "eval: doc.items[0]?.item_code", + "parent_field": "", + "position": "Top", + "title": "Items" + }, + { + "description": "Edit the Posting Date by clicking on the Edit Posting Date and Time checkbox below.", + "field": "", + "fieldname": "posting_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Posting Date", + "parent_field": "", + "position": "Bottom", + "title": "Posting Date" + } + ], + "title": "Stock Reconciliation" +} \ No newline at end of file diff --git a/erpnext/stock/form_tour/stock_settings/stock_settings.json b/erpnext/stock/form_tour/stock_settings/stock_settings.json new file mode 100644 index 00000000000..3d164e33b3b --- /dev/null +++ b/erpnext/stock/form_tour/stock_settings/stock_settings.json @@ -0,0 +1,89 @@ +{ + "creation": "2021-08-20 15:20:59.336585", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-25 16:19:37.699528", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Settings", + "owner": "Administrator", + "reference_doctype": "Stock Settings", + "save_on_complete": 1, + "steps": [ + { + "description": "By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a Naming Series choose the 'Naming Series' option.", + "field": "", + "fieldname": "item_naming_by", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Naming By", + "parent_field": "", + "position": "Bottom", + "title": "Item Naming By" + }, + { + "description": "Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.", + "field": "", + "fieldname": "default_warehouse", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Warehouse", + "parent_field": "", + "position": "Bottom", + "title": "Default Warehouse" + }, + { + "description": "Quality inspection is performed on the inward and outward movement of goods. Receipt and delivery transactions will be stopped or the user will be warned if the quality inspection is not performed.", + "field": "", + "fieldname": "action_if_quality_inspection_is_not_submitted", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Action If Quality Inspection Is Not Submitted", + "parent_field": "", + "position": "Bottom", + "title": "Action if Quality Inspection Is Not Submitted" + }, + { + "description": "Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.", + "field": "", + "fieldname": "automatically_set_serial_nos_based_on_fifo", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Automatically Set Serial Nos Based on FIFO", + "parent_field": "", + "position": "Bottom", + "title": "Automatically Set Serial Nos based on FIFO" + }, + { + "description": "Show 'Scan Barcode' field above every child table to insert Items with ease.", + "field": "", + "fieldname": "show_barcode_field", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Show Barcode Field in Stock Transactions", + "parent_field": "", + "position": "Bottom", + "title": "Show Barcode Field" + }, + { + "description": "Choose between FIFO and Moving Average Valuation Methods. Click here to know more about them.", + "field": "", + "fieldname": "valuation_method", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Valuation Method", + "parent_field": "", + "position": "Bottom", + "title": "Default Valuation Method" + } + ], + "title": "Stock Settings" +} \ No newline at end of file diff --git a/erpnext/stock/form_tour/warehouse/warehouse.json b/erpnext/stock/form_tour/warehouse/warehouse.json new file mode 100644 index 00000000000..23ff2aebbaa --- /dev/null +++ b/erpnext/stock/form_tour/warehouse/warehouse.json @@ -0,0 +1,54 @@ +{ + "creation": "2021-08-24 14:43:44.465237", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-24 14:50:31.988256", + "modified_by": "Administrator", + "module": "Stock", + "name": "Warehouse", + "owner": "Administrator", + "reference_doctype": "Warehouse", + "save_on_complete": 1, + "steps": [ + { + "description": "Select a name for the warehouse. This should reflect its location or purpose.", + "field": "", + "fieldname": "warehouse_name", + "fieldtype": "Data", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Warehouse Name", + "next_step_condition": "eval: doc.warehouse_name", + "parent_field": "", + "position": "Bottom", + "title": "Warehouse Name" + }, + { + "description": "Select a warehouse type to categorize the warehouse into a sub-group.", + "field": "", + "fieldname": "warehouse_type", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Warehouse Type", + "parent_field": "", + "position": "Top", + "title": "Warehouse Type" + }, + { + "description": "Select an account to set a default account for all transactions with this warehouse.", + "field": "", + "fieldname": "account", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Account", + "parent_field": "", + "position": "Top", + "title": "Account" + } + ], + "title": "Warehouse" +} \ No newline at end of file diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json index 847464822b4..c246747a5b3 100644 --- a/erpnext/stock/module_onboarding/stock/stock.json +++ b/erpnext/stock/module_onboarding/stock/stock.json @@ -19,32 +19,26 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock", "idx": 0, "is_complete": 0, - "modified": "2020-10-14 14:54:42.741971", + "modified": "2021-08-20 14:38:55.570067", "modified_by": "Administrator", "module": "Stock", "name": "Stock", "owner": "Administrator", "steps": [ { - "step": "Setup your Warehouse" + "step": "Stock Settings" }, { - "step": "Create a Product" - }, - { - "step": "Create a Supplier" - }, - { - "step": "Introduction to Stock Entry" + "step": "Create a Warehouse" }, { "step": "Create a Stock Entry" }, { - "step": "Create a Purchase Receipt" + "step": "Stock Opening Balance" }, { - "step": "Stock Settings" + "step": "View Stock Projected Qty" } ], "subtitle": "Inventory, Warehouses, Analysis, and more.", diff --git a/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json deleted file mode 100644 index 9012493f57e..00000000000 --- a/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "action": "Create Entry", - "creation": "2020-05-19 18:59:13.266713", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_mandatory": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2020-10-14 14:53:25.618434", - "modified_by": "Administrator", - "name": "Create a Purchase Receipt", - "owner": "Administrator", - "reference_document": "Purchase Receipt", - "show_full_form": 1, - "title": "Create a Purchase Receipt", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json index 09902b8844e..3cb522c893d 100644 --- a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json +++ b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create a Material Transfer Entry", "creation": "2020-05-15 03:20:16.277043", + "description": "# Manage Stock Movements\nStock entry allows you to register the movement of stock for various purposes like transfer, received, issues, repacked, etc. To address issues related to theft and pilferages, you can always ensure that the movement of goods happens against a document reference Stock Entry in ERPNext.\n\nLet\u2019s get a quick walk-through on the various scenarios covered in Stock Entry by watching [*this video*](https://www.youtube.com/watch?v=Njt107hlY3I).", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-14 14:53:00.105905", + "modified": "2021-06-18 13:57:11.434063", "modified_by": "Administrator", "name": "Create a Stock Entry", "owner": "Administrator", "reference_document": "Stock Entry", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a Stock Entry", + "title": "Manage Stock Movements", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json index ef61fa3b2e2..49efe578a29 100644 --- a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json +++ b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json @@ -1,18 +1,19 @@ { - "action": "Create Entry", + "action": "Show Form Tour", "creation": "2020-05-14 22:09:10.043554", + "description": "# Create a Supplier\nIn this step we will create a **Supplier**. If you have already created a **Supplier** you can skip this step.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-14 14:53:00.120455", + "modified": "2021-05-17 16:37:37.697077", "modified_by": "Administrator", "name": "Create a Supplier", "owner": "Administrator", "reference_document": "Supplier", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Supplier", "validate_action": 1 diff --git a/erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json b/erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json new file mode 100644 index 00000000000..22c88bf10ea --- /dev/null +++ b/erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Let\u2019s create your first warehouse ", + "creation": "2021-05-17 16:13:19.297789", + "description": "# Setup a Warehouse\nThe warehouse can be your location/godown/store where you maintain the item's inventory, and receive/deliver them to various parties.\n\nIn ERPNext, you can maintain a Warehouse in the tree structure, so that location and sub-location of an item can be tracked. Also, you can link a Warehouse to a specific Accounting ledger, where the real-time stock value of that warehouse\u2019s item will be reflected.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-18 12:23:36.675572", + "modified_by": "Administrator", + "name": "Create a Warehouse", + "owner": "Administrator", + "reference_document": "Warehouse", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Setup a Warehouse", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/create_an_item/create_an_item.json b/erpnext/stock/onboarding_step/create_an_item/create_an_item.json new file mode 100644 index 00000000000..016cbd566d5 --- /dev/null +++ b/erpnext/stock/onboarding_step/create_an_item/create_an_item.json @@ -0,0 +1,22 @@ +{ + "action": "Create Entry", + "action_label": "", + "creation": "2021-05-17 13:47:18.515052", + "description": "# Create an Item\nThe Stock module deals with the movement of items.\n\nIn this step we will create an [**Item**](https://docs.erpnext.com/docs/user/manual/en/stock/item).", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "intro_video_url": "", + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-05-18 16:15:20.695028", + "modified_by": "Administrator", + "name": "Create an Item", + "owner": "Administrator", + "reference_document": "Item", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Create an Item", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json index 212e5055eda..384950e8b99 100644 --- a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json +++ b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json @@ -1,17 +1,18 @@ { "action": "Watch Video", "creation": "2020-05-15 02:47:17.958806", + "description": "# Introduction to Stock Entry\nThis video will give a quick introduction to [**Stock Entry**](https://docs.erpnext.com/docs/user/manual/en/stock/stock-entry).", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-14 14:53:00.075177", + "modified": "2021-05-18 15:13:43.306064", "modified_by": "Administrator", "name": "Introduction to Stock Entry", "owner": "Administrator", + "show_form_tour": 0, "show_full_form": 0, "title": "Introduction to Stock Entry", "validate_action": 1, diff --git a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json index 75940ed2a6c..5d33a649100 100644 --- a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json +++ b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json @@ -5,15 +5,15 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-14 14:53:25.538900", + "modified": "2021-05-17 13:53:06.936579", "modified_by": "Administrator", "name": "Setup your Warehouse", "owner": "Administrator", "path": "Tree/Warehouse", "reference_document": "Warehouse", + "show_form_tour": 0, "show_full_form": 0, "title": "Set up your Warehouse", "validate_action": 1 diff --git a/erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json b/erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json new file mode 100644 index 00000000000..48fd1fddee0 --- /dev/null +++ b/erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json @@ -0,0 +1,22 @@ +{ + "action": "Create Entry", + "action_label": "Let\u2019s create a stock opening entry", + "creation": "2021-05-17 16:13:47.511883", + "description": "# Update Stock Opening Balance\nIt\u2019s an entry to update the stock balance of an item, in a warehouse, on a date and time you are going live on ERPNext.\n\nOnce opening stocks are updated, you can create transactions like manufacturing and stock deliveries, where this opening stock will be consumed.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-06-18 13:59:36.021097", + "modified_by": "Administrator", + "name": "Stock Opening Balance", + "owner": "Administrator", + "reference_document": "Stock Reconciliation", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Update Stock Opening Balance", + "validate_action": 1, + "video_url": "https://www.youtube.com/watch?v=nlHX0ZZ84Lw" +} \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/stock_settings/stock_settings.json b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json index ae34afa695f..2cf90e806cd 100644 --- a/erpnext/stock/onboarding_step/stock_settings/stock_settings.json +++ b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json @@ -1,19 +1,21 @@ { "action": "Show Form Tour", + "action_label": "Take a walk through Stock Settings", "creation": "2020-05-15 02:53:57.209967", + "description": "# Review Stock Settings\n\nIn ERPNext, the Stock module\u2019s features are configurable as per your business needs. Stock Settings is the place where you can set your preferences for:\n- Default values for Item and Pricing\n- Default valuation method for inventory valuation\n- Set preference for serialization and batching of item\n- Set tolerance for over-receipt and delivery of items", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-10-14 14:53:00.092504", + "modified": "2021-08-18 12:06:51.139387", "modified_by": "Administrator", "name": "Stock Settings", "owner": "Administrator", "reference_document": "Stock Settings", + "show_form_tour": 0, "show_full_form": 0, - "title": "Explore Stock Settings", + "title": "Review Stock Settings", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json b/erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json new file mode 100644 index 00000000000..e684780751f --- /dev/null +++ b/erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json @@ -0,0 +1,24 @@ +{ + "action": "View Report", + "action_label": "Check Stock Projected Qty", + "creation": "2021-08-20 14:38:41.649103", + "description": "# Check Stock Reports\nBased on the various stock transactions, you can get a host of one-click Stock Reports in ERPNext like Stock Ledger, Stock Balance, Projected Quantity, and Ageing analysis.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-20 14:38:41.649103", + "modified_by": "Administrator", + "name": "View Stock Projected Qty", + "owner": "Administrator", + "reference_report": "Stock Projected Qty", + "report_description": "You can set the filters to narrow the results, then click on Generate New Report to see the updated report.", + "report_reference_doctype": "Item", + "report_type": "Script Report", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Check Stock Projected Qty", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json b/erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json new file mode 100644 index 00000000000..c46c4bdab86 --- /dev/null +++ b/erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json @@ -0,0 +1,20 @@ +{ + "action": "Go to Page", + "creation": "2021-05-17 16:12:43.427579", + "description": "# View Warehouse\nIn ERPNext the term 'warehouse' can be thought of as a storage location.\n\nWarehouses are arranged in ERPNext in a tree like structure, where multiple sub-warehouses can be grouped under a single warehouse.\n\nIn this step we will view the [**Warehouse Tree**](https://docs.erpnext.com/docs/user/manual/en/stock/warehouse#21-tree-view) to view the [**Warehouses**](https://docs.erpnext.com/docs/user/manual/en/stock/warehouse) that are set by default.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-05-18 15:04:41.198413", + "modified_by": "Administrator", + "name": "View Warehouses", + "owner": "Administrator", + "path": "Tree/Warehouse", + "show_form_tour": 0, + "show_full_form": 0, + "title": "View Warehouses", + "validate_action": 1 +} \ No newline at end of file From 4c3034ad798b844c09109b954d0c474cd5fd342c Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 17:47:51 +0530 Subject: [PATCH 18/25] fix: remove VARCHARs from Sales Invoice (#27136) (#27139) Sales Invoice doctype is starting to hit row length limit as many integrations add custom fields on this doctype. This is just a small change to remove VARCHAR(140) fields and reduce row size wherever possible. (cherry picked from commit 8d116fb9ff45b193d4dad75db700439bc6fa5799) Co-authored-by: Ankush Menat --- .../doctype/sales_invoice/sales_invoice.json | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 01ae713cd36..99ecb8a4fea 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -247,7 +247,7 @@ "depends_on": "customer", "fetch_from": "customer.customer_name", "fieldname": "customer_name", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "in_global_search": 1, @@ -692,10 +692,11 @@ { "fieldname": "scan_barcode", "fieldtype": "Data", - "options": "Barcode", "hide_days": 1, "hide_seconds": 1, - "label": "Scan Barcode" + "label": "Scan Barcode", + "length": 1, + "options": "Barcode" }, { "allow_bulk_edit": 1, @@ -1059,6 +1060,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Apply Additional Discount On", + "length": 15, "options": "\nGrand Total\nNet Total", "print_hide": 1 }, @@ -1145,7 +1147,7 @@ { "description": "In Words will be visible once you save the Sales Invoice.", "fieldname": "base_in_words", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "label": "In Words (Company Currency)", @@ -1205,7 +1207,7 @@ }, { "fieldname": "in_words", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "label": "In Words", @@ -1558,6 +1560,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Print Language", + "length": 6, "print_hide": 1, "read_only": 1 }, @@ -1645,6 +1648,7 @@ "hide_seconds": 1, "in_standard_filter": 1, "label": "Status", + "length": 30, "no_copy": 1, "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, @@ -1704,6 +1708,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Is Opening Entry", + "length": 4, "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", @@ -1715,6 +1720,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "C-Form Applicable", + "length": 4, "no_copy": 1, "options": "No\nYes", "print_hide": 1 @@ -2017,7 +2023,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-08-18 16:07:45.122570", + "modified": "2021-08-25 14:46:05.279588", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From f8ec0b6a86acbee91f26e5961291424350e78a83 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 20:05:54 +0530 Subject: [PATCH 19/25] feat: provision to create customer from opportunity (#27145) * feat: provision to create customer from opportunity (#27141) * feat: provision to create customer from opportunity * fead: linking of address and contact * revert: create_opportunity_address_contact * enabming print hide and no copy (cherry picked from commit 4d98be2126572a32d13ab76db5954d674d6845b2) # Conflicts: # erpnext/crm/doctype/opportunity/opportunity.js * Update opportunity.js * fix: conflicts Co-authored-by: Anupam Kumar Co-authored-by: Rucha Mahabal --- .../crm/doctype/opportunity/opportunity.js | 19 +++++++++++++++++-- .../crm/doctype/opportunity/opportunity.py | 18 ++++++++++++++++++ .../selling/doctype/customer/customer.json | 11 ++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 875d221efeb..5cc63d4511b 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -95,9 +95,17 @@ frappe.ui.form.on("Opportunity", { }, __('Create')); } - frm.add_custom_button(__('Quotation'), - cur_frm.cscript.create_quotation, __('Create')); + if (frm.doc.opportunity_from != "Customer") { + frm.add_custom_button(__('Customer'), + function() { + frm.trigger("make_customer") + }, __('Create')); + } + frm.add_custom_button(__('Quotation'), + function() { + frm.trigger("create_quotation") + }, __('Create')); } if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { @@ -194,6 +202,13 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({ method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation", frm: cur_frm }) + }, + + make_customer: function() { + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.opportunity.opportunity.make_customer", + frm: cur_frm + }) } }); diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 8ce482a3f9f..a74a94afd68 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -287,6 +287,24 @@ def make_request_for_quotation(source_name, target_doc=None): return doclist +@frappe.whitelist() +def make_customer(source_name, target_doc=None): + def set_missing_values(source, target): + if source.opportunity_from == "Lead": + target.lead_name = source.party_name + + doclist = get_mapped_doc("Opportunity", source_name, { + "Opportunity": { + "doctype": "Customer", + "field_map": { + "currency": "default_currency", + "customer_name": "customer_name" + } + } + }, target_doc, set_missing_values) + + return doclist + @frappe.whitelist() def make_supplier_quotation(source_name, target_doc=None): doclist = get_mapped_doc("Opportunity", source_name, { diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index cd94ee101af..0d839fc8228 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -20,6 +20,7 @@ "tax_withholding_category", "default_bank_account", "lead_name", + "opportunity_name", "image", "column_break0", "account_manager", @@ -493,6 +494,14 @@ "fieldtype": "Link", "label": "Tax Withholding Category", "options": "Tax Withholding Category" + }, + { + "fieldname": "opportunity_name", + "fieldtype": "Link", + "label": "From Opportunity", + "no_copy": 1, + "options": "Opportunity", + "print_hide": 1 } ], "icon": "fa fa-user", @@ -500,7 +509,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-28 12:54:57.258959", + "modified": "2021-08-25 18:56:09.929905", "modified_by": "Administrator", "module": "Selling", "name": "Customer", From 0fe6995816fc9b136d8c0fc17a118637af6ac50b Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 20:11:23 +0530 Subject: [PATCH 20/25] fix: sequence of sub-operations in job card (#27138) (#27147) (cherry picked from commit ad45ddcabe90c628344474ca5022fd67e27f43ad) Co-authored-by: rohitwaghchaure --- erpnext/manufacturing/doctype/job_card/job_card.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 66e2394b847..3efbe88adaf 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -26,17 +26,17 @@ class JobCard(Document): self.set_status() self.validate_operation_id() self.validate_sequence_id() - self.get_sub_operations() + self.set_sub_operations() self.update_sub_operation_status() - def get_sub_operations(self): + def set_sub_operations(self): if self.operation: self.sub_operations = [] - for row in frappe.get_all("Sub Operation", - filters = {"parent": self.operation}, fields=["operation", "idx"]): - row.status = "Pending" + for row in frappe.get_all('Sub Operation', + filters = {'parent': self.operation}, fields=['operation', 'idx'], order_by='idx'): + row.status = 'Pending' row.sub_operation = row.operation - self.append("sub_operations", row) + self.append('sub_operations', row) def validate_time_logs(self): self.total_time_in_mins = 0.0 @@ -690,7 +690,7 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta target.set('time_logs', []) target.set('employee', []) target.set('items', []) - target.get_sub_operations() + target.set_sub_operations() target.get_required_items() target.validate_time_logs() From 7b9a23eb7aabc926fab79db17e3c9638aeb3972a Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 25 Aug 2021 21:15:44 +0530 Subject: [PATCH 21/25] feat: Increase number of supported currency exchanges (#26763) * fix: update test suite to accodomate new currency exchange function * feat: Increase number of supported currency exchanges * fix: don't make api call when testing * remove condition for test(being fixed in another pull request) --- .../test_currency_exchange.py | 23 +++++-------------- erpnext/setup/utils.py | 14 +++++------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 1c928cd87d0..4ff2dd7e0e9 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -63,11 +63,11 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) - # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io + # Exchange rate as on 15th Dec, 2015 self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling") self.assertFalse(exchange_rate == 60) - self.assertEqual(flt(exchange_rate, 3), 66.894) + self.assertEqual(flt(exchange_rate, 3), 66.999) def test_exchange_rate_strict(self): # strict currency settings @@ -77,28 +77,17 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") self.assertEqual(exchange_rate, 60.0) - # Will fetch from fixer.io self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 67.79) + self.assertEqual(flt(exchange_rate, 3), 67.235) exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) - # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io + # Exchange rate as on 15th Dec, 2015 self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 66.894) - - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling") - self.assertEqual(exchange_rate, 65.1) - - # NGN is not available on fixer.io so these should return 0 - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling") - self.assertEqual(exchange_rate, 0) - - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling") - self.assertEqual(exchange_rate, 0) + self.assertEqual(flt(exchange_rate, 3), 66.999) def test_exchange_rate_strict_switched(self): # Start with allow_stale is True @@ -111,4 +100,4 @@ class TestCurrencyExchange(unittest.TestCase): # Will fetch from fixer.io self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 67.79) + self.assertEqual(flt(exchange_rate, 3), 67.235) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 13269a82823..27237bf2cbe 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -93,21 +93,21 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No try: cache = frappe.cache() - key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency) + key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency) value = cache.get(key) if not value: import requests - api_url = "https://frankfurter.app/{0}".format(transaction_date) + api_url = "https://api.exchangerate.host/convert" response = requests.get(api_url, params={ - "base": from_currency, - "symbols": to_currency + "date": transaction_date, + "from": from_currency, + "to": to_currency }) # expire in 6 hours response.raise_for_status() - value = response.json()["rates"][to_currency] - - cache.set_value(key, value, expires_in_sec=6 * 60 * 60) + value = response.json()["result"] + cache.setex(name=key, time=21600, value=flt(value)) return flt(value) except: frappe.log_error(title="Get Exchange Rate") From 4837d8872e5454a911e069c3670b0c5f5734f49a Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 22:25:29 +0530 Subject: [PATCH 22/25] fix: unable to create manual / auto asset depreciation entry when cost_center is mandatory (#26912) (#27149) Summary : unable to create manual / auto asset depreciation entry when cost_center is mandatory Reason: Though we are calculating value for depreciation_cost_center, it is not passed in credit_entry(it is passed in debit_entry) and this prevents creation of manual / auto asset depreciation entry when cost_center is mandatory Solution : pass already calculated depreciation_cost_center value in credit_entry (in line with, already done as in debit_entry) (cherry picked from commit b99c0119475ed82895f479f89685977204fcdbba) Co-authored-by: Ashish Shah --- erpnext/assets/doctype/asset/depreciation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 8f0afb42b2c..251fe3fa493 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -59,7 +59,7 @@ def make_depreciation_entry(asset_name, date=None): "credit_in_account_currency": d.depreciation_amount, "reference_type": "Asset", "reference_name": asset.name, - "cost_center": "" + "cost_center": depreciation_cost_center } debit_entry = { From 7f27586cbe6f3112183228c53b4f46c39299850e Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 25 Aug 2021 22:26:09 +0530 Subject: [PATCH 23/25] fix(healthcare): Removed ignore user permissions flag in appointment (#27146) * fix(healthcare): Removed ignore user permissions flag in appointment (#27129) (cherry picked from commit 81b28b899843dcbf66d5c2cc241429e363ab04ac) # Conflicts: # erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py * chore: Fix merge conflicts Co-authored-by: Chillar Anand --- .../healthcare_practitioner.json | 5 ++- .../patient_appointment.json | 1 - .../test_patient_appointment.py | 37 ++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json index 8162f03f6dc..cb455eb5014 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -282,7 +282,7 @@ ], "image_field": "image", "links": [], - "modified": "2021-01-22 10:14:43.187675", + "modified": "2021-08-24 10:42:08.513054", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Practitioner", @@ -295,6 +295,7 @@ "read": 1, "report": 1, "role": "Laboratory User", + "select": 1, "share": 1, "write": 1 }, @@ -307,6 +308,7 @@ "read": 1, "report": 1, "role": "Physician", + "select": 1, "share": 1, "write": 1 }, @@ -319,6 +321,7 @@ "read": 1, "report": 1, "role": "Nursing User", + "select": 1, "share": 1, "write": 1 } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 0267e65a212..a6929c28511 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -142,7 +142,6 @@ { "fieldname": "practitioner", "fieldtype": "Link", - "ignore_user_permissions": 1, "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner", diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index c0fe5f08854..062a32a92e6 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -251,6 +251,27 @@ class TestPatientAppointment(unittest.TestCase): appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) self.assertRaises(MaximumCapacityError, appointment.save) + def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self): + patient, practitioner = create_healthcare_docs() + create_appointment(patient, practitioner, nowdate()) + + patient, new_practitioner = create_healthcare_docs(id=2) + create_appointment(patient, new_practitioner, nowdate()) + + roles = [{"doctype": "Has Role", "role": "Physician"}] + user = create_user(roles=roles) + new_practitioner = frappe.get_doc('Healthcare Practitioner', new_practitioner) + new_practitioner.user_id = user.email + new_practitioner.save() + + frappe.set_user(user.name) + appointments = frappe.get_list('Patient Appointment') + assert len(appointments) == 1 + + frappe.set_user("Administrator") + appointments = frappe.get_list('Patient Appointment') + assert len(appointments) == 2 + def create_healthcare_docs(id=0): patient = create_patient(id) @@ -298,7 +319,6 @@ def create_practitioner(id=0, medical_department=None): return practitioner.name - def create_encounter(appointment): if appointment: encounter = frappe.new_doc('Patient Encounter') @@ -313,7 +333,6 @@ def create_encounter(appointment): return encounter - def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, appointment_type=None, save=1, department=None): item = create_healthcare_service_items() @@ -423,3 +442,17 @@ def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0): service_unit.save(ignore_permissions=True) return service_unit.name + +def create_user(email=None, roles=None): + if not email: + email = '{}@frappe.com'.format(frappe.utils.random_string(10)) + user = frappe.db.exists('User', email) + if not user: + user = frappe.get_doc({ + "doctype": "User", + "email": email, + "first_name": "test_user", + "password": "password", + "roles": roles, + }).insert() + return user From 6609321399da1fc6e6a9357b1470ffca21e0b7fa Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 26 Aug 2021 11:02:01 +0530 Subject: [PATCH 24/25] fix: removing toggle_display for address and contact HTML (#27152) (#27155) (cherry picked from commit c8f22e5524980996eec998566d2cea19be1f2b93) Co-authored-by: Anupam Kumar --- erpnext/selling/doctype/customer/customer.js | 1 - erpnext/selling/doctype/customer/customer.json | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index fb027be11c7..8416901b7cb 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -111,7 +111,6 @@ frappe.ui.form.on("Customer", { } frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'} - frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); if(!frm.doc.__islocal) { frappe.contacts.render_address_and_contact(frm); diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 0d839fc8228..2acc64cb433 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -268,6 +268,7 @@ "options": "fa fa-map-marker" }, { + "depends_on": "eval: !doc.__islocal", "fieldname": "address_html", "fieldtype": "HTML", "label": "Address HTML", @@ -284,6 +285,7 @@ "width": "50%" }, { + "depends_on": "eval: !doc.__islocal", "fieldname": "contact_html", "fieldtype": "HTML", "label": "Contact HTML", From 16fbee30a6cae38e1e3264272441af226b49f377 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 26 Aug 2021 12:23:52 +0530 Subject: [PATCH 25/25] fix: Shopping cart Exchange rate validation (#27050) * fix: Shopping cart Exchange rate validation - Use `get_exchange_rate` to check for price list exchange rate in cart settings - Move cart exchange rate validation for Price List from hooks to doc event - Call cart exchange rate validation on PL update only if PL is in cart and currency is changed * chore: Comment out obsolete test - Modifying this test means considering extreme edge cases, which seems pointless now * fix: Remove snippet that got in due to cherry-pick from `develop` - This snippet is not present in v13-hotfix. Via https://github.com/frappe/erpnext/pull/26520 Co-authored-by: Nabin Hait --- erpnext/hooks.py | 2 +- .../shopping_cart_settings.py | 61 ++++++++----------- .../test_shopping_cart_settings.py | 26 +++++--- .../stock/doctype/price_list/price_list.py | 14 +++++ 4 files changed, 57 insertions(+), 46 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 748bd088077..aede8ff2f46 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -243,7 +243,7 @@ doc_events = { "on_update": ["erpnext.hr.doctype.employee.employee.update_user_permissions", "erpnext.portal.utils.set_default_role"] }, - ("Sales Taxes and Charges Template", 'Price List'): { + "Sales Taxes and Charges Template": { "on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings" }, "Website Settings": { diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py index 2a497225fbc..efed1968a14 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _, msgprint -from frappe.utils import comma_and +from frappe.utils import flt from frappe.model.document import Document from frappe.utils import get_datetime, get_datetime_str, now_datetime @@ -18,46 +18,35 @@ class ShoppingCartSettings(Document): def validate(self): if self.enabled: - self.validate_exchange_rates_exist() + self.validate_price_list_exchange_rate() + + def validate_price_list_exchange_rate(self): + "Check if exchange rate exists for Price List currency (to Company's currency)." + from erpnext.setup.utils import get_exchange_rate + + if not self.enabled or not self.company or not self.price_list: + return # this function is also called from hooks, check values again + + company_currency = frappe.get_cached_value("Company", self.company, "default_currency") + price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency") - def validate_exchange_rates_exist(self): - """check if exchange rates exist for all Price List currencies (to company's currency)""" - company_currency = frappe.get_cached_value('Company', self.company, "default_currency") if not company_currency: - msgprint(_("Please specify currency in Company") + ": " + self.company, - raise_exception=ShoppingCartSetupError) + msg = f"Please specify currency in Company {self.company}" + frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - price_list_currency_map = frappe.db.get_values("Price List", - [self.price_list], "currency") + if not price_list_currency: + msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}" + frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - price_list_currency_map = dict(price_list_currency_map) + if price_list_currency != company_currency: + from_currency, to_currency = price_list_currency, company_currency - # check if all price lists have a currency - for price_list, currency in price_list_currency_map.items(): - if not currency: - frappe.throw(_("Currency is required for Price List {0}").format(price_list)) + # Get exchange rate checks Currency Exchange Records too + exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling") - expected_to_exist = [currency + "-" + company_currency - for currency in price_list_currency_map.values() - if currency != company_currency] - - # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange - from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency] - to_currency = company_currency - # manqala end - - if expected_to_exist: - # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange. - # exchange rates defined with date less than the date on which this document is being saved will be selected - exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange` - where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency)) - # manqala end - - missing = list(set(expected_to_exist).difference(exists)) - - if missing: - msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)), - raise_exception=ShoppingCartSetupError) + if not flt(exchange_rate): + msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}" + frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError) def validate_tax_rule(self): if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"): @@ -71,7 +60,7 @@ class ShoppingCartSettings(Document): def get_shipping_rules(self, shipping_territory): return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") -def validate_cart_settings(doc, method): +def validate_cart_settings(doc=None, method=None): frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate") def get_shopping_cart_settings(): diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py index 008751e2088..9965e1af672 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -16,17 +16,25 @@ class TestShoppingCartSettings(unittest.TestCase): return frappe.get_doc({"doctype": "Shopping Cart Settings", "company": "_Test Company"}) - def test_exchange_rate_exists(self): - frappe.db.sql("""delete from `tabCurrency Exchange`""") + # NOTE: Exchangrate API has all enabled currencies that ERPNext supports. + # We aren't checking just currency exchange record anymore + # while validating price list currency exchange rate to that of company. + # The API is being used to fetch the rate which again almost always + # gives back a valid value (for valid currencies). + # This makes the test obsolete. + # Commenting because im not sure if there's a better test we can write - cart_settings = self.get_cart_settings() - cart_settings.price_list = "_Test Price List Rest of the World" - self.assertRaises(ShoppingCartSetupError, cart_settings.validate_exchange_rates_exist) + # def test_exchange_rate_exists(self): + # frappe.db.sql("""delete from `tabCurrency Exchange`""") - from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \ - currency_exchange_records - frappe.get_doc(currency_exchange_records[0]).insert() - cart_settings.validate_exchange_rates_exist() + # cart_settings = self.get_cart_settings() + # cart_settings.price_list = "_Test Price List Rest of the World" + # self.assertRaises(ShoppingCartSetupError, cart_settings.validate_price_list_exchange_rate) + + # from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \ + # currency_exchange_records + # frappe.get_doc(currency_exchange_records[0]).insert() + # cart_settings.validate_price_list_exchange_rate() def test_tax_rule_validation(self): frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index 10abde17eb2..002d3d898eb 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -13,6 +13,9 @@ class PriceList(Document): if not cint(self.buying) and not cint(self.selling): throw(_("Price List must be applicable for Buying or Selling")) + if not self.is_new(): + self.check_impact_on_shopping_cart() + def on_update(self): self.set_default_if_missing() self.update_item_price() @@ -32,6 +35,17 @@ class PriceList(Document): buying=%s, selling=%s, modified=NOW() where price_list=%s""", (self.currency, cint(self.buying), cint(self.selling), self.name)) + def check_impact_on_shopping_cart(self): + "Check if Price List currency change impacts Shopping Cart." + from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import validate_cart_settings + + doc_before_save = self.get_doc_before_save() + currency_changed = self.currency != doc_before_save.currency + affects_cart = self.name == frappe.get_cached_value("Shopping Cart Settings", None, "price_list") + + if currency_changed and affects_cart: + validate_cart_settings() + def on_trash(self): self.delete_price_list_details_key()