diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index b61a2539b..221d2cf3b 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -5,6 +5,7 @@ import frappe from frappe import _ from frappe.utils import sbool +from frappe.utils.scheduler import is_scheduler_disabled from india_compliance.exceptions import GatewayTimeoutError, GSPServerError from india_compliance.gst_india.utils import is_api_enabled @@ -290,7 +291,7 @@ def check_scheduler_status(): if frappe.flags.in_test or frappe.conf.developer_mode: return - if frappe.utils.scheduler.is_scheduler_disabled(): + if is_scheduler_disabled(): frappe.throw( _( "The Scheduler is currently disabled, which needs to be enabled to use e-Invoicing and e-Waybill features. " diff --git a/india_compliance/gst_india/client_scripts/company.js b/india_compliance/gst_india/client_scripts/company.js index a0594e0de..7cf469a40 100644 --- a/india_compliance/gst_india/client_scripts/company.js +++ b/india_compliance/gst_india/client_scripts/company.js @@ -27,13 +27,13 @@ frappe.ui.form.on(DOCTYPE, { frm.set_query("autofield", "bank_details_for_printing", (_, cdt, cdn) => { return { query: "india_compliance.gst_india.overrides.company.get_default_print_options", - params : {for_bank : true} + params : {for_bank : 1} } }); frm.set_query("autofield", "registration_details_for_printing", (_, cdt, cdn) => { return { query: "india_compliance.gst_india.overrides.company.get_default_print_options", - params : {for_bank : false} + params : {for_bank : 0} } }); }, diff --git a/india_compliance/gst_india/constants/__init__.py b/india_compliance/gst_india/constants/__init__.py index eff4a5ae0..f0bae3ad3 100644 --- a/india_compliance/gst_india/constants/__init__.py +++ b/india_compliance/gst_india/constants/__init__.py @@ -16,6 +16,10 @@ GST_TAX_TYPES = tuple(field[:-8] for field in GST_ACCOUNT_FIELDS) +GST_RCM_TAX_TYPES = tuple(tax_type + "_rcm" for tax_type in GST_TAX_TYPES) + +TAX_TYPES = (*GST_TAX_TYPES, *GST_RCM_TAX_TYPES) + GST_PARTY_TYPES = ("Customer", "Supplier", "Company") # Map for e-Invoice Supply Type diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 628a026af..ff1185b7b 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -592,6 +592,20 @@ "translatable": 0, }, ], + ( + "Sales Taxes and Charges", + "Purchase Taxes and Charges", + "Advance Taxes and Charges", + ): [ + { + "fieldname": "gst_tax_type", + "label": "GST Tax Type", + "fieldtype": "Data", + "insert_after": "rate", + "read_only": 1, + "translatable": 0, + }, + ], "Purchase Invoice": [ { "fieldname": "gst_section", diff --git a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py index 0f0fc18bb..d7d39efa3 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py +++ b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py @@ -13,6 +13,7 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.taxes_and_totals import get_round_off_applicable_accounts +from india_compliance.gst_india.constants import GST_TAX_TYPES from india_compliance.gst_india.overrides.ineligible_itc import ( update_landed_cost_voucher_for_gst_expense, update_regional_gl_entries, @@ -21,6 +22,7 @@ from india_compliance.gst_india.overrides.transaction import ( ItemGSTDetails, ItemGSTTreatment, + set_gst_tax_type, validate_charge_type_for_cess_non_advol_accounts, ) from india_compliance.gst_india.utils import get_gst_accounts_by_type @@ -41,12 +43,11 @@ def set_item_wise_tax_details(self): if ( not row.tax_amount or not row.item_wise_tax_rates - or row.account_head not in self.gst_account_map + or row.gst_tax_type not in GST_TAX_TYPES ): continue - account_type = self.gst_account_map[row.account_head] - tax = account_type[:-8] + tax = row.gst_tax_type tax_rate_field = f"{tax}_rate" tax_amount_field = f"{tax}_amount" @@ -113,6 +114,7 @@ def onload(self): def before_validate(self): self.set_taxes_and_totals() + set_gst_tax_type(self) def before_save(self): update_gst_details(self) @@ -287,9 +289,7 @@ def validate_taxes(self): ).format(tax.idx) ) - validate_charge_type_for_cess_non_advol_accounts( - [input_accounts.cess_non_advol_account], tax - ) + validate_charge_type_for_cess_non_advol_accounts(tax) if tax.charge_type != "Actual": continue @@ -306,9 +306,7 @@ def validate_taxes(self): # validating total tax total_tax = 0 - is_non_cess_advol = ( - tax.account_head == input_accounts.cess_non_advol_account - ) + is_non_cess_advol = tax.gst_tax_type == "cess_non_advol" for item, rate in item_wise_tax_rates.items(): multiplier = ( @@ -516,6 +514,7 @@ def set_missing_values(source, target): "charge_type": "On Net Total", "account_head": input_igst_account, "rate": rate, + "gst_tax_type": "igst", "description": description, }, ) diff --git a/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json b/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json index 3a56d6bca..916797e27 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json +++ b/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json @@ -13,6 +13,7 @@ "account_head", "section_break_10", "rate", + "gst_tax_type", "column_break_pipk", "tax_amount", "total", @@ -89,11 +90,17 @@ "fieldtype": "Code", "hidden": 1, "label": "Item Wise Tax Rates" + }, + { + "fieldname": "gst_tax_type", + "fieldtype": "Data", + "label": "GST Tax Type", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2024-03-29 11:36:18.468715", + "modified": "2024-06-12 18:03:36.955731", "modified_by": "Administrator", "module": "GST India", "name": "Bill of Entry Taxes", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 35c04c52e..0858bcb36 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2102,6 +2102,8 @@ async function set_default_company_gstin(frm) { frm.set_value("company_gstin", ""); const company = frm.doc.company; + if (!company) return; + const { message: gstin_list } = await frappe.call( "india_compliance.gst_india.utils.get_gstin_list", { party: company } diff --git a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py index 6d251bbfe..930018d92 100644 --- a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py +++ b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py @@ -17,7 +17,6 @@ from india_compliance.gst_india.report.gstr_3b_details.gstr_3b_details import ( IneligibleITC, ) -from india_compliance.gst_india.utils import get_gst_accounts_by_type VALUES_TO_UPDATE = ["iamt", "camt", "samt", "csamt"] @@ -121,24 +120,33 @@ def get_itc_reversal_entries(self): self.update_itc_reversal_from_bill_of_entry() def update_itc_reversal_from_purchase_invoice(self): + self.update_itc_reversal_for_purchase_us_17_4() + self.update_itc_reversal_for_purchase_due_to_pos() + + def update_itc_reversal_for_purchase_due_to_pos(self): ineligible_credit = IneligibleITC( self.company, self.gst_details.get("gstin"), self.month_no, self.year - ).get_ineligible_itc_us_17_5_for_purchase(group_by="ineligibility_reason") + ).get_for_purchase( + "ITC restricted due to PoS rules", group_by="ineligibility_reason" + ) - ineligible_credit_due_to_pos = IneligibleITC( - self.company, self.gst_details.get("gstin"), self.month_no, self.year - ).get_ineligible_itc_due_to_pos_for_purchase(group_by="ineligibility_reason") + self.process_ineligible_credit(ineligible_credit) - ineligible_credit.extend(ineligible_credit_due_to_pos) + def update_itc_reversal_for_purchase_us_17_4(self): + ineligible_credit = IneligibleITC( + self.company, self.gst_details.get("gstin"), self.month_no, self.year + ).get_for_purchase( + "Ineligible As Per Section 17(5)", group_by="ineligibility_reason" + ) - return self.process_ineligible_credit(ineligible_credit) + self.process_ineligible_credit(ineligible_credit) def update_itc_reversal_from_bill_of_entry(self): ineligible_credit = IneligibleITC( self.company, self.gst_details.get("gstin"), self.month_no, self.year ).get_for_bill_of_entry() - return self.process_ineligible_credit(ineligible_credit) + self.process_ineligible_credit(ineligible_credit) def process_ineligible_credit(self, ineligible_credit): if not ineligible_credit: @@ -230,7 +238,6 @@ def get_itc_details(self): def update_imports_from_bill_of_entry(self, itc_details): boe = frappe.qb.DocType("Bill of Entry") boe_taxes = frappe.qb.DocType("Bill of Entry Taxes") - gst_accounts = get_gst_accounts_by_type(self.company, "Input") def _get_tax_amount(account_type): return ( @@ -245,12 +252,12 @@ def _get_tax_amount(account_type): ) & boe.company_gstin.eq(self.gst_details.get("gstin")) & boe.docstatus.eq(1) - & boe_taxes.account_head.eq(gst_accounts[account_type]) + & boe_taxes.gst_tax_type.eq(account_type) ) .run() )[0][0] or 0 - igst, cess = _get_tax_amount("igst_account"), _get_tax_amount("cess_account") + igst, cess = _get_tax_amount("igst"), _get_tax_amount("cess") itc_details.setdefault("Import Of Goods", {"iamt": 0, "csamt": 0}) itc_details["Import Of Goods"]["iamt"] += igst itc_details["Import Of Goods"]["csamt"] += cess @@ -317,12 +324,13 @@ def get_invoice_item_wise_tax_details(self, doctype): def set_item_wise_tax_details(self, docs): self.invoice_item_wise_tax_details = {} item_wise_details = {} - account_head_gst_map = {} - - for key, values in self.account_heads.items(): - for value in values: - if value is not None: - account_head_gst_map[value] = key + gst_tax_type_map = { + "cgst": "camt", + "sgst": "samt", + "igst": "iamt", + "cess": "csamt", + "cess_non_advol": "csamt", + } item_defaults = frappe._dict( { @@ -358,7 +366,7 @@ def set_item_wise_tax_details(self, docs): # Process tax details for tax in details["taxes"]: - gst_tax_type = account_head_gst_map.get(tax.account_head) + gst_tax_type = gst_tax_type_map.get(tax.gst_tax_type) if not gst_tax_type: continue @@ -452,7 +460,7 @@ def get_outward_tax_details(self, doctype): tax_details = frappe.db.sql( f""" SELECT - parent, account_head, item_wise_tax_detail + parent, item_wise_tax_detail, gst_tax_type FROM `tab{tax_template}` WHERE parenttype = %s and docstatus = 1 diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py index 0af13d91d..03ca4afd2 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py @@ -13,11 +13,7 @@ from frappe.utils import add_months, format_date, getdate, rounded from india_compliance.gst_india.constants import GST_TAX_TYPES -from india_compliance.gst_india.utils import ( - get_escaped_name, - get_gst_accounts_by_type, - get_party_for_gstin, -) +from india_compliance.gst_india.utils import get_party_for_gstin from india_compliance.gst_india.utils.gstr_2 import IMPORT_CATEGORY, ReturnType @@ -454,10 +450,8 @@ def get_query(self, additional_fields=None, is_return=False): return query def get_fields(self, additional_fields=None, is_return=False): - gst_accounts = get_gst_accounts_by_type(self.company, "Input") tax_fields = [ - self.query_tax_amount(account).as_(tax[:-8]) - for tax, account in gst_accounts.items() + self.query_tax_amount(tax_type).as_(tax_type) for tax_type in GST_TAX_TYPES ] fields = [ @@ -490,13 +484,12 @@ def get_fields(self, additional_fields=None, is_return=False): return fields - def query_tax_amount(self, account): - account = get_escaped_name(account) + def query_tax_amount(self, gst_tax_type): return Abs( Sum( Case() .when( - self.PI_TAX.account_head == account, + self.PI_TAX.gst_tax_type == gst_tax_type, self.PI_TAX.base_tax_amount_after_discount_amount, ) .else_(0) @@ -600,10 +593,8 @@ def get_query(self, additional_fields=None): return query def get_fields(self, additional_fields=None): - gst_accounts = get_gst_accounts_by_type(self.company, "Input") tax_fields = [ - self.query_tax_amount(account).as_(tax[:-8]) - for tax, account in gst_accounts.items() + self.query_tax_amount(tax_type).as_(tax_type) for tax_type in GST_TAX_TYPES ] fields = [ @@ -638,13 +629,12 @@ def get_fields(self, additional_fields=None): return fields - def query_tax_amount(self, account): - account = get_escaped_name(account) + def query_tax_amount(self, gst_tax_type): return Abs( Sum( Case() .when( - self.BOE_TAX.account_head == account, + self.BOE_TAX.gst_tax_type == gst_tax_type, self.BOE_TAX.tax_amount, ) .else_(0) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js index 91487550f..a44294349 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js @@ -17,15 +17,9 @@ const ALERT_HTML = ` ${ api_enabled - ? `` + ? ` + Download 2B + ` : "" } @@ -1465,6 +1459,7 @@ async function fetch_date_range(frm, field_prefix, method) { function set_date_range_description(frm, field_prefixs) { if (!field_prefixs) field_prefixs = ["inward_supply", "purchase"]; + else field_prefixs = [field_prefixs]; field_prefixs.forEach(prefix => { const period_field = prefix + "_period"; diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json index d743dcf1c..4acc2f59e 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json @@ -47,7 +47,7 @@ "fieldname": "purchase_period", "fieldtype": "Select", "label": "Purchase Period", - "options": "\nThis Month\nThis Quarter\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" + "options": "\nThis Month\nThis Quarter to Last Month\nThis Quarter\nThis Fiscal Year to Last Month\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" }, { "depends_on": "eval: doc.purchase_period == 'Custom'", @@ -71,7 +71,7 @@ "fieldname": "inward_supply_period", "fieldtype": "Select", "label": "Inward Supply Period", - "options": "\nThis Month\nThis Quarter\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" + "options": "\nThis Month\nThis Quarter to Last Month\nThis Quarter\nThis Fiscal Year to Last Month\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" }, { "depends_on": "eval: doc.inward_supply_period == 'Custom'", @@ -150,7 +150,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-03-29 11:54:43.553746", + "modified": "2024-06-20 13:19:57.316043", "modified_by": "Administrator", "module": "GST India", "name": "Purchase Reconciliation Tool", diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py index 1558e70d5..dfc3f6324 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py @@ -490,6 +490,9 @@ def _download_gstr_2a( if not force: periods = get_periods_to_download(company_gstin, return_type, periods) + if not periods: + return + return download_gstr_2a(company_gstin, periods, otp, gst_categories) @@ -498,6 +501,10 @@ def _download_gstr_2b(date_range, company_gstin, otp=None): periods = get_periods_to_download( company_gstin, return_type, BaseUtil.get_periods(date_range, return_type) ) + + if not periods: + return + return download_gstr_2b(company_gstin, periods, otp) @@ -602,11 +609,11 @@ def wrapper(*args, **kwargs): def auto_refresh_authtoken(): - is_auto_refresh_enabled = frappe.db.get_single_value( - "GST Settings", "auto_refresh_auth_token" + enable_auto_reconciliation = frappe.get_cached_value( + "GST Settings", "GST Settings", "enable_auto_reconciliation" ) - if not is_auto_refresh_enabled: + if not enable_auto_reconciliation: return for credential in frappe.get_all( diff --git a/india_compliance/gst_india/overrides/address.py b/india_compliance/gst_india/overrides/address.py index 5c4a2711e..ba50196a0 100644 --- a/india_compliance/gst_india/overrides/address.py +++ b/india_compliance/gst_india/overrides/address.py @@ -60,11 +60,7 @@ def validate(doc, method=None): def validate_overseas_gst_category(doc): - if doc.country == "India" and doc.gst_category == "Overseas": - frappe.throw( - _("Cannot set GST Category as Overseas for Indian Address"), - title=_("Invalid GST Category"), - ) + # High Seas Sales is possible (Indian Address with Overseas GST Category) if doc.country != "India" and doc.gst_category != "Overseas": frappe.throw( diff --git a/india_compliance/gst_india/overrides/company.py b/india_compliance/gst_india/overrides/company.py index 0b87aba92..bd22a2255 100644 --- a/india_compliance/gst_india/overrides/company.py +++ b/india_compliance/gst_india/overrides/company.py @@ -229,8 +229,8 @@ def create_default_company_account( @frappe.whitelist() -def get_default_print_options(for_bank=True): - if for_bank: +def get_default_print_options(for_bank=1) -> list: + if int(for_bank): return ["Account No.", "Bank Name", "Branch", "IFSC Code", "UPI ID"] else: return ["MSME No.", "MSME Type", "LLPIN", "LUT No."] diff --git a/india_compliance/gst_india/overrides/ineligible_itc.py b/india_compliance/gst_india/overrides/ineligible_itc.py index c53a318e9..7c1a9b957 100644 --- a/india_compliance/gst_india/overrides/ineligible_itc.py +++ b/india_compliance/gst_india/overrides/ineligible_itc.py @@ -1,16 +1,18 @@ +from collections import defaultdict + import frappe from frappe import _ -from frappe.utils import flt, get_link_to_form, rounded +from frappe.utils import flt, get_link_to_form from erpnext.assets.doctype.asset.asset import ( get_asset_account, is_cwip_accounting_enabled, ) from erpnext.stock import get_warehouse_account_map +from india_compliance.gst_india.constants import GST_TAX_TYPES from india_compliance.gst_india.overrides.transaction import ( is_indian_registered_company, ) -from india_compliance.gst_india.utils import get_gst_accounts_by_type class IneligibleITC: @@ -32,41 +34,27 @@ def update_valuation_rate(self): - Only updates if its a stock item or fixed asset - No updates for expense items """ - self.doc._has_ineligible_itc_items = False - stock_items = self.doc.get_stock_items() - - for item in self.doc.items: - if ( - not self.is_eligibility_restricted_due_to_pos() - and not item.is_ineligible_for_itc - ): - continue - self.update_ineligible_taxes(item) + self.update_item_ineligibility() - if item._ineligible_tax_amount: - self.doc._has_ineligible_itc_items = True + if not self.doc.get("_has_ineligible_itc_items"): + return - if item.item_code in stock_items and self.is_perpetual: - item._is_stock_item = True + for item in self.doc.items: + if not item.get("_ineligible_tax_amount"): + continue if item.get("_is_stock_item") or item.get("is_fixed_asset"): ineligible_tax_amount = item._ineligible_tax_amount if self.doc.get("is_return"): ineligible_tax_amount = -ineligible_tax_amount - # TODO: handle rounding off of gst amount from gst settings self.update_item_valuation_rate(item, ineligible_tax_amount) def update_gl_entries(self, gl_entries): self.gl_entries = gl_entries - if ( - frappe.flags.through_repost_accounting_ledger - or frappe.flags.through_repost_item_valuation - ): - self.doc.update_valuation_rate() - self.update_valuation_rate() + self.update_item_ineligibility() if not self.doc.get("_has_ineligible_itc_items"): return gl_entries @@ -84,6 +72,34 @@ def update_gl_entries(self, gl_entries): self.update_item_gl_entries(item) + def update_item_ineligibility(self): + self.doc._has_ineligible_itc_items = False + stock_items = self.doc.get_stock_items() + + self.tax_account_dict = { + row.gst_tax_type: row.account_head + for row in self.doc.taxes + if row.gst_tax_type + } + + if not self.tax_account_dict: + return + + for item in self.doc.items: + if ( + not self.is_eligibility_restricted_due_to_pos() + and not item.is_ineligible_for_itc + ): + continue + + self.update_ineligible_taxes(item) + + if item._ineligible_tax_amount: + self.doc._has_ineligible_itc_items = True + + if item.item_code in stock_items and self.is_perpetual: + item._is_stock_item = True + def update_item_gl_entries(self, item): return @@ -272,40 +288,25 @@ def update_ineligible_taxes(self, item): "Input SGST - FC": 50, } """ - gst_accounts = get_gst_accounts_by_type(self.doc.company, "Input").values() - ineligible_taxes = frappe._dict() + ineligible_taxes = defaultdict(float) + ineligible_tax_amount = 0 - for tax in self.doc.taxes: - if tax.account_head not in gst_accounts: + for tax_type in GST_TAX_TYPES: + tax_amount = abs(flt(item.get(f"{tax_type}_amount"))) + tax_account = self.tax_account_dict.get(tax_type) + + if not tax_amount: continue - ineligible_taxes[tax.account_head] = self.get_item_tax_amount(item, tax) + ineligible_taxes[tax_account] += tax_amount + ineligible_tax_amount += tax_amount item._ineligible_taxes = ineligible_taxes - item._ineligible_tax_amount = sum(ineligible_taxes.values()) + item._ineligible_tax_amount = ineligible_tax_amount def update_item_valuation_rate(self, item, ineligible_tax_amount): item.valuation_rate += flt(ineligible_tax_amount / item.stock_qty, 2) - def get_item_tax_amount(self, item, tax): - """ - Returns proportionate item tax amount for each tax component - """ - tax_rate = rounded( - frappe.parse_json(tax.item_wise_tax_detail).get( - item.item_code or item.item_name - )[0], - 3, - ) - - tax_amount = ( - tax_rate * item.qty - if tax.charge_type == "On Item Quantity" - else tax_rate * item.taxable_value / 100 - ) - - return abs(tax_amount) - def is_debit_entry_required(self, item): return True @@ -417,16 +418,6 @@ def update_valuation_rate(self): super().update_valuation_rate() - def get_item_tax_amount(self, item, tax): - tax_rate = frappe.parse_json(tax.item_wise_tax_rates).get(item.name) - if tax_rate is None: - return 0 - - tax_rate = rounded(tax_rate, 3) - tax_amount = tax_rate * item.taxable_value / 100 - - return abs(tax_amount) - def update_item_valuation_rate(self, item, ineligible_tax_amount): item.valuation_rate = ineligible_tax_amount diff --git a/india_compliance/gst_india/overrides/party.py b/india_compliance/gst_india/overrides/party.py index dfb6d5433..23d8b1d68 100644 --- a/india_compliance/gst_india/overrides/party.py +++ b/india_compliance/gst_india/overrides/party.py @@ -41,9 +41,13 @@ def set_gst_category(doc): def fetch_or_guess_gst_category(doc): + # High Seas Sales + if doc.gst_category == "Overseas": + return doc.gst_category + # Any transaction can be treated as deemed export if doc.gstin and doc.gst_category == "Deemed Export": - return "Deemed Export" + return doc.gst_category if doc.gstin and is_autofill_party_info_enabled(): gstin_info = _get_gstin_info(doc.gstin, throw_error=False) or {} diff --git a/india_compliance/gst_india/overrides/payment_entry.py b/india_compliance/gst_india/overrides/payment_entry.py index 62d961468..15962adfa 100644 --- a/india_compliance/gst_india/overrides/payment_entry.py +++ b/india_compliance/gst_india/overrides/payment_entry.py @@ -7,6 +7,7 @@ from erpnext.accounts.utils import create_payment_ledger_entry from erpnext.controllers.accounts_controller import get_advance_payment_entries +from india_compliance.gst_india.constants import TAX_TYPES from india_compliance.gst_india.overrides.transaction import get_gst_details from india_compliance.gst_india.overrides.transaction import ( validate_backdated_transaction as _validate_backdated_transaction, @@ -14,10 +15,7 @@ from india_compliance.gst_india.overrides.transaction import ( validate_transaction as validate_transaction_for_advance_payment, ) -from india_compliance.gst_india.utils import ( - get_all_gst_accounts, - get_gst_accounts_by_type, -) +from india_compliance.gst_india.utils import get_all_gst_accounts @frappe.whitelist() @@ -92,9 +90,8 @@ def validate(doc, method=None): validate_transaction_for_advance_payment(doc, method) else: - gst_accounts = get_all_gst_accounts(doc.company) for row in doc.taxes: - if row.account_head in gst_accounts and row.tax_amount != 0: + if row.gst_tax_type in TAX_TYPES and row.tax_amount != 0: frappe.throw( _("GST Taxes are not allowed for Supplier Advance Payment Entry") ) @@ -116,9 +113,8 @@ def before_cancel(doc, method=None): def validate_backdated_transaction(doc, action="create"): - gst_accounts = get_gst_accounts_by_type(doc.company, "Output").values() for row in doc.taxes: - if row.account_head in gst_accounts and row.tax_amount != 0: + if row.gst_tax_type in TAX_TYPES and row.tax_amount != 0: _validate_backdated_transaction(doc, action=action) break diff --git a/india_compliance/gst_india/overrides/purchase_invoice.py b/india_compliance/gst_india/overrides/purchase_invoice.py index aca5e27a4..1d53302fb 100644 --- a/india_compliance/gst_india/overrides/purchase_invoice.py +++ b/india_compliance/gst_india/overrides/purchase_invoice.py @@ -6,7 +6,7 @@ update_dashboard_with_gst_logs, ) from india_compliance.gst_india.overrides.transaction import validate_transaction -from india_compliance.gst_india.utils import get_gst_accounts_by_type, is_api_enabled +from india_compliance.gst_india.utils import is_api_enabled from india_compliance.gst_india.utils.e_waybill import get_e_waybill_info @@ -96,19 +96,17 @@ def update_itc_totals(doc, method=None): if doc.ineligibility_reason == "ITC restricted due to PoS rules": return - gst_accounts = get_gst_accounts_by_type(doc.company, "Input") - for tax in doc.get("taxes"): - if tax.account_head == gst_accounts.igst_account: + if tax.gst_tax_type == "igst": doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount) - if tax.account_head == gst_accounts.sgst_account: + if tax.gst_tax_type == "sgst": doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount) - if tax.account_head == gst_accounts.cgst_account: + if tax.gst_tax_type == "cgst": doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount) - if tax.account_head == gst_accounts.cess_account: + if tax.gst_tax_type == "cess": doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) @@ -174,11 +172,10 @@ def validate_with_inward_supply(doc): mismatch_fields["Taxable Value"] = doc._inward_supply.get("taxable_value") # mismatch for taxes - gst_accounts = get_gst_accounts_by_type(doc.company, "Input") for tax in ["cgst", "sgst", "igst", "cess"]: - tax_amount = get_tax_amount(doc.taxes, gst_accounts[tax + "_account"]) + tax_amount = get_tax_amount(doc.taxes, tax) if tax == "cess": - tax_amount += get_tax_amount(doc.taxes, gst_accounts.cess_non_advol_account) + tax_amount += get_tax_amount(doc.taxes, "cess_non_advol") if tax_amount == doc._inward_supply.get(tax): continue @@ -206,15 +203,15 @@ def validate_with_inward_supply(doc): ) -def get_tax_amount(taxes, account_head): - if not (taxes or account_head): +def get_tax_amount(taxes, gst_tax_type): + if not (taxes or gst_tax_type): return 0 return sum( [ tax.base_tax_amount_after_discount_amount for tax in taxes - if tax.account_head == account_head + if tax.gst_tax_type == gst_tax_type ] ) diff --git a/india_compliance/gst_india/overrides/purchase_receipt.py b/india_compliance/gst_india/overrides/purchase_receipt.py index 3cedd46bb..bdaf9b294 100644 --- a/india_compliance/gst_india/overrides/purchase_receipt.py +++ b/india_compliance/gst_india/overrides/purchase_receipt.py @@ -24,17 +24,14 @@ def onload(doc, method=None): if ignore_gst_validations(doc): return - doc.flags.ignore_mandatory = True if ( validate_mandatory_fields( - doc, ("company_gstin", "place_of_supply", "gst_category") + doc, ("company_gstin", "place_of_supply", "gst_category"), throw=False ) is False ): return - doc.flags.ignore_mandatory = False - set_ineligibility_reason(doc, show_alert=False) diff --git a/india_compliance/gst_india/overrides/test_ineligible_itc.py b/india_compliance/gst_india/overrides/test_ineligible_itc.py index d9df89581..c8f749fd8 100644 --- a/india_compliance/gst_india/overrides/test_ineligible_itc.py +++ b/india_compliance/gst_india/overrides/test_ineligible_itc.py @@ -518,6 +518,7 @@ def test_purchase_returns_with_update_stock(self): doc = create_transaction(**transaction_details) doc = make_return_doc("Purchase Invoice", doc.name) + doc.save() doc.submit() self.assertGLEntry( diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 1494051d2..02a2c1bfc 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -7,9 +7,11 @@ from erpnext.controllers.accounts_controller import get_taxes_and_charges from india_compliance.gst_india.constants import ( + GST_RCM_TAX_TYPES, GST_TAX_TYPES, SALES_DOCTYPES, STATE_NUMBERS, + TAX_TYPES, ) from india_compliance.gst_india.constants.custom_fields import E_WAYBILL_INV_FIELDS from india_compliance.gst_india.doctype.gst_settings.gst_settings import ( @@ -18,7 +20,7 @@ from india_compliance.gst_india.doctype.gstin.gstin import get_and_validate_gstin_status from india_compliance.gst_india.utils import ( get_all_gst_accounts, - get_gst_accounts_by_tax_type, + get_gst_account_gst_tax_type_map, get_gst_accounts_by_type, get_hsn_settings, get_place_of_supply, @@ -56,7 +58,8 @@ def set_gst_breakup(doc): doc.gst_breakup_table = gst_breakup_html.replace("\n", "").replace(" ", "") -def update_taxable_values(doc, valid_accounts): +def update_taxable_values(doc): + if doc.doctype not in DOCTYPES_WITH_GST_DETAIL: return @@ -69,7 +72,7 @@ def update_taxable_values(doc, valid_accounts): row for row in doc.taxes if row.base_tax_amount_after_discount_amount - and row.account_head in valid_accounts + and row.gst_tax_type in TAX_TYPES ): reference_row_index = next( ( @@ -77,7 +80,7 @@ def update_taxable_values(doc, valid_accounts): for row in doc.taxes if row.base_tax_amount_after_discount_amount and row.charge_type == "On Previous Row Total" - and row.account_head in valid_accounts + and row.gst_tax_type in TAX_TYPES ), None, # ignore accounts after GST accounts ) @@ -120,22 +123,20 @@ def update_taxable_values(doc, valid_accounts): item.taxable_value += total_charges - apportioned_charges -def validate_item_wise_tax_detail(doc, gst_accounts): +def validate_item_wise_tax_detail(doc): if doc.doctype not in DOCTYPES_WITH_GST_DETAIL: return item_taxable_values = defaultdict(float) item_qty_map = defaultdict(float) - cess_non_advol_account = get_gst_accounts_by_tax_type(doc.company, "cess_non_advol") - for row in doc.items: item_key = row.item_code or row.item_name item_taxable_values[item_key] += row.taxable_value item_qty_map[item_key] += row.qty for row in doc.taxes: - if row.account_head not in gst_accounts: + if not row.gst_tax_type: continue if row.charge_type != "Actual": @@ -156,7 +157,7 @@ def validate_item_wise_tax_detail(doc, gst_accounts): # Sales Invoice is created with manual tax amount. So, when a sales return is created, # the tax amount is not recalculated, causing the issue. - is_cess_non_advol = row.account_head in cess_non_advol_account + is_cess_non_advol = "cess_non_advol" in row.gst_tax_type multiplier = ( item_qty_map.get(item_name, 0) if is_cess_non_advol @@ -205,7 +206,7 @@ def is_indian_registered_company(doc): return True -def validate_mandatory_fields(doc, fields, error_message=None): +def validate_mandatory_fields(doc, fields, error_message=None, throw=True): if isinstance(fields, str): fields = (fields,) @@ -219,6 +220,9 @@ def validate_mandatory_fields(doc, fields, error_message=None): if doc.flags.ignore_mandatory: return False + if not throw: + return False + frappe.throw( error_message.format(bold(_(doc.meta.get_label(field)))), title=_("Missing Required Field"), @@ -294,6 +298,17 @@ def get_valid_accounts(company, *, for_sales=False, for_purchase=False, throw=Tr return all_valid_accounts, intra_state_accounts, inter_state_accounts +def set_gst_tax_type(doc, method=None): + if not doc.taxes: + return + + gst_tax_account_map = get_gst_account_gst_tax_type_map() + + for tax in doc.taxes: + # Setting as None if not GST Account + tax.gst_tax_type = gst_tax_account_map.get(tax.account_head) + + def validate_gst_accounts(doc, is_sales_transaction=False): """ Validate GST accounts @@ -310,19 +325,18 @@ def validate_gst_accounts(doc, is_sales_transaction=False): rows_to_validate := [ row for row in doc.taxes - if row.tax_amount and row.account_head in get_all_gst_accounts(doc.company) + if row.tax_amount and row.gst_tax_type and row.gst_tax_type in TAX_TYPES ] ): return # Helper functions - - def _get_matched_idx(rows_to_search, account_head_list): + def _get_matched_idx(rows_to_search, gst_tax_type_list): return next( ( row.idx for row in rows_to_search - if row.account_head in account_head_list + if row.gst_tax_type in gst_tax_type_list ), None, ) @@ -330,15 +344,6 @@ def _get_matched_idx(rows_to_search, account_head_list): def _throw(message, title=None): frappe.throw(message, title=title or _("Invalid GST Account")) - all_valid_accounts, intra_state_accounts, inter_state_accounts = get_valid_accounts( - doc.company, - for_sales=is_sales_transaction, - for_purchase=not is_sales_transaction, - ) - cess_non_advol_accounts = get_gst_accounts_by_tax_type( - doc.company, "cess_non_advol" - ) - # Company GSTIN = Party GSTIN party_gstin = ( doc.billing_address_gstin if is_sales_transaction else doc.supplier_gstin @@ -346,7 +351,7 @@ def _throw(message, title=None): if ( party_gstin and doc.company_gstin == party_gstin - and (idx := _get_matched_idx(rows_to_validate, all_valid_accounts)) + and (idx := _get_matched_idx(rows_to_validate, TAX_TYPES)) ): _throw( _( @@ -358,7 +363,7 @@ def _throw(message, title=None): # Sales / Purchase Validations if is_sales_transaction: if is_export_without_payment_of_gst(doc) and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -368,7 +373,7 @@ def _throw(message, title=None): ) if doc.get("is_reverse_charge") and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -377,7 +382,7 @@ def _throw(message, title=None): ) elif doc.gst_category == "Registered Composition" and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -389,7 +394,7 @@ def _throw(message, title=None): elif not doc.is_reverse_charge: if idx := _get_matched_idx( rows_to_validate, - get_gst_accounts_by_type(doc.company, "Reverse Charge").values(), + GST_RCM_TAX_TYPES, ): _throw( _( @@ -399,7 +404,7 @@ def _throw(message, title=None): ) if not doc.supplier_gstin and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -411,6 +416,12 @@ def _throw(message, title=None): is_inter_state = is_inter_state_supply(doc) previous_row_references = set() + all_valid_accounts, intra_state_accounts, inter_state_accounts = get_valid_accounts( + doc.company, + for_sales=is_sales_transaction, + for_purchase=not is_sales_transaction, + ) + for row in rows_to_validate: account_head = row.account_head if account_head not in all_valid_accounts: @@ -445,12 +456,11 @@ def _throw(message, title=None): ).format(row.idx), title=_("Invalid Charge Type"), ) - if row.charge_type == "On Previous Row Total": previous_row_references.add(row.row_id) # validating charge type "On Item Quantity" and non_cess_advol_account - validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, row) + validate_charge_type_for_cess_non_advol_accounts(row) used_accounts = set(row.account_head for row in rows_to_validate) if not is_inter_state: @@ -471,15 +481,12 @@ def _throw(message, title=None): ), title=_("Invalid Reference Row"), ) - for row in doc.get("items") or []: if not row.item_tax_template: continue - for account in used_accounts: if account in row.item_tax_rate: continue - frappe.msgprint( _( "Item Row #{0}: GST Account {1} is missing in Item Tax Template {2}" @@ -488,13 +495,10 @@ def _throw(message, title=None): indicator="orange", ) - return all_valid_accounts - -def validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, tax_row): - if ( - tax_row.charge_type == "On Item Quantity" - and tax_row.account_head not in cess_non_advol_accounts +def validate_charge_type_for_cess_non_advol_accounts(tax_row): + if tax_row.charge_type == "On Item Quantity" and ( + tax_row.gst_tax_type not in ("cess_non_advol", "cess_non_advol_rcm") ): frappe.throw( _( @@ -504,9 +508,8 @@ def validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, ta title=_("Invalid Charge Type"), ) - if ( - tax_row.charge_type not in ["On Item Quantity", "Actual"] - and tax_row.account_head in cess_non_advol_accounts + if tax_row.charge_type not in ["On Item Quantity", "Actual"] and ( + "cess_non_advol" in tax_row.gst_tax_type ): frappe.throw( _( @@ -910,19 +913,13 @@ def validate_reverse_charge_transaction(doc, method=None): if not doc.is_reverse_charge: return - reverse_charge_accounts = get_gst_accounts_by_type( - doc.company, "Reverse Charge" - ).values() - - input_gst_accounts = get_gst_accounts_by_type(doc.company, "Input").values() - for tax in doc.get("taxes"): - if tax.account_head in input_gst_accounts: + if tax.gst_tax_type in GST_TAX_TYPES: if tax.add_deduct_tax == "Add": base_gst_tax += tax.base_tax_amount_after_discount_amount else: base_gst_tax += tax.base_tax_amount_after_discount_amount - elif tax.account_head in reverse_charge_accounts: + elif tax.gst_tax_type in GST_RCM_TAX_TYPES: if tax.add_deduct_tax == "Add": base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount else: @@ -956,14 +953,11 @@ def get(self, docs, doctype, company): """ Return Item GST Details for a list of documents """ - self.set_gst_accounts_and_item_defaults(doctype, company) + self.get_item_defaults() self.set_tax_amount_precisions(doctype) response = frappe._dict() - if not self.gst_account_map: - return response - for doc in docs: self.doc = doc if not doc.get("items") or not doc.get("taxes"): @@ -984,23 +978,12 @@ def update(self, doc): if not self.doc.get("items"): return - self.set_gst_accounts_and_item_defaults(doc.doctype, doc.company) - if not self.gst_account_map: - return - + self.get_item_defaults() self.set_tax_amount_precisions(doc.doctype) self.set_item_wise_tax_details() self.update_item_tax_details() - def set_gst_accounts_and_item_defaults(self, doctype, company): - if doctype in SALES_DOCTYPES: - account_type = "Output" - else: - account_type = "Input" - - gst_account_map = get_gst_accounts_by_type(company, account_type, throw=False) - self.gst_account_map = {v: k for k, v in gst_account_map.items()} - + def get_item_defaults(self): item_defaults = frappe._dict(count=0) for row in GST_TAX_TYPES: @@ -1042,13 +1025,12 @@ def set_item_wise_tax_details(self): for row in self.doc.taxes: if ( not row.base_tax_amount_after_discount_amount + or row.gst_tax_type not in GST_TAX_TYPES or not row.item_wise_tax_detail - or row.account_head not in self.gst_account_map ): continue - account_type = self.gst_account_map[row.account_head] - tax = account_type[:-8] + tax = row.gst_tax_type tax_rate_field = f"{tax}_rate" tax_amount_field = f"{tax}_amount" @@ -1159,10 +1141,7 @@ def set(self, doc): self.set_for_overseas() return - self.gst_accounts = get_all_gst_accounts(self.doc.company) - has_gst_accounts = any( - row.account_head in self.gst_accounts for row in self.doc.taxes - ) + has_gst_accounts = any(row.gst_tax_type in TAX_TYPES for row in self.doc.taxes) if not has_gst_accounts: self.set_for_no_taxes() @@ -1221,7 +1200,7 @@ def get_default_treatment(self): if row.charge_type in ("Actual", "On Item Quantity"): continue - if row.account_head not in self.gst_accounts: + if row.gst_tax_type not in GST_TAX_TYPES: continue if row.rate == 0: @@ -1399,9 +1378,10 @@ def validate_transaction(doc, method=None): validate_ecommerce_gstin(doc) validate_gst_category(doc.gst_category, gstin) - valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () - update_taxable_values(doc, valid_accounts) - validate_item_wise_tax_detail(doc, valid_accounts) + + validate_gst_accounts(doc, is_sales_transaction) + update_taxable_values(doc) + validate_item_wise_tax_detail(doc) def before_print(doc, method=None, print_settings=None): @@ -1530,9 +1510,9 @@ def before_update_after_submit(doc, method=None): if is_sales_transaction := doc.doctype in SALES_DOCTYPES: validate_hsn_codes(doc) - valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () - update_taxable_values(doc, valid_accounts) - validate_item_wise_tax_detail(doc, valid_accounts) + validate_gst_accounts(doc, is_sales_transaction) + update_taxable_values(doc) + validate_item_wise_tax_detail(doc) update_gst_details(doc) diff --git a/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json b/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json index 5eba8216e..9c036e4c7 100644 --- a/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json +++ b/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json @@ -2,7 +2,7 @@ "absolute_value": 0, "align_labels_right": 0, "creation": "2017-07-04 16:26:21.120187", - "css": "/*css for pdf*/\n@media print{\n .payment_qr img {\n width:81px !important;\n height: 81px;\n }\n}\n\n.payment_qr img {\n width:81px !important;\n height: 81px;\n}\n.payment_qr{\n float:left;\n}\n \n \n/*general css*/ \nb{\n color : initial;\n}\n\np{\n font-size : 12px !important;\n word-break:break-all;\n margin:0px !important;\n}\n\ntable{\n width:100%;\n}\n\n\n/*section after heading of print format*/\ndiv[data-label=\"First Section\"] > div:nth-child(1){\n width:40% !important;\n padding-right:0px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(2) {\n width:34% !important;\n padding-right:0px !important;\n padding-left:6px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(3) {\n width:26% !important;\n padding-left:0px !important;\n}\n\n\n/*css for place of supply*/\ndiv[data-fieldname=\"place_of_supply\"]{\n padding-top:5px !important;\n padding-bottom:5px !important;\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div > label{\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(1){\n width:50% !important;\n line-height: 1;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(2){\n width:50% !important;\n line-height: 1;\n}\n\n\n/*css for posting date*/\ndiv[data-fieldname=\"posting_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(1){\n width:50% !important;\n line-height:1.6;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(2){\n width:50% !important;\n line-height:1.6;\n}\n\n\n/*css for first section after heading second column*/\n.first_section_second_column{\n padding-bottom:5px !important;\n padding-top:5px !important;\n}\n\n\n/*css for first section after heading third column - qr code column*/\n.paid_unpaid{\n padding-bottom:5px !important;\n text-align:end;\n}\n.paid_unpaid > p{\n font-size:120% !important;\n line-height:1;\n}\n\n\n/*css for irn*/\n.print-format p.field-property{\n padding-top:5px !important;\n padding-bottom:5px !important;\n}\n\n\n/*css for due_date adn paid amount*/\ndiv[data-fieldname=\"due_date\"] > div{\n width:50%;\n}\ndiv[data-fieldname=\"due_date\"] > div > label {\n margin:0px ;\n}\ndiv[data-fieldname=\"due_date\"] > div:nth-child(2){\n width:50%;\n text-align:end;\n}\ndiv[data-fieldname=\"due_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"paid_amount\"]{\n margin-top:0px;\n margin-bottom:0px;\n padding-top:5px;\n padding-bottom:5px;\n line-height:1;\n}\ndiv[data-fieldname=\"paid_amount\"] > div > label {\n margin:0px;\n}\n\n.data-field{\n margin-top:0px;\n margin-bottom:0px;\n}\n\n\n/*changing css of erpnext elements*/\n#taxes-section > .row > .col-xs-6:first-child {\n display: none;\n}\n\n#taxes-section > .row > .col-xs-6 {\n width: 100%;\n}\n\n#taxes-section > .row > .col-xs-6 > .row {\n padding-top:5px !important;\n padding-bottom:5px !important;\n line-height:1;\n}\n\n#taxes-section > .row > .col-xs-6 > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\ndiv[data-label=\"erpnext\"] > div:nth-child(2) > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\n.column-break > .row:first-child {\n line-height:1;\n padding-bottom:5px !important;\n}\n\ndiv[data-fieldname=\"grand_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"rounded_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"in_words\"] {\n margin-top:0px ;\n margin-bottom:0px;\n line-height:1;\n padding-top:5px;\n}\n\n\n/* last section of print format*/\n", + "css": "/*css for pdf*/\n@media print{\n .payment_qr img {\n width:81px !important;\n height: 81px;\n }\n}\n\n.payment_qr img {\n width:81px !important;\n height: 81px;\n}\n.payment_qr{\n float:left;\n}\n \n \n/*general css*/ \nb{\n color : initial;\n}\n\np{\n font-size : 12px !important;\n word-break:break-all;\n margin:0px !important;\n}\n\ntable{\n width:100%;\n}\n\n\n/*section after heading of print format*/\ndiv[data-label=\"First Section\"] > div:nth-child(1){\n width:40% !important;\n padding-right:0px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(2) {\n width:34% !important;\n padding-right:0px !important;\n padding-left:6px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(3) {\n width:26% !important;\n padding-left:0px !important;\n}\n\n\n/*css for place of supply*/\ndiv[data-fieldname=\"place_of_supply\"]{\n padding-top:5px !important;\n padding-bottom:5px !important;\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div > label{\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(1){\n width:50% !important;\n line-height: 1;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(2){\n width:50% !important;\n line-height: 1;\n}\n\n\n/*css for posting date*/\ndiv[data-fieldname=\"posting_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(1){\n width:50% !important;\n line-height:1.6;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(2){\n width:50% !important;\n line-height:1.6;\n}\n\n\n/*css for first section after heading second column*/\n.first_section_second_column{\n padding-bottom:5px !important;\n padding-top:5px !important;\n}\n\n\n/*css for first section after heading third column - qr code column*/\n.paid_unpaid{\n padding-bottom:5px !important;\n text-align:end;\n}\n.paid_unpaid > p{\n font-size:120% !important;\n line-height:1;\n}\n\n\n/*css for irn*/\n.print-format p.field-property{\n padding-top:5px !important;\n padding-bottom:5px !important;\n}\n\n/*css for company-details*/\ntable.company-details {\n width:66%;\n display: inline-block;\n}\n\ntable.company-details tbody {\n display: table;\n width:100%;\n}\n\ntable.company-details:nth-child(2) {\n width: 32%;\n}\n\n/*css for due_date adn paid amount*/\ndiv[data-fieldname=\"due_date\"] > div{\n width:50%;\n}\ndiv[data-fieldname=\"due_date\"] > div > label {\n margin:0px ;\n}\ndiv[data-fieldname=\"due_date\"] > div:nth-child(2){\n width:50%;\n text-align:end;\n}\ndiv[data-fieldname=\"due_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"paid_amount\"]{\n margin-top:0px;\n margin-bottom:0px;\n padding-top:5px;\n padding-bottom:5px;\n line-height:1;\n}\ndiv[data-fieldname=\"paid_amount\"] > div > label {\n margin:0px;\n}\n\n.data-field{\n margin-top:0px;\n margin-bottom:0px;\n}\n\n\n/*changing css of erpnext elements*/\n#taxes-section > .row > .col-xs-6:first-child {\n display: none;\n}\n\n#taxes-section > .row > .col-xs-6 {\n width: 100%;\n}\n\n#taxes-section > .row > .col-xs-6 > .row {\n padding-top:5px !important;\n padding-bottom:5px !important;\n line-height:1;\n}\n\n#taxes-section > .row > .col-xs-6 > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\ndiv[data-label=\"erpnext\"] > div:nth-child(2) > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\n.column-break > .row:first-child {\n line-height:1;\n padding-bottom:5px !important;\n}\n\ndiv[data-fieldname=\"grand_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"rounded_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"in_words\"] {\n margin-top:0px ;\n margin-bottom:0px;\n line-height:1;\n padding-top:5px;\n}\n\n\n/* last section of print format*/\n", "custom_format": 0, "disabled": 0, "doc_type": "Sales Invoice", @@ -10,14 +10,14 @@ "doctype": "Print Format", "font": "Default", "font_size": 14, - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"{% set logo_for_printing = frappe.db.get_value(\\\"Company\\\",doc.company,\\\"logo_for_printing\\\") %}\\n\\n\\n \\n \\n \\n \\n \\n \\n
\\n {% if logo_for_printing != \\\"\\\" %}\\n
\\n \\n
\\n {% endif %}\\n
\\n

{{ doc.company }}

\\n
\\n
\\n

\\n {{ doc.select_print_heading if doc.select_print_heading else 'Tax Invoice' }}
\\n {{ doc.name }} \\n

\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"First Section\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n\\t\\n\\t{% if not doc.shipping_address or doc.address_display == doc.shipping_address %}\\n\\t\\t

Billing and Shipping Address

\\n\\t{% else %}\\n\\t\\t

Billed To

\\n\\t{% endif %} \\n\\t{% if doc.customer_name %}\\n\\t\\t

{{ doc.customer_name }}

\\n\\t{% endif %}\\n\\n\\t

{{ doc.address_display if doc.address_display else '' }}

\\n\\n\\t\\n\\t{% if doc.shipping_address and doc.address_display !=\\n\\t\\tdoc.shipping_address %}\\n\\t\\t

Shipped To

\\n\\t\\t\\t{% if doc.dispatch_address_name %}\\n\\t\\t\\t\\t

{{ doc.dispatch_address_name }}

\\n\\t\\t\\t{% endif %}\\n\\t\\t

{{ doc.shipping_address }}

\\n\\t{% endif %}\\n
\"}, {\"fieldname\": \"place_of_supply\", \"print_hide\": 0, \"label\": \"Place of Supply\"}, {\"fieldname\": \"is_reverse_charge\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Reverse Charge\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.contact_display or doc.contact_mobile or doc.contact_email %}\\n
\\n

Contact Person

\\n {% if doc.contact_display %}\\n

{{ doc.contact_display }}

\\n {% endif %} {% if doc.contact_mobile %}\\n

{{ doc.contact_mobile }}

\\n {% endif %} {% if doc.contact_email %}\\n

{{ doc.contact_email }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.po_no %}\\n
\\n\\t

Purchase Order Reference

\\n\\t

{{ doc.po_no }}

\\n\\t{% if doc.po_date %}\\n\\t

dtd. {{ doc.get_formatted(\\\"po_date\\\") }}

\\n\\t{% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\r\\n{% set delivery_note_list = [] %}\\r\\n{% for item in doc.items %}\\r\\n {% if item.delivery_note and item.delivery_note not in delivery_note_list %}\\r\\n {% set _ = delivery_note_list.append(item.delivery_note) %}\\r\\n {% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if delivery_note_list %}\\r\\n
\\r\\n

Delivery Note Reference

\\r\\n {% for dn in delivery_note_list %}\\r\\n {% set dn_date = frappe.db.get_value('Delivery Note', dn, 'posting_date') %}\\r\\n

{{ dn }} dtd. {{ frappe.utils.formatdate(dn_date, 'dd-MM-yyyy') }}

\\r\\n {% endfor %}\\r\\n
\\r\\n{% endif %}\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.transporter_name or doc.vehicle_no or doc.lr_no or doc.ewaybill %}\\n
\\n

Transporter Details

\\n {% if doc.transporter_name %}\\n

{{ doc.transporter_name }}

\\n {% endif %} \\n {% if doc.vehicle_no %}\\n

Vehicle No: {{ doc.vehicle_no }}

\\n {% endif %} \\n {% if doc.lr_no %}\\n

LR No: {{ doc.lr_no }}

\\n {% endif %} \\n {% if doc.ewaybill %}\\n

e-Waybill No: {{ doc.ewaybill }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n \\n {% if doc.outstanding_amount !=0 %}\\n

\\n Unpaid Amount\\n

\\n

\\n {{ doc.get_formatted(\\\"outstanding_amount\\\") }}\\n

\\n {% else %}\\n

\\n Fully Paid\\n

\\n {% endif %}\\n
\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% if doc.irn %}\\r\\n{% set e_invoice_log = frappe.db.get_value( \\\"e-Invoice Log\\\", doc.irn,\\r\\n(\\\"invoice_data\\\", \\\"signed_qr_code\\\"), as_dict=True ) %}\\r\\n\\r\\n{% if e_invoice_log %}\\r\\n{% set invoice_data = json.loads(e_invoice_log.invoice_data) %}\\r\\n{% set date_format = frappe.db.get_single_value(\\\"System Settings\\\",'date_format').replace(\\\"mm\\\",\\\"MM\\\")\\r\\n%}\\r\\n\\r\\n
\\r\\n\\t\\r\\n\\t
\\r\\n\\t\\t{{ web_block('e-Invoice QR Code', values={'e_invoice_qr_text':\\r\\n\\t\\te_invoice_log.signed_qr_code }) }}\\r\\n\\t
\\r\\n\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tIRN : {{ doc.irn }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck No. {{ invoice_data.get(\\\"AckNo\\\") if doc.irn and invoice_data.get(\\\"AckNo\\\")\\r\\n\\t\\telse '' }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck Dt.\\r\\n\\t\\t{{ frappe.utils.format_datetime(invoice_data.get(\\\"AckDt\\\"),date_format) if doc.irn and\\r\\n\\t\\tinvoice_data.get(\\\"AckDt\\\") else '' }}\\r\\n\\t

\\r\\n
\\r\\n{% endif %}\\r\\n{% endif %}\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"items\", \"print_hide\": 0, \"label\": \"Items\", \"visible_columns\": [{\"fieldname\": \"serial_no\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"gst_hsn_code\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"200px\", \"print_hide\": 0}, {\"fieldname\": \"qty\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"erpnext\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Due Date\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n{% set currency_format = frappe.db.get_single_value(\\\"System Settings\\\",\\\"number_format\\\") %}\\r\\n\\r\\n{% set bank_details = {} %}\\r\\n{% set upi_id = namespace(value=\\\"\\\") %}\\r\\n{% set bank_name = namespace(value=\\\"\\\") %}\\r\\n{% set paid_amount = namespace(value=0) %}\\r\\n\\r\\n{% for doc in company_doc.bank_details_for_printing %}\\r\\n\\t{% if doc.autofield == \\\"UPI ID\\\" %}\\r\\n\\t\\t{% set upi_id.value = doc.autofield_value %}\\r\\n\\t{% elif doc.autofield == \\\"Bank Name\\\" %}\\r\\n\\t\\t{% set bank_name.value = doc.autofield_value %}\\r\\n\\t{% else %}\\r\\n\\t\\t{% set _ = bank_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n\\t{% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.rounded_total %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.rounded_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% else %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.grand_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% endif %}\\r\\n{% set paid_amount.value = frappe.utils.fmt_money(amount=paid_amount.value, format=currency_format, currency=company_doc.default_currency) %}\\r\\n\\r\\n\\r\\n
\\r\\n\\t
\\r\\n\\t\\t\\r\\n\\t
\\r\\n\\t
{{ paid_amount.value }}
\\r\\n
\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% if doc.outstanding_amount != 0 and upi_id.value != '' %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\t{% if bank_details %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t\\t{{ web_block('UPI QR Code', values={'upi_qr_text': 'upi://pay?pa=' ~ upi_id.value ~ '&am=' ~ doc.outstanding_amount ~ '&tn=' ~ doc.name ~ '&cu=INR' }) }}\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t

UPI QR

\\r\\n\\t\\t\\t
\\r\\n\\t\\t\\t\\t

Bank Details

\\r\\n\\t\\t\\t\\t

{{ bank_name.value }}

\\r\\n\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t{% for key,value in bank_details.items() %}\\r\\n\\t\\t\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t\\t{% endfor %}\\r\\n\\t\\t\\t
\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n {%- set data = doc.taxes -%}\\n {%- set print_settings = frappe.get_doc(\\\"Print Settings\\\") -%}\\n {% include \\\"erpnext/templates/print_formats/includes/taxes.html\\\" %}\\n
\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"gst_breakup_table\", \"print_hide\": 0, \"label\": \"GST Breakup Table\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set all_details = {} %}\\r\\n\\r\\n{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n\\r\\n{% for doc in company_doc.registration_details_for_printing %}\\r\\n\\t{% set _ = all_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.company_gstin %}\\r\\n\\t{% set _ = all_details.update({ \\\"Our GSTIN\\\": doc.company_gstin, }) %}\\r\\n{% endif %}\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% for key,value in all_details.items() %}\\r\\n\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t \\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t{% if loop.index % 2 == 0 and not loop.last %}\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t{% endfor %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t
\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n{% set first_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"first_name\\\") %}\\r\\n{% set last_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"last_name\\\") %}\\r\\n{% set generated_by = first_name ~ (' ' ~ last_name if last_name else '') %}\\r\\n{% set generated_on = frappe.utils.getdate(doc.get_formatted(\\\"creation\\\")) %}\\r\\n\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

Generated By : {{ generated_by }}

Generated On: {{ generated_on }}

\\r\\n{% else %}\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

For {{ doc.company }}

Authorized By

\\r\\n{% endif %}\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n\\t

This is computer-generated document and no signature is required

\\r\\n{% endif %}\"}]", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"{% set logo_for_printing = frappe.db.get_value(\\\"Company\\\",doc.company,\\\"logo_for_printing\\\") %}\\n\\n\\n \\n \\n \\n \\n \\n \\n
\\n {% if logo_for_printing != \\\"\\\" %}\\n
\\n \\n
\\n {% endif %}\\n
\\n

{{ doc.company }}

\\n
\\n
\\n

\\n {{ doc.select_print_heading if doc.select_print_heading else 'Tax Invoice' }}
\\n {{ doc.name }} \\n

\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"First Section\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n\\t\\n\\t{% if not doc.shipping_address or doc.address_display == doc.shipping_address %}\\n\\t\\t

Billing and Shipping Address

\\n\\t{% else %}\\n\\t\\t

Billed To

\\n\\t{% endif %} \\n\\t{% if doc.customer_name %}\\n\\t\\t

{{ doc.customer_name }}

\\n\\t{% endif %}\\n\\n\\t

{{ doc.address_display if doc.address_display else '' }}

\\n\\n\\t\\n\\t{% if doc.shipping_address and doc.address_display !=\\n\\t\\tdoc.shipping_address %}\\n\\t\\t

Shipped To

\\n\\t\\t\\t{% if doc.dispatch_address_name %}\\n\\t\\t\\t\\t

{{ doc.dispatch_address_name }}

\\n\\t\\t\\t{% endif %}\\n\\t\\t

{{ doc.shipping_address }}

\\n\\t{% endif %}\\n
\"}, {\"fieldname\": \"place_of_supply\", \"print_hide\": 0, \"label\": \"Place of Supply\"}, {\"fieldname\": \"is_reverse_charge\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Reverse Charge\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.contact_display or doc.contact_mobile or doc.contact_email %}\\n
\\n

Contact Person

\\n {% if doc.contact_display %}\\n

{{ doc.contact_display }}

\\n {% endif %} {% if doc.contact_mobile %}\\n

{{ doc.contact_mobile }}

\\n {% endif %} {% if doc.contact_email %}\\n

{{ doc.contact_email }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.po_no %}\\n
\\n\\t

Purchase Order Reference

\\n\\t

{{ doc.po_no }}

\\n\\t{% if doc.po_date %}\\n\\t

dtd. {{ doc.get_formatted(\\\"po_date\\\") }}

\\n\\t{% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\r\\n{% set delivery_note_list = [] %}\\r\\n{% for item in doc.items %}\\r\\n {% if item.delivery_note and item.delivery_note not in delivery_note_list %}\\r\\n {% set _ = delivery_note_list.append(item.delivery_note) %}\\r\\n {% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if delivery_note_list %}\\r\\n
\\r\\n

Delivery Note Reference

\\r\\n {% for dn in delivery_note_list %}\\r\\n {% set dn_date = frappe.db.get_value('Delivery Note', dn, 'posting_date') %}\\r\\n

{{ dn }} dtd. {{ frappe.utils.formatdate(dn_date, 'dd-MM-yyyy') }}

\\r\\n {% endfor %}\\r\\n
\\r\\n{% endif %}\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.transporter_name or doc.vehicle_no or doc.lr_no or doc.ewaybill %}\\n
\\n

Transporter Details

\\n {% if doc.transporter_name %}\\n

{{ doc.transporter_name }}

\\n {% endif %} \\n {% if doc.vehicle_no %}\\n

Vehicle No: {{ doc.vehicle_no }}

\\n {% endif %} \\n {% if doc.lr_no %}\\n

LR No: {{ doc.lr_no }}

\\n {% endif %} \\n {% if doc.ewaybill %}\\n

e-Waybill No: {{ doc.ewaybill }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n \\n {% if doc.outstanding_amount !=0 %}\\n

\\n Unpaid Amount\\n

\\n

\\n {{ doc.get_formatted(\\\"outstanding_amount\\\") }}\\n

\\n {% else %}\\n

\\n Fully Paid\\n

\\n {% endif %}\\n
\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% if doc.irn %}\\r\\n{% set e_invoice_log = frappe.db.get_value( \\\"e-Invoice Log\\\", doc.irn,\\r\\n(\\\"invoice_data\\\", \\\"signed_qr_code\\\"), as_dict=True ) %}\\r\\n\\r\\n{% if e_invoice_log %}\\r\\n{% set invoice_data = json.loads(e_invoice_log.invoice_data) %}\\r\\n{% set date_format = frappe.db.get_single_value(\\\"System Settings\\\",'date_format').replace(\\\"mm\\\",\\\"MM\\\")\\r\\n%}\\r\\n\\r\\n
\\r\\n\\t\\r\\n\\t
\\r\\n\\t\\t{{ web_block('e-Invoice QR Code', values={'e_invoice_qr_text':\\r\\n\\t\\te_invoice_log.signed_qr_code }) }}\\r\\n\\t
\\r\\n\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tIRN : {{ doc.irn }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck No. {{ invoice_data.get(\\\"AckNo\\\") if doc.irn and invoice_data.get(\\\"AckNo\\\")\\r\\n\\t\\telse '' }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck Dt.\\r\\n\\t\\t{{ frappe.utils.format_datetime(invoice_data.get(\\\"AckDt\\\"),date_format) if doc.irn and\\r\\n\\t\\tinvoice_data.get(\\\"AckDt\\\") else '' }}\\r\\n\\t

\\r\\n
\\r\\n{% endif %}\\r\\n{% endif %}\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"items\", \"print_hide\": 0, \"label\": \"Items\", \"visible_columns\": [{\"fieldname\": \"serial_no\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"gst_hsn_code\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"200px\", \"print_hide\": 0}, {\"fieldname\": \"qty\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"erpnext\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Due Date\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n{% set currency_format = frappe.db.get_single_value(\\\"System Settings\\\",\\\"number_format\\\") %}\\r\\n\\r\\n{% set bank_details = {} %}\\r\\n{% set upi_id = namespace(value=\\\"\\\") %}\\r\\n{% set bank_name = namespace(value=\\\"\\\") %}\\r\\n{% set paid_amount = namespace(value=0) %}\\r\\n\\r\\n{% for doc in company_doc.bank_details_for_printing %}\\r\\n\\t{% if doc.autofield == \\\"UPI ID\\\" %}\\r\\n\\t\\t{% set upi_id.value = doc.autofield_value %}\\r\\n\\t{% elif doc.autofield == \\\"Bank Name\\\" %}\\r\\n\\t\\t{% set bank_name.value = doc.autofield_value %}\\r\\n\\t{% else %}\\r\\n\\t\\t{% set _ = bank_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n\\t{% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.rounded_total %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.rounded_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% else %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.grand_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% endif %}\\r\\n{% set paid_amount.value = frappe.utils.fmt_money(amount=paid_amount.value, format=currency_format, currency=company_doc.default_currency) %}\\r\\n\\r\\n\\r\\n
\\r\\n\\t
\\r\\n\\t\\t\\r\\n\\t
\\r\\n\\t
{{ paid_amount.value }}
\\r\\n
\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% if doc.outstanding_amount != 0 and upi_id.value != '' %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\t{% if bank_details %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t\\t{{ web_block('UPI QR Code', values={'upi_qr_text': 'upi://pay?pa=' ~ upi_id.value ~ '&am=' ~ doc.outstanding_amount ~ '&tn=' ~ doc.name ~ '&cu=INR' }) }}\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t

UPI QR

\\r\\n\\t\\t\\t
\\r\\n\\t\\t\\t\\t

Bank Details

\\r\\n\\t\\t\\t\\t

{{ bank_name.value }}

\\r\\n\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t{% for key,value in bank_details.items() %}\\r\\n\\t\\t\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t\\t{% endfor %}\\r\\n\\t\\t\\t
\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n {%- set data = doc.taxes -%}\\n {%- set print_settings = frappe.get_doc(\\\"Print Settings\\\") -%}\\n {% include \\\"erpnext/templates/print_formats/includes/taxes.html\\\" %}\\n
\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"gst_breakup_table\", \"print_hide\": 0, \"label\": \"GST Breakup Table\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set all_details = {} %}\\r\\n\\r\\n{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n\\r\\n{% for doc in company_doc.registration_details_for_printing %}\\r\\n\\t{% set _ = all_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.company_gstin %}\\r\\n\\t{% set _ = all_details.update({ \\\"Our GSTIN\\\": doc.company_gstin, }) %}\\r\\n{% endif %}\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% for key,value in all_details.items() %}\\r\\n\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t \\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t{% if loop.index % 2 == 0 and not loop.last %}\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t{% endfor %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t
\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n{% set first_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"first_name\\\") %}\\r\\n{% set last_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"last_name\\\") %}\\r\\n{% set generated_by = first_name ~ (' ' ~ last_name if last_name else '') %}\\r\\n\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

Generated By : {{ generated_by }}

Generated On: {{ doc.get_formatted(\\\"creation\\\") }}

\\r\\n{% else %}\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

For {{ doc.company }}

Authorized By

\\r\\n{% endif %}\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n\\t

This is computer-generated document and no signature is required

\\r\\n{% endif %}\"}]", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2024-06-18 15:53:21.428025", + "modified": "2024-06-22 15:07:56.623009", "modified_by": "Administrator", "module": "GST India", "name": "GST Tax Invoice", diff --git a/india_compliance/gst_india/report/gstr_1/gstr_1.py b/india_compliance/gst_india/report/gstr_1/gstr_1.py index 76e9e77ca..55f2549f6 100644 --- a/india_compliance/gst_india/report/gstr_1/gstr_1.py +++ b/india_compliance/gst_india/report/gstr_1/gstr_1.py @@ -11,6 +11,7 @@ from frappe.query_builder.functions import Date, IfNull, Sum from frappe.utils import cint, flt, formatdate, getdate +from india_compliance.gst_india.constants.__init__ import GST_TAX_TYPES from india_compliance.gst_india.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import ( get_columns as get_hsn_columns, ) @@ -108,7 +109,7 @@ def get_data(self): elif self.filters.get("type_of_business") == "Document Issued Summary": self.get_documents_issued_data() elif self.filters.get("type_of_business") == "HSN": - self.data = get_hsn_data(self.filters, self.columns, self.gst_accounts) + self.data = get_hsn_data(self.filters, self.columns) elif self.filters.get("type_of_business") == "Section 14": self.data = self.get_data_for_supplies_through_ecommerce_operators() elif self.invoices: @@ -509,7 +510,7 @@ def get_invoice_wise_tax_details(self): invoice_tax_details = frappe.db.sql( """ select - parent, account_head, item_wise_tax_detail + parent, account_head, item_wise_tax_detail,gst_tax_type from `tab%s` where parenttype = %s and docstatus = 1 @@ -521,11 +522,11 @@ def get_invoice_wise_tax_details(self): ) invoice_item_wise_tax_details = frappe._dict() - for parent, account, item_wise_tax_detail in invoice_tax_details: + for parent, account, item_wise_tax_detail, gst_tax_type in invoice_tax_details: if not item_wise_tax_detail: continue - if account not in self.gst_accounts.values(): + if gst_tax_type not in GST_TAX_TYPES: if "gst" in account.lower(): unidentified_gst_accounts.add(account) continue @@ -535,14 +536,8 @@ def get_invoice_wise_tax_details(self): except ValueError: continue - is_cess = account in ( - self.gst_accounts.cess_account, - self.gst_accounts.cess_non_advol_account, - ) - is_cgst_or_sgst = ( - account == self.gst_accounts.cgst_account - or account == self.gst_accounts.sgst_account - ) + is_cess = "cess" in gst_tax_type + is_cgst_or_sgst = gst_tax_type in ("cgst", "sgst") parent_dict = invoice_item_wise_tax_details.setdefault(parent, {}) for item_code, invoice_tax_details in item_wise_tax_detail.items(): diff --git a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py index 9c9ab9ed9..2e48cc05b 100644 --- a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py +++ b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py @@ -6,7 +6,7 @@ from frappe.query_builder import Case, DatePart from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Extract, Ifnull, IfNull, LiteralValue, Sum -from frappe.utils import cint, flt, get_first_day, get_last_day +from frappe.utils import cint, get_first_day, get_last_day from india_compliance.gst_india.utils import get_escaped_gst_accounts @@ -181,7 +181,7 @@ def get_itc_from_boe(self): Sum( Case() .when( - boe_taxes.account_head == self.gst_accounts.igst_account, + boe_taxes.gst_tax_type == "igst", boe_taxes.tax_amount, ) .else_(0) @@ -189,7 +189,7 @@ def get_itc_from_boe(self): Sum( Case() .when( - boe_taxes.account_head == self.gst_accounts.cess_account, + boe_taxes.gst_tax_type == "cess", boe_taxes.tax_amount, ) .else_(0) @@ -270,7 +270,7 @@ def get_itc_from_journal_entry(self): def get_ineligible_itc_from_purchase(self): ineligible_itc = IneligibleITC( self.company, self.company_gstin, self.filters.month, self.filters.year - ).get_ineligible_itc_us_17_5_for_purchase() + ).get_for_purchase("Ineligible As Per Section 17(5)") return self.process_ineligible_itc(ineligible_itc) @@ -419,275 +419,61 @@ def get_inward_nil_exempt(self): class IneligibleITC: def __init__(self, company, gstin, month, year) -> None: - self.gl_entry = frappe.qb.DocType("GL Entry") self.company = company self.gstin = gstin self.month = month self.year = year - self.gst_accounts = get_escaped_gst_accounts(company, "Input") - def get_ineligible_itc_us_17_5_for_purchase(self, group_by="name"): - """ - - Ineligible As Per Section 17(5) - - ITC restricted due to ineligible items in purchase invoice - """ - ineligible_transactions = self.get_vouchers_with_gst_expense("Purchase Invoice") + def get_for_purchase(self, ineligibility_reason, group_by="name"): + doctype = "Purchase Invoice" + dt = frappe.qb.DocType(doctype) + dt_item = frappe.qb.DocType(f"{doctype} Item") - if not ineligible_transactions: - return [] - - pi = frappe.qb.DocType("Purchase Invoice") - - credit_availed = ( - self.get_gl_entry_query("Purchase Invoice") - .inner_join(pi) - .on(pi.name == self.gl_entry.voucher_no) - .select(*self.select_net_gst_amount_from_gl_entry()) - .select( - pi.name.as_("voucher_no"), - pi.ineligibility_reason.as_("itc_classification"), - ) - .where( - IfNull(pi.ineligibility_reason, "") == "Ineligible As Per Section 17(5)" - ) - .where(pi.name.isin(ineligible_transactions)) - .groupby(pi[group_by]) - .run(as_dict=1) - ) - - credit_available = ( - frappe.qb.from_(pi) - .select( - ConstantColumn("Purchase Invoice").as_("voucher_type"), - pi.name.as_("voucher_no"), - pi.posting_date, - pi.ineligibility_reason.as_("itc_classification"), - Sum(pi.itc_integrated_tax).as_("iamt"), - Sum(pi.itc_central_tax).as_("camt"), - Sum(pi.itc_state_tax).as_("samt"), - Sum(pi.itc_cess_amount).as_("csamt"), - ) - .where( - IfNull(pi.ineligibility_reason, "") == "Ineligible As Per Section 17(5)" - ) - .where(pi.name.isin(ineligible_transactions)) - .groupby(pi[group_by]) - .run(as_dict=1) + query = ( + self.get_common_query(doctype, dt, dt_item) + .select((dt.ineligibility_reason).as_("itc_classification")) + .where((dt.is_opening == "No")) + .where(IfNull(dt.ineligibility_reason, "") == ineligibility_reason) ) - return self.get_ineligible_credit(credit_availed, credit_available, group_by) - - def get_ineligible_itc_due_to_pos_for_purchase(self, group_by="name"): - """ - - ITC restricted due to PoS rules - """ - ineligible_transactions = self.get_vouchers_with_gst_expense("Purchase Invoice") - - if not ineligible_transactions: - return [] - - pi = frappe.qb.DocType("Purchase Invoice") - taxes = frappe.qb.DocType("Purchase Taxes and Charges") - - # utility function - def get_tax_case_statement(account, alias): - return Sum( - Case() - .when( - taxes.account_head.isin(account), - taxes.base_tax_amount_after_discount_amount, - ) - .else_(0) - ).as_(alias) - - # Credit availed is not required as it will be always 0 for pos + if ineligibility_reason == "Ineligible As Per Section 17(5)": + query = query.where(dt_item.is_ineligible_for_itc == 1) - ineligible_credit = ( - frappe.qb.from_(pi) - .inner_join(taxes) - .on(pi.name == taxes.parent) - .select( - pi.name.as_("voucher_no"), - pi.posting_date, - pi.ineligibility_reason.as_("itc_classification"), - get_tax_case_statement([self.gst_accounts.igst_account], "iamt"), - get_tax_case_statement([self.gst_accounts.cgst_account], "camt"), - get_tax_case_statement([self.gst_accounts.sgst_account], "camt"), - get_tax_case_statement( - [ - self.gst_accounts.cess_account, - self.gst_accounts.cess_non_advol_account, - ], - "csamt", - ), - ) - .where(taxes.account_head.isin(list(self.gst_accounts.values()))) - .where( - IfNull(pi.ineligibility_reason, "") == "ITC restricted due to PoS rules" - ) - .where(pi.name.isin(ineligible_transactions)) - .where(taxes.parenttype == "Purchase Invoice") - .groupby(pi[group_by]) - .run(as_dict=True) - ) - - return ineligible_credit + return query.groupby(dt[group_by]).run(as_dict=True) def get_for_bill_of_entry(self, group_by="name"): - ineligible_transactions = self.get_vouchers_with_gst_expense("Bill of Entry") - - if not ineligible_transactions: - return - - boe = frappe.qb.DocType("Bill of Entry") - boe_taxes = frappe.qb.DocType("Bill of Entry Taxes") - - credit_availed = ( - self.get_gl_entry_query("Bill of Entry") - .inner_join(boe) - .on(boe.name == self.gl_entry.voucher_no) - .select(*self.select_net_gst_amount_from_gl_entry()) - .select( - boe.name.as_("voucher_no"), - ConstantColumn("Ineligible As Per Section 17(5)").as_( - "itc_classification" - ), - ) - .where(boe.name.isin(ineligible_transactions)) - .groupby(boe[group_by]) - .run(as_dict=1) - ) - - credit_available = ( - frappe.qb.from_(boe) - .join(boe_taxes) - .on(boe_taxes.parent == boe.name) + doctype = "Bill of Entry" + dt = frappe.qb.DocType(doctype) + dt_item = frappe.qb.DocType(f"{doctype} Item") + query = ( + self.get_common_query(doctype, dt, dt_item) .select( - ConstantColumn("Bill of Entry").as_("voucher_type"), - boe.name.as_("voucher_no"), - boe.posting_date, - Sum( - Case() - .when( - boe_taxes.account_head == self.gst_accounts.igst_account, - boe_taxes.tax_amount, - ) - .else_(0) - ).as_("iamt"), - Sum( - Case() - .when( - boe_taxes.account_head == self.gst_accounts.cess_account, - boe_taxes.tax_amount, - ) - .else_(0) - ).as_("csamt"), - LiteralValue(0).as_("camt"), - LiteralValue(0).as_("samt"), ConstantColumn("Ineligible As Per Section 17(5)").as_( "itc_classification" - ), + ) ) - .where(boe.name.isin(ineligible_transactions)) - .groupby(boe[group_by]) - .run(as_dict=1) - ) - - return self.get_ineligible_credit(credit_availed, credit_available, group_by) - - def get_ineligible_credit(self, credit_availed, credit_available, group_by): - if group_by == "name": - group_by_field = "voucher_no" - elif group_by == "ineligibility_reason": - group_by_field = "itc_classification" - else: - group_by_field = group_by - - credit_availed_dict = frappe._dict( - {d[group_by_field]: d for d in credit_availed} - ) - ineligible_credit = [] - tax_amounts = ["camt", "samt", "iamt", "csamt"] - - for row in credit_available: - credit_availed = credit_availed_dict.get(row[group_by_field]) - if not credit_availed: - ineligible_credit.append(row) - continue - - for key in tax_amounts: - if key not in row: - continue - - row[key] -= flt(credit_availed.get(key, 0)) - - ineligible_credit.append(row) - - return ineligible_credit - - def get_vouchers_with_gst_expense(self, voucher_type): - gst_expense_account = frappe.get_cached_value( - "Company", self.company, "default_gst_expense_account" - ) - - data = ( - self.get_gl_entry_query(voucher_type) - .select(self.gl_entry.voucher_no) - .where(self.gl_entry.account == gst_expense_account) - .run(as_dict=1) + .where(dt_item.is_ineligible_for_itc == 1) ) - return set([d.voucher_no for d in data]) - - def select_net_gst_amount_from_gl_entry(self): - account_field_map = { - "cgst_account": "camt", - "sgst_account": "samt", - "igst_account": "iamt", - "cess_account": "csamt", - } - fields = [] + return query.groupby(dt[group_by]).run(as_dict=True) - for account_field, key in account_field_map.items(): - if ( - account_field not in self.gst_accounts - or not self.gst_accounts[account_field] - ): - continue - - fields.append( - ( - Sum( - Case() - .when( - self.gl_entry.account.eq(self.gst_accounts[account_field]), - self.gl_entry.debit_in_account_currency, - ) - .else_(0) - ) - - Sum( - Case() - .when( - self.gl_entry.account.eq(self.gst_accounts[account_field]), - self.gl_entry.credit_in_account_currency, - ) - .else_(0) - ) - ).as_(key) + def get_common_query(self, doctype, dt, dt_item): + return ( + frappe.qb.from_(dt) + .join(dt_item) + .on(dt.name == dt_item.parent) + .select( + ConstantColumn(doctype).as_("voucher_type"), + dt.name.as_("voucher_no"), + dt.posting_date, + Sum(dt_item.igst_amount).as_("iamt"), + Sum(dt_item.cgst_amount).as_("camt"), + Sum(dt_item.sgst_amount).as_("samt"), + Sum(dt_item.cess_amount + dt_item.cess_non_advol_amount).as_("csamt"), ) - - return fields - - def get_gl_entry_query(self, voucher_type): - query = ( - frappe.qb.from_(self.gl_entry) - .where(self.gl_entry.docstatus == 1) - .where(self.gl_entry.is_opening == "No") - .where(self.gl_entry.voucher_type == voucher_type) - .where(self.gl_entry.is_cancelled == 0) - .where(self.gl_entry.company_gstin == self.gstin) - .where(Extract(DatePart.month, self.gl_entry.posting_date).eq(self.month)) - .where(Extract(DatePart.year, self.gl_entry.posting_date).eq(self.year)) + .where(dt.docstatus == 1) + .where(dt.company_gstin == self.gstin) + .where(dt.company == self.company) + .where(Extract(DatePart.month, dt.posting_date).eq(self.month)) + .where(Extract(DatePart.year, dt.posting_date).eq(self.year)) ) - - return query diff --git a/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 670446e0b..10fac50d3 100644 --- a/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -10,7 +10,8 @@ from frappe.utils import flt, getdate import erpnext -from india_compliance.gst_india.utils import get_gst_accounts_by_type, get_gst_uom +from india_compliance.gst_india.constants import GST_TAX_TYPES +from india_compliance.gst_india.utils import get_gst_uom def execute(filters=None): @@ -20,33 +21,19 @@ def execute(filters=None): validate_filters(filters) columns = get_columns() - output_gst_accounts_dict = get_gst_accounts_by_type(filters.company, "Output") - data = get_hsn_data(filters, columns, output_gst_accounts_dict) + data = get_hsn_data(filters, columns) return columns, data -def get_hsn_data(filters, columns, output_gst_accounts_dict): - output_gst_accounts = set() +def get_hsn_data(filters, columns): non_cess_accounts = ["igst_account", "cgst_account", "sgst_account"] tax_columns = non_cess_accounts + ["cess_account"] - for account_type, account_name in output_gst_accounts_dict.items(): - if not account_name: - continue - - output_gst_accounts.add(account_name) - company_currency = erpnext.get_company_currency(filters.company) item_list = get_items(filters) - itemised_tax = get_item_taxes( - item_list, - columns, - company_currency, - output_gst_accounts, - output_gst_accounts_dict, - ) + itemised_tax = get_item_taxes(item_list, company_currency) data = [] added_item = set() @@ -235,9 +222,7 @@ def get_items(filters): return items -def get_item_taxes( - item_list, columns, company_currency, output_gst_accounts, output_gst_accounts_dict -): +def get_item_taxes(item_list, company_currency): if not item_list: return [] @@ -260,19 +245,19 @@ def get_item_taxes( frappe.qb.from_(doctype) .select( doctype.parent, - doctype.account_head, + doctype.gst_tax_type, doctype.item_wise_tax_detail, doctype.base_tax_amount_after_discount_amount, ) .where(doctype.parenttype == "Sales Invoice") .where(doctype.docstatus == 1) .where(doctype.parent.isin(invoice_numbers)) - .where(doctype.account_head.isin(output_gst_accounts)) + .where(doctype.gst_tax_type.isin(GST_TAX_TYPES)) ).run() - gst_account_map = {value: key for key, value in output_gst_accounts_dict.items()} + gst_account_map = get_gst_account_tax_type_map() - for parent, account_head, item_wise_tax_detail, tax_amount in tax_details: + for parent, gst_tax_type, item_wise_tax_detail, tax_amount in tax_details: if not item_wise_tax_detail: continue @@ -284,7 +269,7 @@ def get_item_taxes( continue item_taxes = itemised_tax.setdefault((parent, item_code), {}) - item_taxes[gst_account_map.get(account_head)] = frappe._dict( + item_taxes[gst_account_map.get(gst_tax_type)] = frappe._dict( tax_rate=flt(tax_rate, 2), tax_amount=flt(tax_amount, tax_amount_precision), ) @@ -295,6 +280,17 @@ def get_item_taxes( return itemised_tax +def get_gst_account_tax_type_map(): + gst_account_map = {} + + for tax_type in GST_TAX_TYPES: + if "cess" in tax_type: + gst_account_map[tax_type] = "cess_account" + else: + gst_account_map[tax_type] = tax_type + "_account" + return gst_account_map + + def get_merged_data(columns, data): merged_hsn_dict = {} diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index a4fa622a8..4ba25ba33 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -32,6 +32,7 @@ def after_install(): create_email_template() set_default_gst_settings() set_default_accounts_settings() + set_default_print_settings() create_hsn_codes() add_fields_to_item_variant_settings() @@ -260,6 +261,28 @@ def set_default_accounts_settings(): frappe.db.set_default("add_taxes_from_item_tax_template", 0) +def set_default_print_settings(): + sales_invoice_format = frappe.get_meta("Sales Invoice").default_print_format + + if sales_invoice_format: + return + + # print style + frappe.db.set_single_value("Print Settings", "print_style", "Modern") + + # print format + frappe.make_property_setter( + { + "doctype": "Sales Invoice", + "doctype_or_field": "DocType", + "property": "default_print_format", + "value": "GST Tax Invoice", + }, + validate_fields_for_doctype=False, + is_system_generated=False, + ) + + def show_accounts_settings_override_warning(): """ Show warning if Determine Address Tax Category From is set to something diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 86e4c2737..90a9db238 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -15,7 +15,9 @@ cint, cstr, get_datetime, + get_last_day, get_link_to_form, + get_quarter_start, get_system_timezone, getdate, ) @@ -93,9 +95,6 @@ def get_gstin_list(party, party_type="Company"): """ Returns a list the party's GSTINs. """ - if not party: - return - frappe.has_permission(party_type, doc=party, throw=True) gstin_list = frappe.get_all( @@ -542,6 +541,32 @@ def get_gst_accounts_by_tax_type(company, tax_type, throw=True): ) +def get_gst_account_gst_tax_type_map(): + """ + - Returns gst_account by tax_type for all the companies + - Eg.: {"Input Tax SGST - _TIRC": "sgst", "Input Tax CGST - _TIRC": "cgst"} + + """ + + gst_account_map = frappe._dict() + settings = frappe.get_cached_doc("GST Settings", "GST Settings") + + for row in settings.gst_accounts: + for account in GST_ACCOUNT_FIELDS: + account_value = row.get(account) + + if not account_value: + continue + + account_key = account[:-8] + if "Reverse Charge" in row.get("account_type"): + account_key = account_key + "_rcm" + + gst_account_map[account_value] = account_key + + return gst_account_map + + @frappe.whitelist() def get_all_gst_accounts(company): """ @@ -773,6 +798,26 @@ def get_timespan_date_range(timespan: str, company: str | None = None) -> tuple fiscal_year = get_fiscal_year(date, company=company) return (fiscal_year[1], fiscal_year[2]) + if timespan == "this fiscal year to last month": + date = getdate() + fiscal_year = get_fiscal_year(date, company=company) + last_month = add_to_date(date, months=-1) + + if fiscal_year[1] > last_month: + return (fiscal_year[1], fiscal_year[1]) + + return (fiscal_year[1], get_last_day(last_month)) + + if timespan == "this quarter to last month": + date = getdate() + quarter_start = get_quarter_start(date) + last_month = get_last_day(add_to_date(date, months=-1)) + + if quarter_start > last_month: + return (quarter_start, quarter_start) + + return (quarter_start, get_last_day(last_month)) + return diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 2695138c6..dc808d6c8 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -1518,7 +1518,7 @@ def set_party_address_details(self): to_party = self.transaction_details.party_name from_party = self.transaction_details.company_name - if self.doc.doctype == "Purchase Invoice": + if self.doc.doctype in ("Purchase Invoice", "Purchase Receipt"): to_party, from_party = from_party, to_party if self.doc.is_return: diff --git a/india_compliance/gst_india/utils/test_utils.py b/india_compliance/gst_india/utils/test_utils.py new file mode 100644 index 000000000..cea70bd5f --- /dev/null +++ b/india_compliance/gst_india/utils/test_utils.py @@ -0,0 +1,51 @@ +from datetime import date +from unittest.mock import patch + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import getdate + + +class TestUtils(FrappeTestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # create old fiscal years + fiscal_year = frappe.new_doc("Fiscal Year") + fiscal_year.update( + { + "year_start_date": "2023-04-01", + "year_end_date": "2024-03-31", + "year": "2023-2024", + } + ).insert(ignore_if_duplicate=True) + + fiscal_year = frappe.new_doc("Fiscal Year") + fiscal_year.update( + { + "year_start_date": "2022-04-01", + "year_end_date": "2023-03-31", + "year": "2022-2023", + } + ).insert(ignore_if_duplicate=True) + + @patch( + "india_compliance.gst_india.utils.getdate", return_value=getdate("2023-06-20") + ) + def test_timespan_date_range(self, getdate_mock): + from india_compliance.gst_india.utils import get_timespan_date_range + + timespan_date_range_map = { + "this fiscal year": (date(2023, 4, 1), date(2024, 3, 31)), + "last fiscal year": (date(2022, 4, 1), date(2023, 3, 31)), + "this fiscal year to last month": (date(2023, 4, 1), date(2023, 5, 31)), + "this quarter to last month": (date(2023, 4, 1), date(2023, 5, 31)), + } + + for timespan, expected_date_range in timespan_date_range_map.items(): + actual_date_range = get_timespan_date_range(timespan) + + for i, expected_date in enumerate(expected_date_range): + self.assertEqual(expected_date, actual_date_range[i]) diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 6c18bb99a..20372d031 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -14,7 +14,6 @@ VEHICLE_TYPES, ) from india_compliance.gst_india.utils import ( - get_gst_accounts_by_type, get_gst_uom, get_validated_country_code, validate_invoice_number, @@ -37,24 +36,17 @@ def __init__(self, doc): self.sandbox_mode = self.settings.sandbox_mode self.transaction_details = frappe._dict() - gst_type = "Output" self.party_name_field = "customer_name" + self.is_purchase_rcm = False - if self.doc.doctype == "Purchase Invoice": + if self.doc.doctype in ("Purchase Invoice", "Purchase Receipt"): self.party_name_field = "supplier_name" - if self.doc.is_reverse_charge != 1: - # for with reverse charge, gst_type is Output - # this will ensure zero taxes in transaction details - gst_type = "Input" + if self.doc.is_reverse_charge == 1: + # for with reverse charge in purchase, do not compute taxes + self.is_purchase_rcm = True self.party_name = self.doc.get(self.party_name_field) - # "CGST Account - TC": "cgst_account" - self.gst_accounts = { - v: k - for k, v in get_gst_accounts_by_type(self.doc.company, gst_type).items() - } - def set_transaction_details(self): rounding_adjustment = self.rounded(self.doc.base_rounding_adjustment) if self.doc.is_return: @@ -123,11 +115,12 @@ def update_transaction_tax_details(self): for row in self.doc.taxes: if ( not row.base_tax_amount_after_discount_amount - or row.account_head not in self.gst_accounts + or self.is_purchase_rcm + or row.gst_tax_type not in GST_TAX_TYPES ): continue - tax = self.gst_accounts[row.account_head][:-8] + tax = row.gst_tax_type self.transaction_details[f"total_{tax}_amount"] = abs( self.rounded(row.base_tax_amount_after_discount_amount) ) @@ -331,12 +324,12 @@ def update_item_tax_details(self, item_details, item): for row in self.doc.taxes: if ( not row.base_tax_amount_after_discount_amount - or row.account_head not in self.gst_accounts + or self.is_purchase_rcm + or row.gst_tax_type not in GST_TAX_TYPES ): continue - # Remove '_account' from 'cgst_account' - tax = self.gst_accounts[row.account_head][:-8] + tax = row.gst_tax_type tax_rate = self.rounded( frappe.parse_json(row.item_wise_tax_detail).get( item.item_code or item.item_name diff --git a/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json index c4dfabea0..2256439e2 100644 --- a/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json +++ b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json @@ -7,15 +7,15 @@ { "fieldname": "upi_qr_text", "fieldtype": "Text", - "label": "UPI Code", + "label": "UPI QR Code", "reqd": 0 } ], "idx": 0, - "modified": "2024-06-03 13:19:21.517387", + "modified": "2024-06-03 13:19:22.517387", "modified_by": "Administrator", "module": "GST India", - "name": "UPI QR", + "name": "UPI QR Code", "owner": "Administrator", "standard": 1, "template": "", diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index f845b2596..dfcefe375 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -103,6 +103,7 @@ "before_print": "india_compliance.gst_india.overrides.transaction.before_print", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": "india_compliance.gst_india.overrides.transaction.update_gst_details", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -123,6 +124,7 @@ }, "Payment Entry": { "onload": "india_compliance.gst_india.overrides.payment_entry.onload", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": "india_compliance.gst_india.overrides.payment_entry.validate", "on_submit": "india_compliance.gst_india.overrides.payment_entry.on_submit", "on_update_after_submit": "india_compliance.gst_india.overrides.payment_entry.on_update_after_submit", @@ -134,22 +136,25 @@ "india_compliance.gst_india.overrides.transaction.onload", ], "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": "india_compliance.gst_india.overrides.purchase_invoice.validate", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": [ "india_compliance.gst_india.overrides.transaction.update_gst_details", - "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", ], - "before_gl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", - "before_sl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", "after_mapping": "india_compliance.gst_india.overrides.transaction.after_mapping", "on_cancel": "india_compliance.gst_india.overrides.purchase_invoice.on_cancel", }, "Purchase Order": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -166,15 +171,15 @@ "india_compliance.gst_india.overrides.purchase_receipt.onload", ], "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": "india_compliance.gst_india.overrides.purchase_receipt.validate", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": [ "india_compliance.gst_india.overrides.transaction.update_gst_details", - "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", ], - "before_gl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", - "before_sl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", }, "Sales Invoice": { "onload": [ @@ -182,6 +187,9 @@ "india_compliance.gst_india.overrides.transaction.onload", ], "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type" + ], "validate": "india_compliance.gst_india.overrides.sales_invoice.validate", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": "india_compliance.gst_india.overrides.transaction.update_gst_details", @@ -197,6 +205,7 @@ "Sales Order": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -228,6 +237,7 @@ "POS Invoice": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -237,6 +247,7 @@ "Quotation": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -246,7 +257,10 @@ "Supplier Quotation": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -278,6 +292,9 @@ "erpnext.controllers.accounts_controller.get_advance_payment_entries_for_regional": ( "india_compliance.gst_india.overrides.payment_entry.get_advance_payment_entries_for_regional" ), + "erpnext.controllers.buying_controller.update_regional_item_valuation_rate": ( + "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate" + ), "erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation.adjust_allocations_for_taxes": ( "india_compliance.gst_india.overrides.payment_entry.adjust_allocations_for_taxes_in_payment_reconciliation" ), diff --git a/india_compliance/install.py b/india_compliance/install.py index 03db81b0f..ef954b442 100644 --- a/india_compliance/install.py +++ b/india_compliance/install.py @@ -21,6 +21,7 @@ "update_gst_accounts", # this is an India Compliance patch, but needs priority "update_itc_amounts", ## India Compliance + "set_gst_tax_type", "update_state_name_to_puducherry", "rename_import_of_capital_goods", "update_hsn_code", diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index aa6d79f2f..036457f13 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -3,7 +3,7 @@ execute:import frappe; frappe.delete_doc_if_exists("DocType", "GSTIN") [post_model_sync] india_compliance.patches.v14.set_default_for_overridden_accounts_setting -execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #48 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #49 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #7 execute:from india_compliance.income_tax_india.setup import create_custom_fields; create_custom_fields() #1 india_compliance.patches.post_install.remove_old_fields #1 @@ -51,4 +51,6 @@ india_compliance.patches.v14.update_default_gstr1_settings india_compliance.patches.v14.add_match_found_in_purchase_reconciliation_status india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log -india_compliance.patches.v14.enable_sales_through_ecommerce_operator \ No newline at end of file +india_compliance.patches.v14.enable_sales_through_ecommerce_operator +india_compliance.patches.post_install.set_gst_tax_type +execute:from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() \ No newline at end of file diff --git a/india_compliance/patches/check_version_compatibility.py b/india_compliance/patches/check_version_compatibility.py index b0e31b094..2067317b3 100644 --- a/india_compliance/patches/check_version_compatibility.py +++ b/india_compliance/patches/check_version_compatibility.py @@ -18,7 +18,7 @@ { "app_name": "ERPNext", "current_version": version.parse(erpnext.__version__), - "required_versions": {"version-14": "14.66.5", "version-15": "15.23.2"}, + "required_versions": {"version-14": "14.70.7", "version-15": "15.27.7"}, }, ] diff --git a/india_compliance/patches/post_install/set_gst_tax_type.py b/india_compliance/patches/post_install/set_gst_tax_type.py new file mode 100644 index 000000000..3a2580746 --- /dev/null +++ b/india_compliance/patches/post_install/set_gst_tax_type.py @@ -0,0 +1,46 @@ +import frappe +from frappe.query_builder import Case + +from india_compliance.gst_india.utils import get_gst_account_gst_tax_type_map + +TAX_DOCTYPES = [ + "Sales Taxes and Charges", + "Purchase Taxes and Charges", + "Advance Taxes and Charges", + "Bill of Entry Taxes", +] + + +def execute(): + gst_tax_type_account_map = get_gst_account_gst_tax_type_map() + + if not gst_tax_type_account_map: + return + + gst_accounts_by_tax_type = {} + for account, tax_type in gst_tax_type_account_map.items(): + gst_accounts_by_tax_type.setdefault(tax_type, []).append(account) + + for tax_doctype in TAX_DOCTYPES: + update_documents(tax_doctype, gst_accounts_by_tax_type) + + +def update_documents(taxes_doctype, gst_accounts_by_tax_type): + taxes_doctype = frappe.qb.DocType(taxes_doctype) + + update_query = frappe.qb.update(taxes_doctype).where( + taxes_doctype.parenttype.notin( + ["Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"] + ) + ) + + conditions = Case() + + for gst_tax_account, gst_tax_name in gst_accounts_by_tax_type.items(): + conditions = conditions.when( + taxes_doctype.account_head.isin(gst_tax_name), gst_tax_account + ) + + conditions = conditions.else_(None) + + update_query.set(taxes_doctype.gst_tax_type, conditions).run() diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 8dd170e7f..a2ad26cde 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -388,7 +388,8 @@ Object.assign(india_compliance, { args: { company_gstin: gstin }, }); - this.authenticate_otp(gstin); + // wait for OTP to be authenticated to proceed + await this.authenticate_otp(gstin); }, async authenticate_otp(gstin) {