From 92d857d49c151e369451d07d36cf782e2f0e860b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Jan 2023 23:07:54 +0530 Subject: [PATCH 01/29] fix: purchase invoice performance issue --- .../purchase_invoice/purchase_invoice.py | 30 ++++++++++++---- .../purchase_receipt/purchase_receipt.py | 35 +++++++++++++------ .../purchase_receipt_item.json | 8 +++-- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 0e9f976106..c95dafad35 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -5,6 +5,7 @@ import frappe from frappe import _, throw from frappe.model.mapper import get_mapped_doc +from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate import erpnext @@ -1463,19 +1464,16 @@ class PurchaseInvoice(BuyingController): def update_billing_status_in_pr(self, update_modified=True): updated_pr = [] po_details = [] + + pr_details_billed_amt = self.get_pr_details_billed_amt() + for d in self.get("items"): if d.pr_detail: - billed_amt = frappe.db.sql( - """select sum(amount) from `tabPurchase Invoice Item` - where pr_detail=%s and docstatus=1""", - d.pr_detail, - ) - billed_amt = billed_amt and billed_amt[0][0] or 0 frappe.db.set_value( "Purchase Receipt Item", d.pr_detail, "billed_amt", - billed_amt, + flt(pr_details_billed_amt.get(d.pr_detail)), update_modified=update_modified, ) updated_pr.append(d.purchase_receipt) @@ -1491,6 +1489,24 @@ class PurchaseInvoice(BuyingController): pr_doc = frappe.get_doc("Purchase Receipt", pr) update_billing_percentage(pr_doc, update_modified=update_modified) + def get_pr_details_billed_amt(self): + # Get billed amount based on purchase receipt item reference (pr_detail) in purchase invoice + + pr_details_billed_amt = {} + pr_details = [d.get("pr_detail") for d in self.get("items") if d.get("pr_detail")] + if pr_details: + doctype = frappe.qb.DocType("Purchase Invoice Item") + query = ( + frappe.qb.from_(doctype) + .select(doctype.pr_detail, Sum(doctype.amount)) + .where(doctype.pr_detail.isin(pr_details) & doctype.docstatus == 1) + .groupby(doctype.pr_detail) + ) + + pr_details_billed_amt = frappe._dict(query.run(as_list=1)) + + return pr_details_billed_amt + def on_recurring(self, reference_doc, auto_repeat_doc): self.due_date = None diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 3739cb8c9d..e6025abf06 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -887,18 +887,10 @@ def update_billing_percentage(pr_doc, update_modified=True): # Update Billing % based on pending accepted qty total_amount, total_billed_amount = 0, 0 - for item in pr_doc.items: - return_data = frappe.db.get_list( - "Purchase Receipt", - fields=["sum(abs(`tabPurchase Receipt Item`.qty)) as qty"], - filters=[ - ["Purchase Receipt", "docstatus", "=", 1], - ["Purchase Receipt", "is_return", "=", 1], - ["Purchase Receipt Item", "purchase_receipt_item", "=", item.name], - ], - ) + item_wise_returned_qty = get_item_wise_returned_qty(pr_doc) - returned_qty = return_data[0].qty if return_data else 0 + for item in pr_doc.items: + returned_qty = flt(item_wise_returned_qty.get(item.name)) returned_amount = flt(returned_qty) * flt(item.rate) pending_amount = flt(item.amount) - returned_amount total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt @@ -915,6 +907,27 @@ def update_billing_percentage(pr_doc, update_modified=True): pr_doc.notify_update() +def get_item_wise_returned_qty(pr_doc): + items = [d.name for d in pr_doc.items] + + return frappe._dict( + frappe.get_all( + "Purchase Receipt", + fields=[ + "`tabPurchase Receipt Item`.purchase_receipt_item", + "sum(abs(`tabPurchase Receipt Item`.qty)) as qty", + ], + filters=[ + ["Purchase Receipt", "docstatus", "=", 1], + ["Purchase Receipt", "is_return", "=", 1], + ["Purchase Receipt Item", "purchase_receipt_item", "in", items], + ], + group_by="`tabPurchase Receipt Item`.purchase_receipt_item", + as_list=1, + ) + ) + + @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): from erpnext.accounts.party import get_payment_terms_template diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 557bb594bf..7a350b9e44 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -859,7 +859,8 @@ "label": "Purchase Receipt Item", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "collapsible": 1, @@ -974,7 +975,8 @@ "label": "Purchase Invoice Item", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "product_bundle", @@ -1010,7 +1012,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-11-02 12:49:28.746701", + "modified": "2023-01-18 15:48:58.114923", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 3e5691072aa1243e932c31e7514dafcd9cd92a43 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Feb 2023 16:24:52 +0530 Subject: [PATCH 02/29] perf: fetch SLE's on demand and memoize --- .../report/gross_profit/gross_profit.py | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index e23265b5e7..afd0328175 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -395,6 +395,7 @@ def get_column_names(): class GrossProfitGenerator(object): def __init__(self, filters=None): + self.sle = {} self.data = [] self.average_buying_rate = {} self.filters = frappe._dict(filters) @@ -404,7 +405,6 @@ class GrossProfitGenerator(object): if filters.group_by == "Invoice": self.group_items_by_invoice() - self.load_stock_ledger_entries() self.load_product_bundle() self.load_non_stock_items() self.get_returned_invoice_items() @@ -633,7 +633,7 @@ class GrossProfitGenerator(object): return flt(row.qty) * item_rate else: - my_sle = self.sle.get((item_code, row.warehouse)) + my_sle = self.get_stock_ledger_entries(item_code, row.warehouse) if (row.update_stock or row.dn_detail) and my_sle: parenttype, parent = row.parenttype, row.parent if row.dn_detail: @@ -651,7 +651,7 @@ class GrossProfitGenerator(object): dn["item_row"], dn["warehouse"], ) - my_sle = self.sle.get((item_code, warehouse)) + my_sle = self.get_stock_ledger_entries(item_code, row.warehouse) return self.calculate_buying_amount_from_sle( row, my_sle, parenttype, parent, item_row, item_code ) @@ -947,24 +947,36 @@ class GrossProfitGenerator(object): "Item", item_code, ["item_name", "description", "item_group", "brand"] ) - def load_stock_ledger_entries(self): - res = frappe.db.sql( - """select item_code, voucher_type, voucher_no, - voucher_detail_no, stock_value, warehouse, actual_qty as qty - from `tabStock Ledger Entry` - where company=%(company)s and is_cancelled = 0 - order by - item_code desc, warehouse desc, posting_date desc, - posting_time desc, creation desc""", - self.filters, - as_dict=True, - ) - self.sle = {} - for r in res: - if (r.item_code, r.warehouse) not in self.sle: - self.sle[(r.item_code, r.warehouse)] = [] + def get_stock_ledger_entries(self, item_code, warehouse): + if item_code and warehouse: + if (item_code, warehouse) not in self.sle: + sle = qb.DocType("Stock Ledger Entry") + res = ( + qb.from_(sle) + .select( + sle.item_code, + sle.voucher_type, + sle.voucher_no, + sle.voucher_detail_no, + sle.stock_value, + sle.warehouse, + sle.actual_qty.as_("qty"), + ) + .where( + (sle.company == self.filters.company) + & (sle.item_code == item_code) + & (sle.warehouse == warehouse) + & (sle.is_cancelled == 0) + ) + .orderby(sle.item_code) + .orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc) + .run(as_dict=True) + ) - self.sle[(r.item_code, r.warehouse)].append(r) + self.sle[(item_code, warehouse)] = res + + return self.sle[(item_code, warehouse)] + return [] def load_product_bundle(self): self.product_bundles = {} From 1b010add265cdc3e646fc15d78c3c39428c4ad84 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 20 Feb 2023 11:14:41 +0530 Subject: [PATCH 03/29] fix(ux): `ReferenceError: me is not defined` Delivery Note --- erpnext/stock/doctype/delivery_note/delivery_note.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index ea3cf1948b..2562c60257 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -97,12 +97,12 @@ frappe.ui.form.on("Delivery Note", { } if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) { - let internal = me.frm.doc.is_internal_customer; + let internal = frm.doc.is_internal_customer; if (internal) { - let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Receipt" : + let button_label = (frm.doc.company === frm.doc.represents_company) ? "Internal Purchase Receipt" : "Inter Company Purchase Receipt"; - me.frm.add_custom_button(button_label, function() { + frm.add_custom_button(button_label, function() { frappe.model.open_mapped_doc({ method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt', frm: frm, From 44ee9f0f190f6761ca92f4c5036109c152bd35db Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 20 Feb 2023 11:43:32 +0530 Subject: [PATCH 04/29] chore: `Linters` --- erpnext/stock/doctype/delivery_note/delivery_note.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 2562c60257..ae56645b73 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -102,7 +102,7 @@ frappe.ui.form.on("Delivery Note", { let button_label = (frm.doc.company === frm.doc.represents_company) ? "Internal Purchase Receipt" : "Inter Company Purchase Receipt"; - frm.add_custom_button(button_label, function() { + frm.add_custom_button(__(button_label), function() { frappe.model.open_mapped_doc({ method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt', frm: frm, From 0e388ba87255ca196068d55afa4839bfc321f56d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 20 Feb 2023 12:20:03 +0530 Subject: [PATCH 05/29] fix: inventory dimension filter not overriding with existing filter for stock ledger report --- erpnext/public/js/utils.js | 9 +++++++-- erpnext/stock/report/stock_ledger/stock_ledger.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 51dcd64d9d..58aa8d7da2 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -221,9 +221,9 @@ $.extend(erpnext.utils, { callback: function(r) { if (r.message && r.message.length) { r.message.forEach((dimension) => { - let found = filters.some(el => el.fieldname === dimension['fieldname']); + let existing_filter = filters.filter(el => el.fieldname === dimension['fieldname']); - if (!found) { + if (!existing_filter.length) { filters.splice(index, 0, { "fieldname": dimension["fieldname"], "label": __(dimension["doctype"]), @@ -232,6 +232,11 @@ $.extend(erpnext.utils, { return frappe.db.get_link_options(dimension["doctype"], txt); }, }); + } else { + existing_filter[0]['fieldtype'] = "MultiSelectList"; + existing_filter[0]['get_data'] = function(txt) { + return frappe.db.get_link_options(dimension["doctype"], txt); + } } }); } diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 8b63c0f998..da17cdeb5a 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -306,7 +306,7 @@ def get_stock_ledger_entries(filters, items): query = query.where(sle.item_code.isin(items)) for field in ["voucher_no", "batch_no", "project", "company"]: - if filters.get(field): + if filters.get(field) and field not in inventory_dimension_fields: query = query.where(sle[field] == filters.get(field)) query = apply_warehouse_filter(query, sle, filters) From 3f2a2c96f610ec592feec2eca5aedec95da2c2f1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Feb 2023 16:07:27 +0530 Subject: [PATCH 06/29] fix: Add company perm check unconditionally (#34142) fix: Add employee perm check unconditionally --- erpnext/setup/doctype/employee/employee.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index facefa376a..ece5a7d554 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -8,7 +8,6 @@ from frappe.permissions import ( get_doc_permissions, has_permission, remove_user_permission, - set_user_permission_if_allowed, ) from frappe.utils import cstr, getdate, today, validate_email_address from frappe.utils.nestedset import NestedSet @@ -96,7 +95,7 @@ class Employee(NestedSet): return add_user_permission("Employee", self.name, self.user_id) - set_user_permission_if_allowed("Company", self.company, self.user_id) + add_user_permission("Company", self.company, self.user_id) def update_user(self): # add employee role if missing From 35cdd996a9e4281ed269655ff5d9e22e866e199e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 21 Feb 2023 12:57:49 +0530 Subject: [PATCH 07/29] fix: Use normal rounding for Tax Withholding Category (#34114) --- .../tax_withholding_category.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 2c829b258b..f0146ea70e 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -278,7 +278,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers) if cint(tax_details.round_off_tax_amount): - tax_amount = round(tax_amount) + tax_amount = normal_round(tax_amount) return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount @@ -603,3 +603,20 @@ def is_valid_certificate( valid = True return valid + + +def normal_round(number): + """ + Rounds a number to the nearest integer. + :param number: The number to round. + """ + decimal_part = number - int(number) + + if decimal_part >= 0.5: + decimal_part = 1 + else: + decimal_part = 0 + + number = int(number) + decimal_part + + return number From 76861eb332061c2e42c75e5ba42f01128024808a Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 21 Feb 2023 13:04:01 +0530 Subject: [PATCH 08/29] fix: fiscal year error for existing assets in fixed asset register --- .../assets/report/fixed_asset_register/fixed_asset_register.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 51a6a86e9f..59d43b1ea6 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -151,6 +151,7 @@ def prepare_chart_data(data, filters): filters.filter_based_on, "Monthly", company=filters.company, + ignore_fiscal_year=True, ) for d in period_list: From 3547252217ac141c4e57de92fc4aebd7d430ec6e Mon Sep 17 00:00:00 2001 From: Himanshu Shivhare Date: Tue, 21 Feb 2023 13:05:03 +0530 Subject: [PATCH 09/29] Telegram Group link updated (#34149) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0708266a47..44bd729688 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB 1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. 2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext. 3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers. -4. [Telegram Group](https://t.me/erpnexthelp) - Get instant help from huge community of users. +4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users. ## Contributing From c88444a6c488369dfbfffdd71e76d5125afa2226 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 21 Feb 2023 14:26:33 +0530 Subject: [PATCH 10/29] fix: Filters in item-wise sales history report (#34145) --- .../report/item_wise_sales_history/item_wise_sales_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 44c4d5497b..2624db3191 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -216,7 +216,7 @@ def get_sales_order_details(company_list, filters): ) if filters.get("item_group"): - query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_group)) + query = query.where(db_so_item.item_group == filters.item_group) if filters.get("from_date"): query = query.where(db_so.transaction_date >= filters.from_date) @@ -225,7 +225,7 @@ def get_sales_order_details(company_list, filters): query = query.where(db_so.transaction_date <= filters.to_date) if filters.get("item_code"): - query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_code)) + query = query.where(db_so_item.item_code == filters.item_code) if filters.get("customer"): query = query.where(db_so.customer == filters.customer) From 4a7b1de2d826cdb9580388018b0571e32c8ad11d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 21 Feb 2023 14:27:00 +0530 Subject: [PATCH 11/29] refactor: clear records in batches in 'Transaction Deletion Record' (#34109) refactor: clear records in batches --- .../transaction_deletion_record.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 4256a7d831..481a3a5ebe 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -3,13 +3,17 @@ import frappe -from frappe import _ +from frappe import _, qb from frappe.desk.notifications import clear_notifications from frappe.model.document import Document -from frappe.utils import cint +from frappe.utils import cint, create_batch class TransactionDeletionRecord(Document): + def __init__(self, *args, **kwargs): + super(TransactionDeletionRecord, self).__init__(*args, **kwargs) + self.batch_size = 5000 + def validate(self): frappe.only_for("System Manager") self.validate_doctypes_to_be_ignored() @@ -155,8 +159,9 @@ class TransactionDeletionRecord(Document): "DocField", filters={"fieldtype": "Table", "parent": doctype}, pluck="options" ) - for table in child_tables: - frappe.db.delete(table, {"parent": ["in", parent_docs_to_be_deleted]}) + for batch in create_batch(parent_docs_to_be_deleted, self.batch_size): + for table in child_tables: + frappe.db.delete(table, {"parent": ["in", batch]}) def delete_docs_linked_with_specified_company(self, doctype, company_fieldname): frappe.db.delete(doctype, {company_fieldname: self.company}) @@ -181,13 +186,16 @@ class TransactionDeletionRecord(Document): frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix)) def delete_version_log(self, doctype, company_fieldname): - frappe.db.sql( - """delete from `tabVersion` where ref_doctype=%s and docname in - (select name from `tab{0}` where `{1}`=%s)""".format( - doctype, company_fieldname - ), - (doctype, self.company), - ) + dt = qb.DocType(doctype) + names = qb.from_(dt).select(dt.name).where(dt[company_fieldname] == self.company).run(as_list=1) + names = [x[0] for x in names] + + if names: + versions = qb.DocType("Version") + for batch in create_batch(names, self.batch_size): + qb.from_(versions).delete().where( + (versions.ref_doctype == doctype) & (versions.docname.isin(batch)) + ).run() def delete_communications(self, doctype, company_fieldname): reference_docs = frappe.get_all(doctype, filters={company_fieldname: self.company}) @@ -199,7 +207,8 @@ class TransactionDeletionRecord(Document): ) communication_names = [c.name for c in communications] - frappe.delete_doc("Communication", communication_names, ignore_permissions=True) + for batch in create_batch(communication_names, self.batch_size): + frappe.delete_doc("Communication", batch, ignore_permissions=True) @frappe.whitelist() From 47add0b7514b701ef43da810986c73c2852d6b97 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Feb 2023 20:42:45 +0530 Subject: [PATCH 12/29] fix: check for duplicate in pos closing and pos merge log entry --- .../pos_closing_entry/pos_closing_entry.py | 16 ++++++++++++++++ .../pos_invoice_merge_log.py | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 655c4ec003..115b415eed 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -21,8 +21,24 @@ class POSClosingEntry(StatusUpdater): if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) + self.validate_duplicate_pos_invoices() self.validate_pos_invoices() + def validate_duplicate_pos_invoices(self): + pos_occurences = {} + for idx, inv in enumerate(self.pos_transactions, 1): + pos_occurences.setdefault(inv.pos_invoice, []).append(idx) + + error_list = [] + for key, value in pos_occurences.items(): + if len(value) > 1: + error_list.append( + _("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value))) + ) + + if error_list: + frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True) + def validate_pos_invoices(self): invalid_rows = [] for d in self.pos_transactions: diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 3a237a43a3..b1e22087db 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -17,6 +17,22 @@ class POSInvoiceMergeLog(Document): def validate(self): self.validate_customer() self.validate_pos_invoice_status() + self.validate_duplicate_pos_invoices() + + def validate_duplicate_pos_invoices(self): + pos_occurences = {} + for idx, inv in enumerate(self.pos_invoices, 1): + pos_occurences.setdefault(inv.pos_invoice, []).append(idx) + + error_list = [] + for key, value in pos_occurences.items(): + if len(value) > 1: + error_list.append( + _("{} is added multiple times on rows: {}".format(frappe.bold(key), frappe.bold(value))) + ) + + if error_list: + frappe.throw(error_list, title=_("Duplicate POS Invoices found"), as_list=True) def validate_customer(self): if self.merge_invoices_based_on == "Customer Group": @@ -425,6 +441,8 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): if closing_entry: closing_entry.set_status(update=True, status="Failed") + if type(error_message) == list: + error_message = frappe.json.dumps(error_message) closing_entry.db_set("error_message", error_message) raise From a06d24037b79887662ed2ffaa3c5bde55c3590dd Mon Sep 17 00:00:00 2001 From: Sankara Subramanian V Date: Tue, 21 Feb 2023 22:52:20 +0530 Subject: [PATCH 13/29] refactor(region): Splitting of KSA(Saudi Arabia) Regional logic from ERPNext (#33895) * feat: remove KSA regional code --- erpnext/hooks.py | 8 +- erpnext/patches.txt | 6 +- .../v13_0/create_ksa_vat_custom_fields.py | 11 - .../disable_ksa_print_format_for_others.py | 19 -- erpnext/patches/v13_0/enable_ksa_vat_docs.py | 12 - erpnext/patches/v13_0/rename_ksa_qr_field.py | 36 --- .../patches/v15_0/delete_saudi_doctypes.py | 25 ++ .../v15_0/saudi_depreciation_warning.py | 12 + .../ksa_vat_purchase_account/__init__.py | 0 .../ksa_vat_purchase_account.json | 49 ---- .../ksa_vat_purchase_account.py | 9 - .../doctype/ksa_vat_sales_account/__init__.py | 0 .../ksa_vat_sales_account.js | 8 - .../ksa_vat_sales_account.json | 49 ---- .../ksa_vat_sales_account.py | 9 - .../test_ksa_vat_sales_account.py | 9 - .../doctype/ksa_vat_setting/__init__.py | 0 .../ksa_vat_setting/ksa_vat_setting.js | 8 - .../ksa_vat_setting/ksa_vat_setting.json | 49 ---- .../ksa_vat_setting/ksa_vat_setting.py | 9 - .../ksa_vat_setting/ksa_vat_setting_list.js | 5 - .../ksa_vat_setting/test_ksa_vat_setting.py | 9 - .../print_format/ksa_pos_invoice/__init__.py | 0 .../ksa_pos_invoice/ksa_pos_invoice.json | 32 --- .../print_format/ksa_vat_invoice/__init__.py | 0 .../ksa_vat_invoice/ksa_vat_invoice.json | 32 --- erpnext/regional/report/ksa_vat/__init__.py | 0 erpnext/regional/report/ksa_vat/ksa_vat.js | 59 ----- erpnext/regional/report/ksa_vat/ksa_vat.json | 32 --- erpnext/regional/report/ksa_vat/ksa_vat.py | 231 ------------------ erpnext/regional/saudi_arabia/__init__.py | 0 erpnext/regional/saudi_arabia/setup.py | 173 ------------- erpnext/regional/saudi_arabia/utils.py | 169 ------------- .../regional/saudi_arabia/wizard/__init__.py | 0 .../saudi_arabia/wizard/data/__init__.py | 0 .../wizard/data/ksa_vat_settings.json | 47 ---- .../wizard/operations/__init__.py | 0 .../operations/setup_ksa_vat_setting.py | 46 ---- .../setup_wizard/data/country_wise_tax.json | 28 --- 39 files changed, 40 insertions(+), 1151 deletions(-) delete mode 100644 erpnext/patches/v13_0/create_ksa_vat_custom_fields.py delete mode 100644 erpnext/patches/v13_0/disable_ksa_print_format_for_others.py delete mode 100644 erpnext/patches/v13_0/enable_ksa_vat_docs.py delete mode 100644 erpnext/patches/v13_0/rename_ksa_qr_field.py create mode 100644 erpnext/patches/v15_0/delete_saudi_doctypes.py create mode 100644 erpnext/patches/v15_0/saudi_depreciation_warning.py delete mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py delete mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json delete mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py delete mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/__init__.py delete mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js delete mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json delete mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py delete mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py delete mode 100644 erpnext/regional/doctype/ksa_vat_setting/__init__.py delete mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js delete mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json delete mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py delete mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js delete mode 100644 erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py delete mode 100644 erpnext/regional/print_format/ksa_pos_invoice/__init__.py delete mode 100644 erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json delete mode 100644 erpnext/regional/print_format/ksa_vat_invoice/__init__.py delete mode 100644 erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json delete mode 100644 erpnext/regional/report/ksa_vat/__init__.py delete mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.js delete mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.json delete mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.py delete mode 100644 erpnext/regional/saudi_arabia/__init__.py delete mode 100644 erpnext/regional/saudi_arabia/setup.py delete mode 100644 erpnext/regional/saudi_arabia/utils.py delete mode 100644 erpnext/regional/saudi_arabia/wizard/__init__.py delete mode 100644 erpnext/regional/saudi_arabia/wizard/data/__init__.py delete mode 100644 erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json delete mode 100644 erpnext/regional/saudi_arabia/wizard/operations/__init__.py delete mode 100644 erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fd19d2585c..fba886ca2c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -311,15 +311,10 @@ doc_events = { "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", - "erpnext.regional.saudi_arabia.utils.create_qr_code", - ], - "on_cancel": [ - "erpnext.regional.italy.utils.sales_invoice_on_cancel", - "erpnext.regional.saudi_arabia.utils.delete_qr_code_file", ], + "on_cancel": ["erpnext.regional.italy.utils.sales_invoice_on_cancel"], "on_trash": "erpnext.regional.check_deletion_permission", }, - "POS Invoice": {"on_submit": ["erpnext.regional.saudi_arabia.utils.create_qr_code"]}, "Purchase Invoice": { "validate": [ "erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm", @@ -347,7 +342,6 @@ doc_events = { "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" }, - "Company": {"on_trash": ["erpnext.regional.saudi_arabia.utils.delete_vat_settings_for_company"]}, "Integration Request": { "validate": "erpnext.accounts.doctype.payment_request.payment_request.validate_payment" }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e6be933f25..2abd65b1b5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -250,18 +250,14 @@ erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit -erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022 erpnext.patches.v14_0.migrate_crm_settings -erpnext.patches.v13_0.rename_ksa_qr_field erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty -erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 erpnext.patches.v13_0.agriculture_deprecation_warning erpnext.patches.v13_0.hospitality_deprecation_warning erpnext.patches.v13_0.update_asset_quantity_field erpnext.patches.v13_0.delete_bank_reconciliation_detail erpnext.patches.v13_0.enable_provisional_accounting erpnext.patches.v13_0.non_profit_deprecation_warning -erpnext.patches.v13_0.enable_ksa_vat_docs #1 erpnext.patches.v13_0.show_india_localisation_deprecation_warning erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.reset_corrupt_defaults @@ -269,6 +265,8 @@ erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair erpnext.patches.v15_0.delete_taxjar_doctypes erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets erpnext.patches.v14_0.update_reference_due_date_in_journal_entry +erpnext.patches.v15_0.saudi_depreciation_warning +erpnext.patches.v15_0.delete_saudi_doctypes [post_model_sync] execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') diff --git a/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py deleted file mode 100644 index 093463a12e..0000000000 --- a/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py +++ /dev/null @@ -1,11 +0,0 @@ -import frappe - -from erpnext.regional.saudi_arabia.setup import make_custom_fields - - -def execute(): - company = frappe.get_all("Company", filters={"country": "Saudi Arabia"}) - if not company: - return - - make_custom_fields() diff --git a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py b/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py deleted file mode 100644 index 84b6c37dd9..0000000000 --- a/erpnext/patches/v13_0/disable_ksa_print_format_for_others.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2020, Wahni Green Technologies and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe - -from erpnext.regional.saudi_arabia.setup import add_print_formats - - -def execute(): - company = frappe.get_all("Company", filters={"country": "Saudi Arabia"}) - if company: - add_print_formats() - return - - if frappe.db.exists("DocType", "Print Format"): - frappe.reload_doc("regional", "print_format", "ksa_vat_invoice", force=True) - frappe.reload_doc("regional", "print_format", "ksa_pos_invoice", force=True) - for d in ("KSA VAT Invoice", "KSA POS Invoice"): - frappe.db.set_value("Print Format", d, "disabled", 1) diff --git a/erpnext/patches/v13_0/enable_ksa_vat_docs.py b/erpnext/patches/v13_0/enable_ksa_vat_docs.py deleted file mode 100644 index 4adf4d71db..0000000000 --- a/erpnext/patches/v13_0/enable_ksa_vat_docs.py +++ /dev/null @@ -1,12 +0,0 @@ -import frappe - -from erpnext.regional.saudi_arabia.setup import add_permissions, add_print_formats - - -def execute(): - company = frappe.get_all("Company", filters={"country": "Saudi Arabia"}) - if not company: - return - - add_print_formats() - add_permissions() diff --git a/erpnext/patches/v13_0/rename_ksa_qr_field.py b/erpnext/patches/v13_0/rename_ksa_qr_field.py deleted file mode 100644 index e4b91412ee..0000000000 --- a/erpnext/patches/v13_0/rename_ksa_qr_field.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2020, Wahni Green Technologies and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.model.utils.rename_field import rename_field - - -def execute(): - company = frappe.get_all("Company", filters={"country": "Saudi Arabia"}) - if not company: - return - - if frappe.db.exists("DocType", "Sales Invoice"): - frappe.reload_doc("accounts", "doctype", "sales_invoice", force=True) - - # rename_field method assumes that the field already exists or the doc is synced - if not frappe.db.has_column("Sales Invoice", "ksa_einv_qr"): - create_custom_fields( - { - "Sales Invoice": [ - dict( - fieldname="ksa_einv_qr", - label="KSA E-Invoicing QR", - fieldtype="Attach Image", - read_only=1, - no_copy=1, - hidden=1, - ) - ] - } - ) - - if frappe.db.has_column("Sales Invoice", "qr_code"): - rename_field("Sales Invoice", "qr_code", "ksa_einv_qr") - frappe.delete_doc_if_exists("Custom Field", "Sales Invoice-qr_code") diff --git a/erpnext/patches/v15_0/delete_saudi_doctypes.py b/erpnext/patches/v15_0/delete_saudi_doctypes.py new file mode 100644 index 0000000000..371e335290 --- /dev/null +++ b/erpnext/patches/v15_0/delete_saudi_doctypes.py @@ -0,0 +1,25 @@ +import click +import frappe + + +def execute(): + if "ksa" in frappe.get_installed_apps(): + return + + doctypes = ["KSA VAT Setting", "KSA VAT Purchase Account", "KSA VAT Sales Account"] + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + print_formats = ["KSA POS Invoice", "KSA VAT Invoice"] + for print_format in print_formats: + frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True) + + reports = ["KSA VAT"] + for report in reports: + frappe.delete_doc("Report", report, ignore_missing=True, force=True) + + click.secho( + "Region Saudi Arabia(KSA) is moved to a separate app" + "Please install the app to continue using the module: https://github.com/8848digital/KSA", + fg="yellow", + ) diff --git a/erpnext/patches/v15_0/saudi_depreciation_warning.py b/erpnext/patches/v15_0/saudi_depreciation_warning.py new file mode 100644 index 0000000000..6af8efe5ff --- /dev/null +++ b/erpnext/patches/v15_0/saudi_depreciation_warning.py @@ -0,0 +1,12 @@ +import click +import frappe + + +def execute(): + if "ksa" in frappe.get_installed_apps(): + return + click.secho( + "Region Saudi Arabia(KSA) is moved to a separate app\n" + "Please install the app to continue using the KSA Features: https://github.com/8848digital/KSA", + fg="yellow", + ) diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json deleted file mode 100644 index 89ba3e977a..0000000000 --- a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "actions": [], - "creation": "2021-07-13 09:17:09.862163", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "title", - "item_tax_template", - "account" - ], - "fields": [ - { - "fieldname": "account", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Account", - "options": "Account", - "reqd": 1 - }, - { - "fieldname": "title", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Title", - "reqd": 1 - }, - { - "fieldname": "item_tax_template", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Tax Template", - "options": "Item Tax Template", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-08-04 06:42:38.205597", - "modified_by": "Administrator", - "module": "Regional", - "name": "KSA VAT Purchase Account", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py deleted file mode 100644 index 3920bc546c..0000000000 --- a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Havenir Solutions and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class KSAVATPurchaseAccount(Document): - pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js deleted file mode 100644 index 72613f4064..0000000000 --- a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2021, Havenir Solutions and contributors -// For license information, please see license.txt - -frappe.ui.form.on('KSA VAT Sales Account', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json deleted file mode 100644 index df2747891d..0000000000 --- a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "actions": [], - "creation": "2021-07-13 08:46:33.820968", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "title", - "item_tax_template", - "account" - ], - "fields": [ - { - "fieldname": "account", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Account", - "options": "Account", - "reqd": 1 - }, - { - "fieldname": "title", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Title", - "reqd": 1 - }, - { - "fieldname": "item_tax_template", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Tax Template", - "options": "Item Tax Template", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-08-04 06:42:00.081407", - "modified_by": "Administrator", - "module": "Regional", - "name": "KSA VAT Sales Account", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py deleted file mode 100644 index 7c2689f530..0000000000 --- a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Havenir Solutions and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class KSAVATSalesAccount(Document): - pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py deleted file mode 100644 index 1d6a6a793d..0000000000 --- a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Havenir Solutions and Contributors -# See license.txt - -# import frappe -import unittest - - -class TestKSAVATSalesAccount(unittest.TestCase): - pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js deleted file mode 100644 index 00b62b9adf..0000000000 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2021, Havenir Solutions and contributors -// For license information, please see license.txt - -frappe.ui.form.on('KSA VAT Setting', { - onload: function () { - frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); - } -}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json deleted file mode 100644 index 33619467ed..0000000000 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "actions": [], - "autoname": "field:company", - "creation": "2021-07-13 08:49:01.100356", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "company", - "ksa_vat_sales_accounts", - "ksa_vat_purchase_accounts" - ], - "fields": [ - { - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "options": "Company", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "ksa_vat_sales_accounts", - "fieldtype": "Table", - "label": "KSA VAT Sales Accounts", - "options": "KSA VAT Sales Account", - "reqd": 1 - }, - { - "fieldname": "ksa_vat_purchase_accounts", - "fieldtype": "Table", - "label": "KSA VAT Purchase Accounts", - "options": "KSA VAT Purchase Account", - "reqd": 1 - } - ], - "links": [], - "modified": "2021-08-26 04:29:06.499378", - "modified_by": "Administrator", - "module": "Regional", - "name": "KSA VAT Setting", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "company", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py deleted file mode 100644 index bdae1161fd..0000000000 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Havenir Solutions and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class KSAVATSetting(Document): - pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js deleted file mode 100644 index 269cbec5fb..0000000000 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js +++ /dev/null @@ -1,5 +0,0 @@ -frappe.listview_settings['KSA VAT Setting'] = { - onload () { - frappe.breadcrumbs.add('Accounts'); - } -} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py deleted file mode 100644 index 7207901fd4..0000000000 --- a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Havenir Solutions and Contributors -# See license.txt - -# import frappe -import unittest - - -class TestKSAVATSetting(unittest.TestCase): - pass diff --git a/erpnext/regional/print_format/ksa_pos_invoice/__init__.py b/erpnext/regional/print_format/ksa_pos_invoice/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json b/erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json deleted file mode 100644 index c2a309231d..0000000000 --- a/erpnext/regional/print_format/ksa_pos_invoice/ksa_pos_invoice.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "absolute_value": 0, - "align_labels_right": 0, - "creation": "2021-12-07 13:25:05.424827", - "css": "", - "custom_format": 1, - "default_print_language": "en", - "disabled": 1, - "doc_type": "POS Invoice", - "docstatus": 0, - "doctype": "Print Format", - "font_size": 0, - "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

