diff --git a/india_compliance/gst_india/overrides/subcontracting_transaction.py b/india_compliance/gst_india/overrides/subcontracting_transaction.py index 390e4d1a9..90f29b01d 100644 --- a/india_compliance/gst_india/overrides/subcontracting_transaction.py +++ b/india_compliance/gst_india/overrides/subcontracting_transaction.py @@ -292,7 +292,7 @@ def validate_for_charge_type(self): def ignore_gst_validation_for_subcontracting(doc): if doc.doctype == "Stock Entry" and not doc.subcontracting_order: - return + return True return ignore_gst_validations(doc) diff --git a/india_compliance/gst_india/utils/itc_04/itc_04_data.py b/india_compliance/gst_india/utils/itc_04/itc_04_data.py new file mode 100644 index 000000000..d406c58a7 --- /dev/null +++ b/india_compliance/gst_india/utils/itc_04/itc_04_data.py @@ -0,0 +1,202 @@ +# Copyright (c) 2024, Resilient Tech and contributors +# For license information, please see license.txt + +from pypika import Order + +import frappe +from frappe.query_builder.functions import Date, IfNull +from frappe.utils import getdate + + +class ITC04Query: + def __init__(self, filters=None): + """Initialize the ITC04Query with optional filters.""" + self.filters = frappe._dict(filters or {}) + + self.ref_doc = frappe.qb.DocType("Dynamic Link") + self.se = frappe.qb.DocType("Stock Entry") + self.se_item = frappe.qb.DocType("Stock Entry Detail") + self.sr = frappe.qb.DocType("Subcontracting Receipt") + self.sr_item = frappe.qb.DocType("Subcontracting Receipt Item") + + def get_base_query_table_4(self, doc, doc_item): + """Construct the base query for Table-4.""" + query = ( + frappe.qb.from_(doc) + .inner_join(doc_item) + .on(doc.name == doc_item.parent) + .select( + IfNull(doc_item.item_code, doc_item.item_name).as_("item_code"), + doc_item.qty, + doc_item.gst_hsn_code, + doc.supplier, + doc.name.as_("invoice_no"), + doc.posting_date, + doc.is_return, + IfNull(doc.place_of_supply, "").as_("place_of_supply"), + doc.base_grand_total.as_("invoice_total"), + doc.gst_category, + IfNull(doc_item.gst_treatment, "Not Defined").as_("gst_treatment"), + (doc_item.cgst_rate + doc_item.sgst_rate + doc_item.igst_rate).as_( + "gst_rate" + ), + doc_item.taxable_value, + doc_item.cgst_amount, + doc_item.sgst_amount, + doc_item.igst_amount, + doc_item.cess_amount, + doc_item.cess_non_advol_amount, + (doc_item.cess_amount + doc_item.cess_non_advol_amount).as_( + "total_cess_amount" + ), + ( + doc_item.cgst_amount + + doc_item.sgst_amount + + doc_item.igst_amount + + doc_item.cess_amount + + doc_item.cess_non_advol_amount + ).as_("total_tax"), + ( + doc_item.taxable_value + + doc_item.cgst_amount + + doc_item.sgst_amount + + doc_item.igst_amount + + doc_item.cess_amount + + doc_item.cess_non_advol_amount + ).as_("total_amount"), + ) + .where(doc.docstatus == 1) + .orderby( + doc.posting_date, + doc.name, + doc_item.item_code, + order=Order.desc, + ) + ) + query = self.get_query_with_common_filters(query, doc) + + return query + + def get_query_table_4_se(self): + """Construct the query for Table-4 Stock Entry.""" + query = ( + self.get_base_query_table_4(self.se, self.se_item) + .select( + self.se_item.uom, + self.se.bill_to_gstin.as_("supplier_gstin"), + self.se.bill_from_gstin.as_("company_gstin"), + self.se.bill_to_gst_category.as_("gst_category"), + ) + .where(IfNull(self.se.bill_to_gstin, "") != self.se.bill_from_gstin) + .where(self.se.subcontracting != "") + .where(self.se.purpose == "Send to Subcontractor") + ) + + if self.filters.company_gstin: + query = query.where(self.se.bill_from_gstin == self.filters.company_gstin) + + return query + + def get_query_table_4_sr(self): + """Construct the query for Table-4 Subcontracting Receipt.""" + query = ( + self.get_base_query_table_4(self.sr, self.sr_item) + .select( + self.sr_item.stock_uom.as_("uom"), + self.sr.company_gstin, + self.sr.supplier_gstin, + ) + .where(IfNull(self.sr.supplier_gstin, "") != self.sr.company_gstin) + .where(self.sr.is_return == 1) + ) + + if self.filters.company_gstin: + query = query.where(self.sr.company_gstin == self.filters.company_gstin) + + return query + + def get_base_query_table_5A(self, doc, doc_item, ref_doc): + """Construct the base query for Table-5A.""" + query = ( + frappe.qb.from_(doc) + .inner_join(doc_item) + .on(doc.name == doc_item.parent) + .inner_join(ref_doc) + .on(ref_doc.parent == doc.name) + .select( + IfNull(doc_item.item_code, doc_item.item_name).as_("item_code"), + doc_item.qty, + doc_item.gst_hsn_code, + IfNull(doc.supplier, "").as_("supplier"), + IfNull(doc.name, "").as_("invoice_no"), + doc.posting_date, + doc.is_return, + IfNull(doc.place_of_supply, "").as_("place_of_supply"), + doc.base_grand_total.as_("invoice_total"), + IfNull(doc.gst_category, "").as_("gst_category"), + IfNull(doc_item.gst_treatment, "Not Defined").as_("gst_treatment"), + IfNull(ref_doc.link_name, "").as_("original_challan_no"), + ) + .where(doc.docstatus == 1) + .orderby( + doc.posting_date, + doc.name, + doc_item.item_code, + order=Order.desc, + ) + ) + + query = self.get_query_with_common_filters(query, doc) + + return query + + def get_query_table_5A_se(self): + """Construct the query for Table-5A Stock Entry.""" + query = ( + self.get_base_query_table_5A(self.se, self.se_item, self.ref_doc) + .select( + self.se_item.uom, + self.se.bill_to_gstin.as_("supplier_gstin"), + self.se.bill_from_gstin.as_("company_gstin"), + ) + .where(IfNull(self.se.bill_to_gstin, "") != self.se.bill_from_gstin) + .where(self.se.subcontracting != "") + .where(self.se.purpose == "Material Transfer") + ) + + if self.filters.company_gstin: + query = query.where(self.se.bill_from_gstin == self.filters.company_gstin) + + return query + + def get_query_table_5A_sr(self): + """Construct the query for Table-5A Subcontracting Receipt.""" + query = ( + self.get_base_query_table_5A(self.sr, self.sr_item, self.ref_doc) + .select( + self.sr_item.stock_uom.as_("uom"), + self.sr.company_gstin, + self.sr.supplier_gstin, + ) + .where(IfNull(self.sr.supplier_gstin, "") != self.sr.company_gstin) + ) + + if self.filters.company_gstin: + query = query.where(self.sr.company_gstin == self.filters.company_gstin) + + return query + + def get_query_with_common_filters(self, query, doc): + """Apply common filters to the query.""" + if self.filters.company: + query = query.where(doc.company == self.filters.company) + + if self.filters.from_date: + query = query.where( + Date(doc.posting_date) >= getdate(self.filters.from_date) + ) + + if self.filters.to_date: + query = query.where(Date(doc.posting_date) <= getdate(self.filters.to_date)) + + return query