diff --git a/india_compliance/gst_india/data/test_e_waybill.json b/india_compliance/gst_india/data/test_e_waybill.json index 848395e5a..82ddb123b 100644 --- a/india_compliance/gst_india/data/test_e_waybill.json +++ b/india_compliance/gst_india/data/test_e_waybill.json @@ -1090,9 +1090,11 @@ "gst_hsn_code": "61149090", "s_warehouse": "Finished Goods - _TIRC", "t_warehouse": "Goods In Transit - _TIRC", - "amount": 100 + "amount": 100, + "taxable_value": 100 }], - "company": "_Test Indian Registered Company" + "company": "_Test Indian Registered Company", + "base_grand_total": 100 }, "values": { "distance": 0.0, diff --git a/india_compliance/gst_india/overrides/subcontracting_transaction.py b/india_compliance/gst_india/overrides/subcontracting_transaction.py index 51f5bf272..969078bf6 100644 --- a/india_compliance/gst_india/overrides/subcontracting_transaction.py +++ b/india_compliance/gst_india/overrides/subcontracting_transaction.py @@ -289,7 +289,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/report/gst_job_work_stock_movement/__init__.py b/india_compliance/gst_india/report/gst_job_work_stock_movement/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.js b/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.js new file mode 100644 index 000000000..85eea61ee --- /dev/null +++ b/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.js @@ -0,0 +1,60 @@ +// Copyright (c) 2024, Resilient Tech and contributors +// For license information, please see license.txt + +frappe.query_reports["GST Job Work Stock Movement"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + on_change: report => { + report.set_filter_value({ + company_gstin: "", + }); + report.refresh(); + }, + get_query: function () { + return { + filters: { + country: "India", + }, + }; + }, + }, + { + fieldname: "company_gstin", + label: __("Company GSTIN"), + fieldtype: "Autocomplete", + get_query() { + const company = frappe.query_report.get_filter_value("company"); + return india_compliance.get_gstin_query(company); + }, + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: india_compliance.last_half_year("start"), + reqd: 1, + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + default: india_compliance.last_half_year("end"), + reqd: 1, + }, + { + fieldname: "category", + label: __("Invoice Category"), + fieldtype: "Select", + options: [ + "Sent for Job Work (Table 4)", + "Received back from Job Worker (Table 5A)", + ], + reqd: 1, + }, + ], +}; diff --git a/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.json b/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.json new file mode 100644 index 000000000..004663280 --- /dev/null +++ b/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.json @@ -0,0 +1,44 @@ +{ + "add_total_row": 1, + "columns": [], + "creation": "2024-08-19 14:40:26.929838", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letterhead": null, + "modified": "2024-08-19 14:40:33.093455", + "modified_by": "Administrator", + "module": "GST India", + "name": "GST Job Work Stock Movement", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Subcontracting Order", + "report_name": "GST Job Work Stock Movement", + "report_type": "Script Report", + "roles": [ + { + "role": "Item Manager" + }, + { + "role": "Stock Manager" + }, + { + "role": "Stock User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Manufacturing User" + } + ] +} \ No newline at end of file diff --git a/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.py b/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.py new file mode 100644 index 000000000..b7c47bd51 --- /dev/null +++ b/india_compliance/gst_india/report/gst_job_work_stock_movement/gst_job_work_stock_movement.py @@ -0,0 +1,233 @@ +# Copyright (c) 2024, Resilient Tech and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ + +from india_compliance.gst_india.utils.itc_04.itc_04_data import ITC04Query + + +def execute(filters=None): + report = JobWorkMovement(filters) + return report.columns(), report.data() + + +class JobWorkMovement: + def __init__(self, filters=None): + self.filters = filters + self.validate_filters() + + def validate_filters(self): + if not self.filters: + self.filters = frappe._dict() + + if not self.filters.category: + frappe.throw(_("Category is mandatory")) + + if not self.filters.from_date: + frappe.throw(_("From Date is mandatory")) + + if not self.filters.to_date: + frappe.throw(_("To Date is mandatory")) + + if self.filters.from_date > self.filters.to_date: + frappe.throw(_("From Date cannot be greater than To Date")) + + def data(self): + itc04 = ITC04Query(self.filters) + data = [] + + if self.filters.category == "Sent for Job Work (Table 4)": + data = itc04.get_query_table_4_se().run(as_dict=True) + data.extend(itc04.get_query_table_4_sr().run(as_dict=True)) + + elif self.filters.category == "Received back from Job Worker (Table 5A)": + data = itc04.get_query_table_5A_se().run(as_dict=True) + data.extend(itc04.get_query_table_5A_sr().run(as_dict=True)) + + return data + + def columns(self): + if self.filters.category == "Sent for Job Work (Table 4)": + return self.get_columns_table_4() + + elif self.filters.category == "Received back from Job Worker (Table 5A)": + return self.get_columns_table_5A() + + return [] + + def get_columns_table_4(self): + return [ + *self.gstin_column(), + *self.posting_date_column(), + { + "fieldname": "invoice_no", + "label": _("Invoice No (Challan No)"), + "fieldtype": "Dynamic Link", + "options": "invoice_type", + "width": 180, + }, + *self.common_columns(), + { + "fieldname": "taxable_value", + "label": _("Taxable Value"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "gst_rate", + "label": _("GST Rate"), + "fieldtype": "Percent", + "width": 100, + }, + { + "fieldname": "cgst_amount", + "label": _("CGST Amount"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "sgst_amount", + "label": _("SGST Amount"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "igst_amount", + "label": _("IGST Amount"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_cess_amount", + "label": _("Cess Amount"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_tax", + "label": _("Total Tax"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_amount", + "label": _("Total Amount"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "gst_treatment", + "label": _("GST Treatment"), + "fieldtype": "Data", + "width": 120, + }, + ] + + def get_columns_table_5A(self): + return [ + *self.gstin_column(), + *self.posting_date_column(), + { + "fieldname": "original_challan_no", + "label": _("Original Challan No"), + "fieldtype": "Dynamic Link", + "options": "original_challan_invoice_type", + "width": 180, + }, + { + "fieldname": "invoice_no", + "label": _("Job Worker Invoice No (Challan No)"), + "fieldtype": "Dynamic Link", + "options": "invoice_type", + "width": 180, + }, + *self.common_columns(), + ] + + def gstin_column(self): + return [ + { + "fieldname": "company_gstin", + "label": _("Company GSTIN"), + "fieldtype": "Data", + "width": 160, + "hidden": True if self.filters.company_gstin else False, + } + ] + + def posting_date_column(self): + return [ + { + "fieldname": "posting_date", + "label": _("Posting Date"), + "fieldtype": "Date", + "width": 120, + } + ] + + def common_columns(self): + return [ + { + "fieldname": "supplier", + "label": _("Job Worker"), + "fieldtype": "Link", + "options": "Supplier", + "width": 200, + }, + { + "fieldname": "supplier_gstin", + "label": _("Job Worker GSTIN"), + "fieldtype": "Data", + "width": 160, + }, + { + "fieldname": "place_of_supply", + "label": _("Place of Supply"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "is_return", + "label": _("Is Return"), + "fieldtype": "Check", + "width": 90, + }, + { + "fieldname": "item_type", + "label": _("Item Type (Input/Capital Goods)"), + "fieldtype": "Data", + "width": 180, + "hidden": ( + False + if self.filters.category == "Sent for Job Work (Table 4)" + else True + ), + }, + { + "fieldname": "item_code", + "label": _("Item Code"), + "fieldtype": "Link", + "options": "Item", + "width": 180, + }, + { + "fieldname": "gst_hsn_code", + "label": _("HSN Code"), + "fieldtype": "Link", + "options": "GST HSN Code", + "width": 100, + }, + { + "fieldname": "qty", + "label": _("Qty"), + "fieldtype": "Float", + "width": 90, + }, + { + "fieldname": "uom", + "label": _("UOM"), + "fieldtype": "Data", + "width": 90, + }, + ] 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..a4beb6433 --- /dev/null +++ b/india_compliance/gst_india/utils/itc_04/itc_04_data.py @@ -0,0 +1,227 @@ +# Copyright (c) 2024, Resilient Tech and contributors +# For license information, please see license.txt + +from pypika import Order +from pypika.terms import ValueWrapper + +import frappe +from frappe.query_builder import Case +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") + self.se_doctype = ValueWrapper("Stock Entry") + self.sr_doctype = ValueWrapper("Subcontracting Receipt") + + def get_base_query_table_4(self, doc, doc_item): + """Construct the base query for Table-4.""" + item = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(doc) + .inner_join(doc_item) + .on(doc.name == doc_item.parent) + .left_join(item) + .on(doc_item.item_code == item.name) + .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"), + Case() + .when(item.is_fixed_asset == 1, "Capital Goods") + .else_("Inputs") + .as_("item_type"), + ) + .where(doc.docstatus == 1) + .orderby(doc.name, 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. + - Table-4 is for goods sent to job worker. + - This query is for Stock Entry with purpose "Send to Subcontractor". + """ + + 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"), + self.se_doctype.as_("invoice_type"), + ) + .where(IfNull(self.se.bill_to_gstin, "") != self.se.bill_from_gstin) + .where(self.se.subcontracting_order != "") + .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. + - Table-4 is for goods sent to job worker. + - This query is for Subcontracting Receipt Returns. + """ + 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, + self.sr_doctype.as_("invoice_type"), + ) + .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"), + ref_doc.link_doctype.as_("original_challan_invoice_type"), + IfNull(ref_doc.link_name, "").as_("original_challan_no"), + ) + .where(doc.docstatus == 1) + .orderby(doc.name, 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. + - Table-5A is for goods received from job worker. + - This query is for Stock Entry Returns. + """ + 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"), + self.se_doctype.as_("invoice_type"), + ) + .where(IfNull(self.se.bill_to_gstin, "") != self.se.bill_from_gstin) + .where(self.se.subcontracting_order != "") + .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. + - Table-5A is for goods received from job worker. + - This query is for 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, + self.sr_doctype.as_("invoice_type"), + ) + .where(IfNull(self.sr.supplier_gstin, "") != self.sr.company_gstin) + .where(self.sr.is_return == 0) + ) + + 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 diff --git a/india_compliance/gst_india/utils/tests.py b/india_compliance/gst_india/utils/tests.py index 6f563a481..d9f1c31f8 100644 --- a/india_compliance/gst_india/utils/tests.py +++ b/india_compliance/gst_india/utils/tests.py @@ -113,6 +113,7 @@ def append_item(transaction, data=None, company_abbr="_TIRC"): "gst_hsn_code": data.gst_hsn_code, "warehouse": f"Stores - {company_abbr}", "expense_account": f"Cost of Goods Sold - {company_abbr}", + "taxable_value": data.taxable_value or 0, }, ) diff --git a/india_compliance/gst_india/workspace/gst_india/gst_india.json b/india_compliance/gst_india/workspace/gst_india/gst_india.json index 12e554b44..6c2fde67a 100644 --- a/india_compliance/gst_india/workspace/gst_india/gst_india.json +++ b/india_compliance/gst_india/workspace/gst_india/gst_india.json @@ -12,25 +12,6 @@ "is_hidden": 0, "label": "GST India", "links": [ - { - "hidden": 0, - "is_query_report": 0, - "label": "New Reports", - "link_count": 1, - "link_type": "DocType", - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 1, - "label": "GST Sales Register Beta", - "link_count": 0, - "link_to": "GST Sales Register Beta", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -48,6 +29,7 @@ "link_to": "GSTR-1", "link_type": "Report", "onboard": 0, + "report_ref_doctype": "GL Entry", "type": "Link" }, { @@ -98,6 +80,7 @@ "link_to": "Bill of Entry Summary", "link_type": "Report", "onboard": 0, + "report_ref_doctype": "Bill of Entry", "type": "Link" }, { @@ -186,6 +169,7 @@ "link_to": "GSTR-3B Details", "link_type": "Report", "onboard": 0, + "report_ref_doctype": "Purchase Invoice", "type": "Link" }, { @@ -196,6 +180,7 @@ "link_to": "GST Balance", "link_type": "Report", "onboard": 0, + "report_ref_doctype": "GL Entry", "type": "Link" }, { @@ -216,6 +201,7 @@ "link_to": "Audit Trail", "link_type": "Report", "onboard": 0, + "report_ref_doctype": "Version", "type": "Link" }, { @@ -226,10 +212,41 @@ "link_to": "GST Advance Detail", "link_type": "Report", "onboard": 0, + "report_ref_doctype": "Payment Entry", + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "New Reports", + "link_count": 2, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "GST Sales Register Beta", + "link_count": 0, + "link_to": "GST Sales Register Beta", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "GST Job Work Stock Movement", + "link_count": 0, + "link_to": "GST Job Work Stock Movement", + "link_type": "Report", + "onboard": 0, + "report_ref_doctype": "Subcontracting Order", "type": "Link" } ], - "modified": "2024-07-14 18:01:43.058590", + "modified": "2024-08-20 10:28:10.623892", "modified_by": "Administrator", "module": "GST India", "name": "GST India", diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index a7ebf09af..9a8fb0824 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -345,6 +345,28 @@ Object.assign(india_compliance, { return frappe.datetime.add_days(frappe.datetime.month_start(), -1); }, + last_half_year(position) { + const today = frappe.datetime.now_date(true); + const current_month = today.getMonth() + 1; + const current_year = today.getFullYear(); + + if (current_month <= 3) { + return position === "start" + ? `${current_year - 1}-03-01` + : `${current_year - 1}-09-30`; + + } else if (current_month <= 9) { + return position === "start" + ? `${current_year - 1}-10-01` + : `${current_year}-03-31`; + + } else { + return position === "start" + ? `${current_year}-04-01` + : `${current_year}-09-30`; + } + }, + primary_to_danger_btn(parent) { parent.$wrapper .find(".btn-primary")