\n\t{{ doc.company }}
\n\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t\n

\n

\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
\n

\n\n
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
{{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
{{ _(\"SR.No\") }}:
\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
{{ item.qty }}{{ item.get_formatted(\"net_amount\") }}
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
\n\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t
\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
\n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
\n
\n

{{ doc.terms or \"\" }}

\n

{{ _(\"Thank you, please visit again.\") }}

", - "idx": 0, - "line_breaks": 0, - "margin_bottom": 0.0, - "margin_left": 0.0, - "margin_right": 0.0, - "margin_top": 0.0, - "modified": "2021-12-08 10:25:01.930885", - "modified_by": "Administrator", - "module": "Regional", - "name": "KSA POS Invoice", - "owner": "Administrator", - "page_number": "Hide", - "print_format_builder": 0, - "print_format_builder_beta": 0, - "print_format_type": "Jinja", - "raw_printing": 0, - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/regional/print_format/ksa_vat_invoice/__init__.py b/erpnext/regional/print_format/ksa_vat_invoice/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json deleted file mode 100644 index 6b64d47453..0000000000 --- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "absolute_value": 0, - "align_labels_right": 0, - "creation": "2021-10-29 22:46:26.039023", - "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", - "custom_format": 1, - "default_print_language": "en", - "disabled": 1, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "font_size": 14, - "html": "
\n
\n
\n

TAX INVOICE

\n

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

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

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", - "idx": 0, - "line_breaks": 0, - "margin_bottom": 15.0, - "margin_left": 15.0, - "margin_right": 15.0, - "margin_top": 15.0, - "modified": "2021-12-07 13:43:38.018593", - "modified_by": "Administrator", - "module": "Regional", - "name": "KSA VAT Invoice", - "owner": "Administrator", - "page_number": "Hide", - "print_format_builder": 0, - "print_format_builder_beta": 0, - "print_format_type": "Jinja", - "raw_printing": 0, - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js deleted file mode 100644 index 59e72c3e63..0000000000 --- a/erpnext/regional/report/ksa_vat/ksa_vat.js +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2016, Havenir Solutions and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["KSA VAT"] = { - onload() { - frappe.breadcrumbs.add('Accounts'); - }, - "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(), -1), - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "reqd": 1, - "default": frappe.datetime.get_today() - } - ], - "formatter": function(value, row, column, data, default_formatter) { - if (data - && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') - && data.title==value) { - value = $(`${value}`); - var $value = $(value).css("font-weight", "bold"); - value = $value.wrap("

").parent().html(); - return value - }else if (data.title=='Grand Total'){ - if (data.title==value) { - value = $(`${value}`); - var $value = $(value).css("font-weight", "bold"); - value = $value.wrap("

").parent().html(); - return value - }else{ - value = default_formatter(value, row, column, data); - value = $(`${value}`); - var $value = $(value).css("font-weight", "bold"); - value = $value.wrap("

").parent().html(); - return value - } - }else{ - value = default_formatter(value, row, column, data); - return value; - } - }, -}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json deleted file mode 100644 index 036e260310..0000000000 --- a/erpnext/regional/report/ksa_vat/ksa_vat.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "add_total_row": 0, - "columns": [], - "creation": "2021-07-13 08:54:38.000949", - "disable_prepared_report": 1, - "disabled": 1, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "modified": "2021-08-26 04:14:37.202594", - "modified_by": "Administrator", - "module": "Regional", - "name": "KSA VAT", - "owner": "Administrator", - "prepared_report": 1, - "ref_doctype": "GL Entry", - "report_name": "KSA VAT", - "report_type": "Script Report", - "roles": [ - { - "role": "System Manager" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Accounts User" - } - ] -} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py deleted file mode 100644 index 15996d2d1f..0000000000 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright (c) 2013, Havenir Solutions and contributors -# For license information, please see license.txt - - -import json - -import frappe -from frappe import _ -from frappe.utils import get_url_to_list - - -def execute(filters=None): - columns = columns = get_columns() - data = get_data(filters) - return columns, data - - -def get_columns(): - return [ - { - "fieldname": "title", - "label": _("Title"), - "fieldtype": "Data", - "width": 300, - }, - { - "fieldname": "amount", - "label": _("Amount (SAR)"), - "fieldtype": "Currency", - "options": "currency", - "width": 150, - }, - { - "fieldname": "adjustment_amount", - "label": _("Adjustment (SAR)"), - "fieldtype": "Currency", - "options": "currency", - "width": 150, - }, - { - "fieldname": "vat_amount", - "label": _("VAT Amount (SAR)"), - "fieldtype": "Currency", - "options": "currency", - "width": 150, - }, - { - "fieldname": "currency", - "label": _("Currency"), - "fieldtype": "Currency", - "width": 150, - "hidden": 1, - }, - ] - - -def get_data(filters): - data = [] - - # Validate if vat settings exist - company = filters.get("company") - company_currency = frappe.get_cached_value("Company", company, "default_currency") - - if frappe.db.exists("KSA VAT Setting", company) is None: - url = get_url_to_list("KSA VAT Setting") - frappe.msgprint(_('Create KSA VAT Setting for this company').format(url)) - return data - - ksa_vat_setting = frappe.get_doc("KSA VAT Setting", company) - - # Sales Heading - append_data(data, "VAT on Sales", "", "", "", company_currency) - - grand_total_taxable_amount = 0 - grand_total_taxable_adjustment_amount = 0 - grand_total_tax = 0 - - for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: - ( - total_taxable_amount, - total_taxable_adjustment_amount, - total_tax, - ) = get_tax_data_for_each_vat_setting(vat_setting, filters, "Sales Invoice") - - # Adding results to data - append_data( - data, - vat_setting.title, - total_taxable_amount, - total_taxable_adjustment_amount, - total_tax, - company_currency, - ) - - grand_total_taxable_amount += total_taxable_amount - grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount - grand_total_tax += total_tax - - # Sales Grand Total - append_data( - data, - "Grand Total", - grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, - grand_total_tax, - company_currency, - ) - - # Blank Line - append_data(data, "", "", "", "", company_currency) - - # Purchase Heading - append_data(data, "VAT on Purchases", "", "", "", company_currency) - - grand_total_taxable_amount = 0 - grand_total_taxable_adjustment_amount = 0 - grand_total_tax = 0 - - for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: - ( - total_taxable_amount, - total_taxable_adjustment_amount, - total_tax, - ) = get_tax_data_for_each_vat_setting(vat_setting, filters, "Purchase Invoice") - - # Adding results to data - append_data( - data, - vat_setting.title, - total_taxable_amount, - total_taxable_adjustment_amount, - total_tax, - company_currency, - ) - - grand_total_taxable_amount += total_taxable_amount - grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount - grand_total_tax += total_tax - - # Purchase Grand Total - append_data( - data, - "Grand Total", - grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, - grand_total_tax, - company_currency, - ) - - return data - - -def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): - """ - (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n - calculates and returns \n - total_taxable_amount, total_taxable_adjustment_amount, total_tax""" - from_date = filters.get("from_date") - to_date = filters.get("to_date") - - # Initiate variables - total_taxable_amount = 0 - total_taxable_adjustment_amount = 0 - total_tax = 0 - # Fetch All Invoices - invoices = frappe.get_all( - doctype, - filters={"docstatus": 1, "posting_date": ["between", [from_date, to_date]]}, - fields=["name", "is_return"], - ) - - for invoice in invoices: - invoice_items = frappe.get_all( - f"{doctype} Item", - filters={ - "docstatus": 1, - "parent": invoice.name, - "item_tax_template": vat_setting.item_tax_template, - }, - fields=["item_code", "net_amount"], - ) - - for item in invoice_items: - # Summing up total taxable amount - if invoice.is_return == 0: - total_taxable_amount += item.net_amount - - if invoice.is_return == 1: - total_taxable_adjustment_amount += item.net_amount - - # Summing up total tax - total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) - - return total_taxable_amount, total_taxable_adjustment_amount, total_tax - - -def append_data(data, title, amount, adjustment_amount, vat_amount, company_currency): - """Returns data with appended value.""" - data.append( - { - "title": _(title), - "amount": amount, - "adjustment_amount": adjustment_amount, - "vat_amount": vat_amount, - "currency": company_currency, - } - ) - - -def get_tax_amount(item_code, account_head, doctype, parent): - if doctype == "Sales Invoice": - tax_doctype = "Sales Taxes and Charges" - - elif doctype == "Purchase Invoice": - tax_doctype = "Purchase Taxes and Charges" - - item_wise_tax_detail = frappe.get_value( - tax_doctype, - {"docstatus": 1, "parent": parent, "account_head": account_head}, - "item_wise_tax_detail", - ) - - tax_amount = 0 - if item_wise_tax_detail and len(item_wise_tax_detail) > 0: - item_wise_tax_detail = json.loads(item_wise_tax_detail) - for key, value in item_wise_tax_detail.items(): - if key == item_code: - tax_amount = value[1] - break - - return tax_amount diff --git a/erpnext/regional/saudi_arabia/__init__.py b/erpnext/regional/saudi_arabia/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py deleted file mode 100644 index 7f41c462cc..0000000000 --- a/erpnext/regional/saudi_arabia/setup.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe.permissions import add_permission, update_permission_property -from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import ( - create_ksa_vat_setting, -) -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields - - -def setup(company=None, patch=True): - add_print_formats() - add_permissions() - make_custom_fields() - - -def add_print_formats(): - frappe.reload_doc("regional", "print_format", "detailed_tax_invoice", force=True) - frappe.reload_doc("regional", "print_format", "simplified_tax_invoice", force=True) - frappe.reload_doc("regional", "print_format", "tax_invoice", force=True) - frappe.reload_doc("regional", "print_format", "ksa_vat_invoice", force=True) - frappe.reload_doc("regional", "print_format", "ksa_pos_invoice", force=True) - - for d in ( - "Simplified Tax Invoice", - "Detailed Tax Invoice", - "Tax Invoice", - "KSA VAT Invoice", - "KSA POS Invoice", - ): - frappe.db.set_value("Print Format", d, "disabled", 0) - - -def add_permissions(): - """Add Permissions for KSA VAT Setting.""" - add_permission("KSA VAT Setting", "All", 0) - for role in ("Accounts Manager", "Accounts User", "System Manager"): - add_permission("KSA VAT Setting", role, 0) - update_permission_property("KSA VAT Setting", role, 0, "write", 1) - update_permission_property("KSA VAT Setting", role, 0, "create", 1) - - """Enable KSA VAT Report""" - frappe.db.set_value("Report", "KSA VAT", "disabled", 0) - - -def make_custom_fields(): - """Create Custom fields - - QR code Image file - - Company Name in Arabic - - Address in Arabic - """ - is_zero_rated = dict( - fieldname="is_zero_rated", - label="Is Zero Rated", - fieldtype="Check", - fetch_from="item_code.is_zero_rated", - insert_after="description", - print_hide=1, - ) - - is_exempt = dict( - fieldname="is_exempt", - label="Is Exempt", - fieldtype="Check", - fetch_from="item_code.is_exempt", - insert_after="is_zero_rated", - print_hide=1, - ) - - purchase_invoice_fields = [ - dict( - fieldname="company_trn", - label="Company TRN", - fieldtype="Read Only", - insert_after="shipping_address", - fetch_from="company.tax_id", - print_hide=1, - ), - dict( - fieldname="supplier_name_in_arabic", - label="Supplier Name in Arabic", - fieldtype="Read Only", - insert_after="supplier_name", - fetch_from="supplier.supplier_name_in_arabic", - print_hide=1, - ), - ] - - sales_invoice_fields = [ - dict( - fieldname="company_trn", - label="Company TRN", - fieldtype="Read Only", - insert_after="company_address", - fetch_from="company.tax_id", - print_hide=1, - ), - dict( - fieldname="customer_name_in_arabic", - label="Customer Name in Arabic", - fieldtype="Read Only", - insert_after="customer_name", - fetch_from="customer.customer_name_in_arabic", - print_hide=1, - ), - dict( - fieldname="ksa_einv_qr", - label="KSA E-Invoicing QR", - fieldtype="Attach Image", - read_only=1, - no_copy=1, - hidden=1, - ), - ] - - custom_fields = { - "Item": [is_zero_rated, is_exempt], - "Customer": [ - dict( - fieldname="customer_name_in_arabic", - label="Customer Name in Arabic", - fieldtype="Data", - insert_after="customer_name", - ), - ], - "Supplier": [ - dict( - fieldname="supplier_name_in_arabic", - label="Supplier Name in Arabic", - fieldtype="Data", - insert_after="supplier_name", - ), - ], - "Purchase Invoice": purchase_invoice_fields, - "Purchase Order": purchase_invoice_fields, - "Purchase Receipt": purchase_invoice_fields, - "Sales Invoice": sales_invoice_fields, - "POS Invoice": sales_invoice_fields, - "Sales Order": sales_invoice_fields, - "Delivery Note": sales_invoice_fields, - "Sales Invoice Item": [is_zero_rated, is_exempt], - "POS Invoice Item": [is_zero_rated, is_exempt], - "Purchase Invoice Item": [is_zero_rated, is_exempt], - "Sales Order Item": [is_zero_rated, is_exempt], - "Delivery Note Item": [is_zero_rated, is_exempt], - "Quotation Item": [is_zero_rated, is_exempt], - "Purchase Order Item": [is_zero_rated, is_exempt], - "Purchase Receipt Item": [is_zero_rated, is_exempt], - "Supplier Quotation Item": [is_zero_rated, is_exempt], - "Address": [ - dict( - fieldname="address_in_arabic", - label="Address in Arabic", - fieldtype="Data", - insert_after="address_line2", - ) - ], - "Company": [ - dict( - fieldname="company_name_in_arabic", - label="Company Name In Arabic", - fieldtype="Data", - insert_after="company_name", - ) - ], - } - - create_custom_fields(custom_fields, ignore_validate=True, update=True) - - -def update_regional_tax_settings(country, company): - create_ksa_vat_setting(company) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py deleted file mode 100644 index cac5ec113e..0000000000 --- a/erpnext/regional/saudi_arabia/utils.py +++ /dev/null @@ -1,169 +0,0 @@ -import io -import os -from base64 import b64encode - -import frappe -from frappe import _ -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.utils.data import add_to_date, get_time, getdate -from pyqrcode import create as qr_create - -from erpnext import get_region - - -def create_qr_code(doc, method=None): - region = get_region(doc.company) - if region not in ["Saudi Arabia"]: - return - - # if QR Code field not present, create it. Invoices without QR are invalid as per law. - if not hasattr(doc, "ksa_einv_qr"): - create_custom_fields( - { - doc.doctype: [ - dict( - fieldname="ksa_einv_qr", - label="KSA E-Invoicing QR", - fieldtype="Attach Image", - read_only=1, - no_copy=1, - hidden=1, - ) - ] - } - ) - - # Don't create QR Code if it already exists - qr_code = doc.get("ksa_einv_qr") - if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): - return - - meta = frappe.get_meta(doc.doctype) - - if "ksa_einv_qr" in [d.fieldname for d in meta.get_image_fields()]: - """TLV conversion for - 1. Seller's Name - 2. VAT Number - 3. Time Stamp - 4. Invoice Amount - 5. VAT Amount - """ - tlv_array = [] - # Sellers Name - - seller_name = frappe.db.get_value("Company", doc.company, "company_name_in_arabic") - - if not seller_name: - frappe.throw(_("Arabic name missing for {} in the company document").format(doc.company)) - - tag = bytes([1]).hex() - length = bytes([len(seller_name.encode("utf-8"))]).hex() - value = seller_name.encode("utf-8").hex() - tlv_array.append("".join([tag, length, value])) - - # VAT Number - tax_id = frappe.db.get_value("Company", doc.company, "tax_id") - if not tax_id: - frappe.throw(_("Tax ID missing for {} in the company document").format(doc.company)) - - tag = bytes([2]).hex() - length = bytes([len(tax_id)]).hex() - value = tax_id.encode("utf-8").hex() - tlv_array.append("".join([tag, length, value])) - - # Time Stamp - posting_date = getdate(doc.posting_date) - time = get_time(doc.posting_time) - seconds = time.hour * 60 * 60 + time.minute * 60 + time.second - time_stamp = add_to_date(posting_date, seconds=seconds) - time_stamp = time_stamp.strftime("%Y-%m-%dT%H:%M:%SZ") - - tag = bytes([3]).hex() - length = bytes([len(time_stamp)]).hex() - value = time_stamp.encode("utf-8").hex() - tlv_array.append("".join([tag, length, value])) - - # Invoice Amount - invoice_amount = str(doc.base_grand_total) - tag = bytes([4]).hex() - length = bytes([len(invoice_amount)]).hex() - value = invoice_amount.encode("utf-8").hex() - tlv_array.append("".join([tag, length, value])) - - # VAT Amount - vat_amount = str(get_vat_amount(doc)) - - tag = bytes([5]).hex() - length = bytes([len(vat_amount)]).hex() - value = vat_amount.encode("utf-8").hex() - tlv_array.append("".join([tag, length, value])) - - # Joining bytes into one - tlv_buff = "".join(tlv_array) - - # base64 conversion for QR Code - base64_string = b64encode(bytes.fromhex(tlv_buff)).decode() - - qr_image = io.BytesIO() - url = qr_create(base64_string, error="L") - url.png(qr_image, scale=2, quiet_zone=1) - - name = frappe.generate_hash(doc.name, 5) - - # making file - filename = f"QRCode-{name}.png".replace(os.path.sep, "__") - _file = frappe.get_doc( - { - "doctype": "File", - "file_name": filename, - "is_private": 0, - "content": qr_image.getvalue(), - "attached_to_doctype": doc.get("doctype"), - "attached_to_name": doc.get("name"), - "attached_to_field": "ksa_einv_qr", - } - ) - - _file.save() - - # assigning to document - doc.db_set("ksa_einv_qr", _file.file_url) - doc.notify_update() - - -def get_vat_amount(doc): - vat_settings = frappe.db.get_value("KSA VAT Setting", {"company": doc.company}) - vat_accounts = [] - vat_amount = 0 - - if vat_settings: - vat_settings_doc = frappe.get_cached_doc("KSA VAT Setting", vat_settings) - - for row in vat_settings_doc.get("ksa_vat_sales_accounts"): - vat_accounts.append(row.account) - - for tax in doc.get("taxes"): - if tax.account_head in vat_accounts: - vat_amount += tax.base_tax_amount - - return vat_amount - - -def delete_qr_code_file(doc, method=None): - region = get_region(doc.company) - if region not in ["Saudi Arabia"]: - return - - if hasattr(doc, "ksa_einv_qr"): - if doc.get("ksa_einv_qr"): - file_doc = frappe.get_list("File", {"file_url": doc.get("ksa_einv_qr")}) - if len(file_doc): - frappe.delete_doc("File", file_doc[0].name) - - -def delete_vat_settings_for_company(doc, method=None): - if doc.country != "Saudi Arabia": - return - - if frappe.db.exists("KSA VAT Setting", doc.name): - frappe.delete_doc("KSA VAT Setting", doc.name) diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json deleted file mode 100644 index 60951a9cec..0000000000 --- a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "type": "Sales Account", - "accounts": [ - { - "title": "Standard rated Sales", - "item_tax_template": "KSA VAT 5%", - "account": "VAT 5%" - }, - { - "title": "Zero rated domestic sales", - "item_tax_template": "KSA VAT Zero", - "account": "VAT Zero" - }, - { - "title": "Exempted sales", - "item_tax_template": "KSA VAT Exempted", - "account": "VAT Exempted" - } - ] - }, - { - "type": "Purchase Account", - "accounts": [ - { - "title": "Standard rated domestic purchases", - "item_tax_template": "KSA VAT 5%", - "account": "VAT 5%" - }, - { - "title": "Imports subject to VAT paid at customs", - "item_tax_template": "KSA Excise 50%", - "account": "Excise 50%" - }, - { - "title": "Zero rated purchases", - "item_tax_template": "KSA VAT Zero", - "account": "VAT Zero" - }, - { - "title": "Exempted purchases", - "item_tax_template": "KSA VAT Exempted", - "account": "VAT Exempted" - } - ] - } -] \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py deleted file mode 100644 index 66d9df224e..0000000000 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ /dev/null @@ -1,46 +0,0 @@ -import json -import os - -import frappe - - -def create_ksa_vat_setting(company): - """On creation of first company. Creates KSA VAT Setting""" - - company = frappe.get_doc("Company", company) - - file_path = os.path.join(os.path.dirname(__file__), "..", "data", "ksa_vat_settings.json") - with open(file_path, "r") as json_file: - account_data = json.load(json_file) - - # Creating KSA VAT Setting - ksa_vat_setting = frappe.get_doc({"doctype": "KSA VAT Setting", "company": company.name}) - - for data in account_data: - if data["type"] == "Sales Account": - for row in data["accounts"]: - item_tax_template = row["item_tax_template"] - account = row["account"] - ksa_vat_setting.append( - "ksa_vat_sales_accounts", - { - "title": row["title"], - "item_tax_template": f"{item_tax_template} - {company.abbr}", - "account": f"{account} - {company.abbr}", - }, - ) - - elif data["type"] == "Purchase Account": - for row in data["accounts"]: - item_tax_template = row["item_tax_template"] - account = row["account"] - ksa_vat_setting.append( - "ksa_vat_purchase_accounts", - { - "title": row["title"], - "item_tax_template": f"{item_tax_template} - {company.abbr}", - "account": f"{account} - {company.abbr}", - }, - ) - - ksa_vat_setting.save() diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 45e39c5bd0..5750914b9a 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -4015,34 +4015,6 @@ "tax_rate": 18.00 } }, - - "Saudi Arabia": { - "KSA VAT 15%": { - "account_name": "VAT 15%", - "tax_rate": 15.00 - }, - "KSA VAT 5%": { - "account_name": "VAT 5%", - "tax_rate": 5.00 - }, - "KSA VAT Zero": { - "account_name": "VAT Zero", - "tax_rate": 0.00 - }, - "KSA VAT Exempted": { - "account_name": "VAT Exempted", - "tax_rate": 0.00 - }, - "KSA Excise 50%": { - "account_name": "Excise 50%", - "tax_rate": 50.00 - }, - "KSA Excise 100%": { - "account_name": "Excise 100%", - "tax_rate": 100.00 - } - }, - "Serbia": { "Serbia Tax": { "account_name": "VAT", From 80e94a08cfccd888b91e921a3c8b2418b6aed6c0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 22 Feb 2023 13:29:06 +0530 Subject: [PATCH 14/29] fix: zero division error while making LCV --- .../landed_cost_voucher.py | 9 ++- .../test_landed_cost_voucher.py | 55 ++++++++++++++++++- .../purchase_receipt/purchase_receipt.py | 24 ++++++-- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index b3af309359..111a0861b7 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -55,7 +55,6 @@ class LandedCostVoucher(Document): self.get_items_from_purchase_receipts() self.set_applicable_charges_on_item() - self.validate_applicable_charges_for_item() def check_mandatory(self): if not self.get("purchase_receipts"): @@ -115,6 +114,13 @@ class LandedCostVoucher(Document): total_item_cost += item.get(based_on_field) for item in self.get("items"): + if not total_item_cost and not item.get(based_on_field): + frappe.throw( + _( + "It's not possible to distribute charges equally when total amount is zero, please set 'Distribute Charges Based On' as 'Quantity'" + ) + ) + item.applicable_charges = flt( flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)), item.precision("applicable_charges"), @@ -162,6 +168,7 @@ class LandedCostVoucher(Document): ) def on_submit(self): + self.validate_applicable_charges_for_item() self.update_landed_cost() def on_cancel(self): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 979b5c4f83..00fa1686c0 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -175,6 +175,59 @@ class TestLandedCostVoucher(FrappeTestCase): ) self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0) + def test_landed_cost_voucher_for_zero_purchase_rate(self): + "Test impact of LCV on future stock balances." + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item("LCV Stock Item", {"is_stock_item": 1}) + warehouse = "Stores - _TC" + + pr = make_purchase_receipt( + item_code=item.name, + warehouse=warehouse, + qty=10, + rate=0, + posting_date=add_days(frappe.utils.nowdate(), -2), + ) + + self.assertEqual( + frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + "stock_value_difference", + ), + 0, + ) + + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=100, + distribute_charges_based_on="Distribute Manually", + do_not_save=True, + ) + + lcv.get_items_from_purchase_receipts() + lcv.items[0].applicable_charges = 100 + lcv.save() + lcv.submit() + + self.assertTrue( + frappe.db.exists( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + ) + ) + self.assertEqual( + frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + "stock_value_difference", + ), + 100, + ) + def test_landed_cost_voucher_against_purchase_invoice(self): pi = make_purchase_invoice( @@ -516,7 +569,7 @@ def make_landed_cost_voucher(**args): lcv = frappe.new_doc("Landed Cost Voucher") lcv.company = args.company or "_Test Company" - lcv.distribute_charges_based_on = "Amount" + lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount" lcv.set( "purchase_receipts", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index af0d148325..53e8053616 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1121,13 +1121,25 @@ def get_item_account_wise_additional_cost(purchase_document): account.expense_account, {"amount": 0.0, "base_amount": 0.0} ) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][ - "amount" - ] += (account.amount * item.get(based_on_field) / total_item_cost) + if total_item_cost > 0: + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["amount"] += ( + account.amount * item.get(based_on_field) / total_item_cost + ) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][ - "base_amount" - ] += (account.base_amount * item.get(based_on_field) / total_item_cost) + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["base_amount"] += ( + account.base_amount * item.get(based_on_field) / total_item_cost + ) + else: + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["amount"] += item.applicable_charges + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["base_amount"] += item.applicable_charges return item_account_wise_cost From a8f03ebf7f61c14d2cff45510acd670f0671a1b5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 22 Feb 2023 14:24:18 +0530 Subject: [PATCH 15/29] fix: incorrect color in the BOM Stock Report --- .../manufacturing/report/bom_stock_report/bom_stock_report.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js index 7beecaceed..e7f67caf24 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js @@ -25,8 +25,9 @@ frappe.query_reports["BOM Stock Report"] = { ], "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); + if (column.id == "item") { - if (data["enough_parts_to_build"] > 0) { + if (data["in_stock_qty"] >= data["required_qty"]) { value = `${data['item']}`; } else { value = `${data['item']}`; From 19c0b7a5234b017c7a15e8a7b149d386f85180e6 Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 22 Feb 2023 10:24:45 +0000 Subject: [PATCH 16/29] fix: currency in coa import --- .../chart_of_accounts/chart_of_accounts.py | 15 ++++++++++++++- .../chart_of_accounts_importer.py | 6 +++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 75f8f0645c..9e67c4cf0d 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -29,6 +29,7 @@ def create_charts( "root_type", "is_group", "tax_rate", + "account_currency", ]: account_number = cstr(child.get("account_number")).strip() @@ -95,7 +96,17 @@ def identify_is_group(child): is_group = child.get("is_group") elif len( set(child.keys()) - - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"]) + - set( + [ + "account_name", + "account_type", + "root_type", + "is_group", + "tax_rate", + "account_number", + "account_currency", + ] + ) ): is_group = 1 else: @@ -185,6 +196,7 @@ def get_account_tree_from_existing_company(existing_company): "root_type", "tax_rate", "account_number", + "account_currency", ], order_by="lft, rgt", ) @@ -267,6 +279,7 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals "root_type", "is_group", "tax_rate", + "account_currency", ]: continue diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 220b74727b..dd2bd88775 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -36,7 +36,7 @@ def validate_columns(data): no_of_columns = max([len(d) for d in data]) - if no_of_columns > 7: + if no_of_columns > 8: frappe.throw( _("More columns found than expected. Please compare the uploaded file with standard template"), title=(_("Wrong Template")), @@ -233,6 +233,7 @@ def build_forest(data): is_group, account_type, root_type, + account_currency, ) = i if not account_name: @@ -253,6 +254,8 @@ def build_forest(data): charts_map[account_name]["account_type"] = account_type if root_type: charts_map[account_name]["root_type"] = root_type + if account_currency: + charts_map[account_name]["account_currency"] = account_currency path = return_parent(data, account_name)[::-1] paths.append(path) # List of path is created line_no += 1 @@ -315,6 +318,7 @@ def get_template(template_type): "Is Group", "Account Type", "Root Type", + "account_currency", ] writer = UnicodeWriter() writer.writerow(fields) From e3c000d0bec35542b30811aa3f2f16782210770a Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 22 Feb 2023 10:53:11 +0000 Subject: [PATCH 17/29] chore: change column label --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index dd2bd88775..cb7da17901 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -318,7 +318,7 @@ def get_template(template_type): "Is Group", "Account Type", "Root Type", - "account_currency", + "Account Currency", ] writer = UnicodeWriter() writer.writerow(fields) From 6412583e9825bd3847f64b43f44409b57236e557 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Feb 2023 09:36:21 +0530 Subject: [PATCH 18/29] fix: ui freeze on item selection in sales invoice --- erpnext/selling/sales_common.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 8ff01f5cb4..5ce6e9c146 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -418,8 +418,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran callback: function(r) { if(r.message) { frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message); - } else { - frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message); } } }); From 88d888d9d08f5649e142ce2f5b376900cb851a4a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Feb 2023 11:15:56 +0530 Subject: [PATCH 19/29] refactor: use docstatus from Delivery Note Item --- erpnext/accounts/report/gross_profit/gross_profit.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index afd0328175..fde4de8402 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -667,15 +667,12 @@ class GrossProfitGenerator(object): def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code): from frappe.query_builder.functions import Sum - delivery_note = frappe.qb.DocType("Delivery Note") delivery_note_item = frappe.qb.DocType("Delivery Note Item") query = ( - frappe.qb.from_(delivery_note) - .inner_join(delivery_note_item) - .on(delivery_note.name == delivery_note_item.parent) + frappe.qb.from_(delivery_note_item) .select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty)) - .where(delivery_note.docstatus == 1) + .where(delivery_note_item.docstatus == 1) .where(delivery_note_item.item_code == item_code) .where(delivery_note_item.against_sales_order == sales_order) .where(delivery_note_item.so_detail == so_detail) From 6417ae0ee83d3a2b384987cc3747917e92d6a4ab Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 23 Feb 2023 20:04:14 +0530 Subject: [PATCH 20/29] fix: user shouldn't able to make item price for item template --- .../stock/doctype/item_price/item_price.js | 13 ++++++++++- .../stock/doctype/item_price/item_price.py | 9 +++++++- .../doctype/item_price/test_item_price.py | 22 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js index 12cf6cf84d..ce489ff52b 100644 --- a/erpnext/stock/doctype/item_price/item_price.js +++ b/erpnext/stock/doctype/item_price/item_price.js @@ -2,7 +2,18 @@ // License: GNU General Public License v3. See license.txt frappe.ui.form.on("Item Price", { - onload: function (frm) { + setup(frm) { + frm.set_query("item_code", function() { + return { + filters: { + "disabled": 0, + "has_variants": 0 + } + }; + }); + }, + + onload(frm) { // Fetch price list details frm.add_fetch("price_list", "buying", "buying"); frm.add_fetch("price_list", "selling", "selling"); diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index bcd31ada83..54d1ae634f 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -3,7 +3,7 @@ import frappe -from frappe import _ +from frappe import _, bold from frappe.model.document import Document from frappe.query_builder import Criterion from frappe.query_builder.functions import Cast_ @@ -21,6 +21,7 @@ class ItemPrice(Document): self.update_price_list_details() self.update_item_details() self.check_duplicates() + self.validate_item_template() def validate_item(self): if not frappe.db.exists("Item", self.item_code): @@ -49,6 +50,12 @@ class ItemPrice(Document): "Item", self.item_code, ["item_name", "description"] ) + def validate_item_template(self): + if frappe.get_cached_value("Item", self.item_code, "has_variants"): + msg = f"Item Price cannot be created for the template item {bold(self.item_code)}" + + frappe.throw(_(msg)) + def check_duplicates(self): item_price = frappe.qb.DocType("Item Price") diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 30d933e247..8fd4938fa3 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -16,6 +16,28 @@ class TestItemPrice(FrappeTestCase): frappe.db.sql("delete from `tabItem Price`") make_test_records_for_doctype("Item Price", force=True) + def test_template_item_price(self): + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item( + "Test Template Item 1", + { + "has_variants": 1, + "variant_based_on": "Manufacturer", + }, + ) + + doc = frappe.get_doc( + { + "doctype": "Item Price", + "price_list": "_Test Price List", + "item_code": item.name, + "price_list_rate": 100, + } + ) + + self.assertRaises(frappe.ValidationError, doc.save) + def test_duplicate_item(self): doc = frappe.copy_doc(test_records[0]) self.assertRaises(ItemPriceDuplicateItem, doc.save) From 8e46aebc50110fab1a3f123aa22cb8e06907d451 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 24 Feb 2023 14:42:55 +0530 Subject: [PATCH 21/29] fix: conversion factor not set --- erpnext/stock/doctype/item/item.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 5bcb05aa98..9a9ddf4404 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -33,6 +33,9 @@ frappe.ui.form.on("Item", { 'Material Request': () => { open_form(frm, "Material Request", "Material Request Item", "items"); }, + 'Stock Entry': () => { + open_form(frm, "Stock Entry", "Stock Entry Detail", "items"); + }, }; }, @@ -893,6 +896,9 @@ function open_form(frm, doctype, child_doctype, parentfield) { new_child_doc.item_name = frm.doc.item_name; new_child_doc.uom = frm.doc.stock_uom; new_child_doc.description = frm.doc.description; + if (!new_child_doc.qty) { + new_child_doc.qty = 1.0; + } frappe.run_serially([ () => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc), From e26c6dc76b16707c31e9a0277c2710a4c5cb43f0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 24 Feb 2023 15:28:14 +0530 Subject: [PATCH 22/29] Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202) Revert "fix: Concurrency issues in Sales and Purchase returns (#34019)" This reverts commit a67284e96dabe71b76a373b5f1f3142dccf3952b. --- erpnext/controllers/sales_and_purchase_return.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index fc6793a9bb..9fcb769bc8 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -252,7 +252,6 @@ def get_already_returned_items(doc): child.parent = par.name and par.docstatus = 1 and par.is_return = 1 and par.return_against = %s group by item_code - for update """.format( column, doc.doctype, doc.doctype ), From 7d10dd9ea8671c27709e88350c6799ba187c4929 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 24 Feb 2023 17:50:00 +0530 Subject: [PATCH 23/29] fix: not able to repost gl entries --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index af0d148325..f19f0a1359 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -473,7 +473,7 @@ class PurchaseReceipt(BuyingController): ) divisional_loss = flt( - valuation_amount_as_per_doc - stock_value_diff, d.precision("base_net_amount") + valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount") ) if divisional_loss: From b6bad728cdff7fd79f175dbd5b631f9bd5234ed7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Feb 2023 14:11:53 +0530 Subject: [PATCH 24/29] fix: permission error while calling get_work_order_items --- .../doctype/sales_order/sales_order.js | 23 ++-- .../doctype/sales_order/sales_order.py | 102 ++++++++++-------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index fb64772479..ee0752549d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -309,9 +309,12 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex make_work_order() { var me = this; - this.frm.call({ - doc: this.frm.doc, - method: 'get_work_order_items', + me.frm.call({ + method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items", + args: { + sales_order: this.frm.docname, + }, + freeze: true, callback: function(r) { if(!r.message) { frappe.msgprint({ @@ -321,14 +324,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex }); return; } - else if(!r.message) { - frappe.msgprint({ - title: __('Work Order not created'), - message: __('Work Order already created for all items with BOM'), - indicator: 'orange' - }); - return; - } else { + else { const fields = [{ label: 'Items', fieldtype: 'Table', @@ -429,9 +425,9 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex make_raw_material_request() { var me = this; this.frm.call({ - doc: this.frm.doc, - method: 'get_work_order_items', + method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items", args: { + sales_order: this.frm.docname, for_raw_material_request: 1 }, callback: function(r) { @@ -450,6 +446,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } make_raw_material_request_dialog(r) { + var me = this; var fields = [ {fieldtype:'Check', fieldname:'include_exploded_items', label: __('Include Exploded Items')}, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index ca6a51a6f3..385d0f3a58 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -6,11 +6,12 @@ import json import frappe import frappe.utils -from frappe import _ +from frappe import _, qb from frappe.contacts.doctype.address.address import get_company_address from frappe.desk.notifications import clear_doctype_notifications from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values +from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( @@ -414,51 +415,6 @@ class SalesOrder(SellingController): self.indicator_color = "green" self.indicator_title = _("Paid") - @frappe.whitelist() - def get_work_order_items(self, for_raw_material_request=0): - """Returns items with BOM that already do not have a linked work order""" - items = [] - item_codes = [i.item_code for i in self.items] - product_bundle_parents = [ - pb.new_item_code - for pb in frappe.get_all( - "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"] - ) - ] - - for table in [self.items, self.packed_items]: - for i in table: - bom = get_default_bom(i.item_code) - stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty - - if not for_raw_material_request: - total_work_order_qty = flt( - frappe.db.sql( - """select sum(qty) from `tabWork Order` - where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""", - (i.item_code, self.name, i.name), - )[0][0] - ) - pending_qty = stock_qty - total_work_order_qty - else: - pending_qty = stock_qty - - if pending_qty and i.item_code not in product_bundle_parents: - items.append( - dict( - name=i.name, - item_code=i.item_code, - description=i.description, - bom=bom or "", - warehouse=i.warehouse, - pending_qty=pending_qty, - required_qty=pending_qty if for_raw_material_request else 0, - sales_order_item=i.name, - ) - ) - - return items - def on_recurring(self, reference_doc, auto_repeat_doc): def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date): delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date) @@ -1350,3 +1306,57 @@ def update_produced_qty_in_so_item(sales_order, sales_order_item): return frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty) + + +@frappe.whitelist() +def get_work_order_items(sales_order, for_raw_material_request=0): + """Returns items with BOM that already do not have a linked work order""" + if sales_order: + so = frappe.get_doc("Sales Order", sales_order) + + wo = qb.DocType("Work Order") + + items = [] + item_codes = [i.item_code for i in so.items] + product_bundle_parents = [ + pb.new_item_code + for pb in frappe.get_all( + "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"] + ) + ] + + for table in [so.items, so.packed_items]: + for i in table: + bom = get_default_bom(i.item_code) + stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty + + if not for_raw_material_request: + total_work_order_qty = flt( + qb.from_(wo) + .select(Sum(wo.qty)) + .where( + (wo.production_item == i.item_code) + & (wo.sales_order == so.name) * (wo.sales_order_item == i.name) + & (wo.docstatus.lte(2)) + ) + .run()[0][0] + ) + pending_qty = stock_qty - total_work_order_qty + else: + pending_qty = stock_qty + + if pending_qty and i.item_code not in product_bundle_parents: + items.append( + dict( + name=i.name, + item_code=i.item_code, + description=i.description, + bom=bom or "", + warehouse=i.warehouse, + pending_qty=pending_qty, + required_qty=pending_qty if for_raw_material_request else 0, + sales_order_item=i.name, + ) + ) + + return items From a11d3327dfb5b016b2ff7ca33a5a0431d6e91019 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Feb 2023 20:21:58 +0530 Subject: [PATCH 25/29] fix(test): use standalone method to fetch work orders from SO --- .../selling/doctype/sales_order/test_sales_order.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index d4d7c58eb8..627914f0c7 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1217,6 +1217,8 @@ class TestSalesOrder(FrappeTestCase): self.assertTrue(si.get("payment_schedule")) def test_make_work_order(self): + from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items + # Make a new Sales Order so = make_sales_order( **{ @@ -1230,7 +1232,7 @@ class TestSalesOrder(FrappeTestCase): # Raise Work Orders po_items = [] so_item_name = {} - for item in so.get_work_order_items(): + for item in get_work_order_items(so.name): po_items.append( { "warehouse": item.get("warehouse"), @@ -1448,6 +1450,7 @@ class TestSalesOrder(FrappeTestCase): from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items make_item( # template item "Test-WO-Tshirt", @@ -1487,7 +1490,7 @@ class TestSalesOrder(FrappeTestCase): ] } ) - wo_items = so.get_work_order_items() + wo_items = get_work_order_items(so.name) self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R") self.assertEqual(wo_items[0].get("bom"), red_var_bom.name) @@ -1497,6 +1500,8 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(wo_items[1].get("bom"), template_bom.name) def test_request_for_raw_materials(self): + from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items + item = make_item( "_Test Finished Item", { @@ -1529,7 +1534,7 @@ class TestSalesOrder(FrappeTestCase): so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]}) so.submit() mr_dict = frappe._dict() - items = so.get_work_order_items(1) + items = get_work_order_items(so.name, 1) mr_dict["items"] = items mr_dict["include_exploded_items"] = 0 mr_dict["ignore_existing_ordered_qty"] = 1 From dd74839eba24c1cd9a0b90d8d353a9cecb920f99 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sun, 26 Feb 2023 17:36:36 +0530 Subject: [PATCH 26/29] fix: manual depr schedule --- .../asset_depreciation_schedule.js | 6 +- .../asset_depreciation_schedule.json | 24 ++++++- .../asset_depreciation_schedule.py | 63 +++++++++++++++++-- ...sset_depreciation_schedules_from_assets.py | 8 ++- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js index c28b2b3b6a..3d2dff179a 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js @@ -43,9 +43,9 @@ erpnext.asset.set_accumulated_depreciation = function(frm) { if(frm.doc.depreciation_method != "Manual") return; var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation); - $.each(frm.doc.schedules || [], function(i, row) { + + $.each(frm.doc.depreciation_schedule || [], function(i, row) { accumulated_depreciation += flt(row.depreciation_amount); - frappe.model.set_value(row.doctype, row.name, - "accumulated_depreciation_amount", accumulated_depreciation); + frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation); }) }; diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json index 898c482079..d38508d0c4 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -10,7 +10,9 @@ "asset", "naming_series", "column_break_2", + "gross_purchase_amount", "opening_accumulated_depreciation", + "number_of_depreciations_booked", "finance_book", "finance_book_id", "depreciation_details_section", @@ -148,18 +150,36 @@ "read_only": 1 }, { - "depends_on": "opening_accumulated_depreciation", "fieldname": "opening_accumulated_depreciation", "fieldtype": "Currency", + "hidden": 1, "label": "Opening Accumulated Depreciation", "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "gross_purchase_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Gross Purchase Amount", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "number_of_depreciations_booked", + "fieldtype": "Int", + "hidden": 1, + "label": "Number of Depreciations Booked", + "print_hide": 1, "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-01-16 21:08:21.421260", + "modified": "2023-02-26 16:37:23.734806", "modified_by": "Administrator", "module": "Assets", "name": "Asset Depreciation Schedule", diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index 6f02662544..b75fbcbeb3 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -4,7 +4,15 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month +from frappe.utils import ( + add_days, + add_months, + cint, + flt, + get_last_day, + getdate, + is_last_day_of_the_month, +) class AssetDepreciationSchedule(Document): @@ -83,15 +91,58 @@ class AssetDepreciationSchedule(Document): date_of_return=None, update_asset_finance_book_row=True, ): + have_asset_details_been_modified = self.have_asset_details_been_modified(asset_doc) + not_manual_depr_or_have_manual_depr_details_been_modified = ( + self.not_manual_depr_or_have_manual_depr_details_been_modified(row) + ) + self.set_draft_asset_depr_schedule_details(asset_doc, row) - self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row) - self.set_accumulated_depreciation(row, date_of_disposal, date_of_return) + + if self.should_prepare_depreciation_schedule( + have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified + ): + self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row) + self.set_accumulated_depreciation(row, date_of_disposal, date_of_return) + + def have_asset_details_been_modified(self, asset_doc): + return ( + asset_doc.gross_purchase_amount != self.gross_purchase_amount + or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation + or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked + ) + + def not_manual_depr_or_have_manual_depr_details_been_modified(self, row): + return ( + self.depreciation_method != "Manual" + or row.total_number_of_depreciations != self.total_number_of_depreciations + or row.frequency_of_depreciation != self.frequency_of_depreciation + or getdate(row.depreciation_start_date) != self.get("depreciation_schedule")[0].schedule_date + or row.expected_value_after_useful_life != self.expected_value_after_useful_life + ) + + def should_prepare_depreciation_schedule( + self, have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified + ): + if not self.get("depreciation_schedule"): + return True + + old_asset_depr_schedule_doc = self.get_doc_before_save() + + if self.docstatus != 0 and not old_asset_depr_schedule_doc: + return True + + if have_asset_details_been_modified or not_manual_depr_or_have_manual_depr_details_been_modified: + return True + + return False def set_draft_asset_depr_schedule_details(self, asset_doc, row): self.asset = asset_doc.name self.finance_book = row.finance_book self.finance_book_id = row.idx self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation + self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked + self.gross_purchase_amount = asset_doc.gross_purchase_amount self.depreciation_method = row.depreciation_method self.total_number_of_depreciations = row.total_number_of_depreciations self.frequency_of_depreciation = row.frequency_of_depreciation @@ -102,7 +153,7 @@ class AssetDepreciationSchedule(Document): def make_depr_schedule( self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True ): - if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"): + if not self.get("depreciation_schedule"): self.depreciation_schedule = [] if not asset_doc.available_for_use_date: @@ -293,7 +344,9 @@ class AssetDepreciationSchedule(Document): ignore_booked_entry=False, ): straight_line_idx = [ - d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line" + d.idx + for d in self.get("depreciation_schedule") + if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual" ] accumulated_depreciation = flt(self.opening_accumulated_depreciation) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index 371ecbc8c1..5c46bf3280 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -27,7 +27,13 @@ def get_details_of_draft_or_submitted_depreciable_assets(): records = ( frappe.qb.from_(asset) - .select(asset.name, asset.opening_accumulated_depreciation, asset.docstatus) + .select( + asset.name, + asset.opening_accumulated_depreciation, + asset.gross_purchase_amount, + asset.number_of_depreciations_booked, + asset.docstatus, + ) .where(asset.calculate_depreciation == 1) .where(asset.docstatus < 2) ).run(as_dict=True) From 83f3e317e1ad29a1c8303a56070d601c3d102608 Mon Sep 17 00:00:00 2001 From: Brian Pond <19827963+brian-pond@users.noreply.github.com> Date: Sat, 25 Feb 2023 12:41:23 -0800 Subject: [PATCH 27/29] fix: Remove missing DocField in fetch_from --- .../maintenance_visit_purpose.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 158f143ae8..ba05355553 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -64,8 +64,6 @@ "fieldtype": "Section Break" }, { - "fetch_from": "prevdoc_detail_docname.sales_person", - "fetch_if_empty": 1, "fieldname": "service_person", "fieldtype": "Link", "in_list_view": 1, @@ -110,13 +108,15 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-27 17:47:21.474282", + "modified": "2023-02-27 11:09:33.114458", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From bbb6a62a7ddbc5337998652721160437f79301eb Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDenis-stack@users.noreply.github.com> Date: Mon, 27 Feb 2023 07:19:22 +0100 Subject: [PATCH 28/29] chore: add german translations (#34167) * chore: add german translations * Apply suggestions from code review Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --------- Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/translations/de.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 5a0a863a47..bec3ce242b 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -9916,3 +9916,5 @@ Cost and Freight,Kosten und Fracht, Delivered at Place,Geliefert benannter Ort, Delivered at Place Unloaded,Geliefert benannter Ort entladen, Delivered Duty Paid,Geliefert verzollt, +Discount Validity,Frist für den Rabatt, +Discount Validity Based On,Frist für den Rabatt berechnet sich nach, From c09a61f360bad2faa12ac1118665d71203061361 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 23 Feb 2023 12:26:19 +0530 Subject: [PATCH 29/29] fix: set `from_warehouse` and `to_warehouse` while mapping SE --- erpnext/stock/doctype/material_request/material_request.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 6426fe8015..dcbc460262 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -587,6 +587,9 @@ def make_stock_entry(source_name, target_doc=None): def set_missing_values(source, target): target.purpose = source.material_request_type + target.from_warehouse = source.set_from_warehouse + target.to_warehouse = source.set_warehouse + if source.job_card: target.purpose = "Material Transfer for Manufacture" @@ -722,6 +725,7 @@ def create_pick_list(source_name, target_doc=None): def make_in_transit_stock_entry(source_name, in_transit_warehouse): ste_doc = make_stock_entry(source_name) ste_doc.add_to_transit = 1 + ste_doc.to_warehouse = in_transit_warehouse for row in ste_doc.items: row.t_warehouse = in_transit_warehouse