From 53eab10f7b3dbb0addc12784b7f7a51e315a65c5 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Sat, 5 Aug 2023 13:25:56 +0530 Subject: [PATCH 1/9] fix: update e-Waybill status only in sales invoice (cherry picked from commit 099c2ddc1f88361c128038676cb49c2033a802bd) --- india_compliance/gst_india/utils/e_waybill.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 14ddaed257..7c8408745a 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -127,10 +127,11 @@ def log_and_process_e_waybill_generation(doc, result, *, with_irn=False): """Separate function, since called in backend from e-invoice utils""" e_waybill_number = str(result["ewayBillNo" if not with_irn else "EwbNo"]) - data = { - "ewaybill": e_waybill_number, - "e_waybill_status": "Generated", - } + data = {"ewaybill": e_waybill_number} + + if doc.doctype == "Sales Invoice": + data["e_waybill_status"] = "Generated" + if distance := result.get("distance"): data["distance"] = distance @@ -201,12 +202,12 @@ def _cancel_e_waybill(doc, values): }, ) - doc.db_set( - { - "ewaybill": "", - "e_waybill_status": "Cancelled", - } - ) + data = {"ewaybill": ""} + + if doc.doctype == "Sales Invoice": + data["e_waybill_status"] = "Cancelled" + + doc.db_set(data) frappe.msgprint( _("e-Waybill cancelled successfully"), From db41218b779ea87170b41b1f5ae16c1a21a37ce3 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Sat, 5 Aug 2023 17:26:39 +0530 Subject: [PATCH 2/9] feat: provision to extend validity of e-Waybill (#807) Co-authored-by: Smit Vora (cherry picked from commit 7239eb204f76751863f7149c4e6be570b69a5b41) # Conflicts: # pyproject.toml --- .../client_scripts/e_waybill_actions.js | 214 +++++- .../gst_india/constants/e_waybill.py | 13 +- .../gst_india/data/test_e_waybill.json | 668 ++++++++++-------- india_compliance/gst_india/utils/e_waybill.py | 149 ++++ .../gst_india/utils/test_e_waybill.py | 69 +- pyproject.toml | 6 +- 6 files changed, 801 insertions(+), 318 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/e_waybill_actions.js b/india_compliance/gst_india/client_scripts/e_waybill_actions.js index 7c81d24f61..dba33b9185 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_actions.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_actions.js @@ -90,6 +90,17 @@ function setup_e_waybill_actions(doctype) { ); } + if ( + frappe.perm.has_perm(frm.doctype, 0, "submit", frm.doc.name) && + can_extend_e_waybill(frm) + ) { + frm.add_custom_button( + __("Extend Validity"), + () => show_extend_validity_dialog(frm), + "e-Waybill" + ); + } + if ( frappe.perm.has_perm(frm.doctype, 0, "cancel", frm.doc.name) && is_e_waybill_cancellable(frm) @@ -660,6 +671,178 @@ function show_update_transporter_dialog(frm) { d.show(); } +async function show_extend_validity_dialog(frm) { + const shipping_address = await frappe.db.get_doc( + "Address", + frm.doc.shipping_address_name || frm.doc.customer_address + ); + + const is_in_movement = "eval: doc.consignment_status === 'In Movement'"; + const is_in_transit = "eval: doc.consignment_status === 'In Transit'"; + + const d = new frappe.ui.Dialog({ + title: __("Extend Validity"), + fields: [ + { + label: "e-Waybill", + fieldname: "ewaybill", + fieldtype: "Data", + read_only: 1, + default: frm.doc.ewaybill, + }, + { + label: "Vehicle No", + fieldname: "vehicle_no", + fieldtype: "Data", + default: frm.doc.vehicle_no, + mandatory_depends_on: "eval: doc.mode_of_transport === 'Road'", + }, + { + label: "Remaining Distance (in km)", + fieldname: "remaining_distance", + fieldtype: "Float", + default: frm.doc.distance, + }, + { + fieldtype: "Column Break", + }, + { + label: "Consignment Status", + fieldname: "consignment_status", + fieldtype: "Select", + options: `In Movement\nIn Transit`, + default: "In Movement", + reqd: 1, + onchange: () => update_transit_type(d), + }, + { + label: "Mode Of Transport", + fieldname: "mode_of_transport", + fieldtype: "Select", + options: `\nRoad\nAir\nRail\nShip`, + default: frm.doc.mode_of_transport, + depends_on: is_in_movement, + mandatory_depends_on: is_in_movement, + onchange: () => update_transit_type(d), + }, + { + label: "Transit Type", + fieldname: "transit_type", + fieldtype: "Select", + options: `\nRoad\nWarehouse\nOthers`, + depends_on: is_in_transit, + mandatory_depends_on: is_in_transit, + }, + { + label: "Transport Receipt No", + fieldname: "lr_no", + fieldtype: "Data", + default: frm.doc.lr_no, + depends_on: is_in_movement, + mandatory_depends_on: + "eval: ['Rail', 'Air', 'Ship'].includes(doc.mode_of_transport) && doc.consignment_status === 'In Movement'", + }, + { + fieldtype: "Section Break", + }, + { + label: "Address Line1", + fieldname: "address_line1", + fieldtype: "Data", + default: shipping_address.address_line1, + depends_on: is_in_transit, + mandatory_depends_on: is_in_transit, + }, + { + label: "Address Line2", + fieldname: "address_line2", + fieldtype: "Data", + default: shipping_address.address_line2, + depends_on: is_in_transit, + mandatory_depends_on: is_in_transit, + }, + { + label: "Address Line3", + fieldname: "address_line3", + fieldtype: "Data", + default: shipping_address.city, + depends_on: is_in_transit, + mandatory_depends_on: is_in_transit, + }, + { + fieldtype: "Column Break", + }, + { + label: "Current Place", + fieldname: "current_place", + fieldtype: "Data", + reqd: 1, + default: shipping_address.city, + }, + { + label: "Current Pincode", + fieldname: "current_pincode", + fieldtype: "Data", + reqd: 1, + default: shipping_address.pincode, + }, + { + label: "Current State", + fieldname: "current_state", + fieldtype: "Autocomplete", + options: frappe.boot.india_state_options.join("\n"), + reqd: 1, + default: shipping_address.state, + }, + { + fieldtype: "Section Break", + }, + { + fieldname: "reason", + label: "Reason", + fieldtype: "Select", + options: [ + "Natural Calamity", + "Law and Order Situation", + "Transshipment", + "Accident", + "Others", + ], + reqd: 1, + }, + { + label: "Update e-Waybill Print/Data", + fieldname: "update_e_waybill_data", + fieldtype: "Check", + default: gst_settings.fetch_e_waybill_data, + }, + { + fieldtype: "Column Break", + }, + { + fieldname: "remark", + label: "Remark", + fieldtype: "Data", + mandatory_depends_on: 'eval: doc.reason === "Others"', + }, + ], + primary_action_label: __("Extend"), + primary_action(values) { + frappe.call({ + method: "india_compliance.gst_india.utils.e_waybill.extend_validity", + args: { + doctype: frm.doctype, + docname: frm.doc.name, + values, + }, + callback: () => frm.refresh(), + }); + d.hide(); + }, + }); + d.show(); +} + function is_e_waybill_valid(frm) { const e_waybill_info = frm.doc.__onload && frm.doc.__onload.e_waybill_info; return ( @@ -695,6 +878,24 @@ function is_e_waybill_applicable(frm) { } } +function can_extend_e_waybill(frm) { + function get_hours(date, hours) { + return moment(date).add(hours, "hours").format(frappe.defaultDatetimeFormat); + } + + const valid_upto = frm.doc.__onload?.e_waybill_info?.valid_upto; + const extend_after = get_hours(valid_upto, -8); + const extend_before = get_hours(valid_upto, 8); + + if ( + extend_after < frappe.datetime.now_datetime() < extend_before && + frm.doc.gst_transporter_id != frm.doc.company_gstin + ) + return true; + + return false; +} + function is_e_waybill_cancellable(frm) { const e_waybill_info = frm.doc.__onload && frm.doc.__onload.e_waybill_info; return ( @@ -765,6 +966,18 @@ function get_vehicle_type(doc) { return ""; } +function update_transit_type(dialog) { + dialog.set_value("transit_type", get_transit_type(dialog.get_values(true))); +} + +function get_transit_type(dialog) { + if (dialog.consignment_status === "In Movement") return ""; + if (dialog.consignment_status === "In Transit") { + if (dialog.mode_of_transport === "Road") return "Road"; + else return "Others"; + } +} + /******** * Utils *******/ @@ -808,4 +1021,3 @@ function get_e_waybill_file_name(docname) { function set_primary_action_label(dialog, primary_action_label) { dialog.get_primary_btn().removeClass("hide").html(primary_action_label); } - diff --git a/india_compliance/gst_india/constants/e_waybill.py b/india_compliance/gst_india/constants/e_waybill.py index f4c8ae3873..004a3e6778 100644 --- a/india_compliance/gst_india/constants/e_waybill.py +++ b/india_compliance/gst_india/constants/e_waybill.py @@ -26,6 +26,14 @@ "Others": "3", } +EXTEND_VALIDITY_REASON_CODES = { + "Natural Calamity": 1, + "Law and Order Situation": 2, + "Transshipment": 4, + "Accident": 5, + "Others": 99, +} + SUPPLY_TYPES = { "I": "Inward", "O": "Outward", @@ -47,7 +55,7 @@ } -TRANSPORT_MODES = {"Road": 1, "Rail": 2, "Air": 3, "Ship": 4} +TRANSPORT_MODES = {"Road": 1, "Rail": 2, "Air": 3, "Ship": 4, "In Transit": 5} TRANSPORT_TYPES = { 1: "Regular", 2: "Bill To - Ship To", @@ -56,4 +64,7 @@ } VEHICLE_TYPES = {"Regular": "R", "Over Dimensional Cargo (ODC)": "O"} +TRANSIT_TYPES = {"Road": "R", "Warehouse": "W", "Others": "O"} +CONSIGNMENT_STATUS = {"In Movement": "M", "In Transit": "T"} + ITEM_LIMIT = 250 diff --git a/india_compliance/gst_india/data/test_e_waybill.json b/india_compliance/gst_india/data/test_e_waybill.json index 9210dedcba..6068181be8 100644 --- a/india_compliance/gst_india/data/test_e_waybill.json +++ b/india_compliance/gst_india/data/test_e_waybill.json @@ -1,314 +1,356 @@ { - "goods_item_with_ewaybill": { - "kwargs": { - "vehicle_no": "GJ07DL9009" - }, - "request_data": { - "OthValue": 0.0, - "TotNonAdvolVal": 0, - "actFromStateCode": 24, - "actToStateCode": 24, - "cessValue": 0, - "cgstValue": 0, - "docDate": "05/10/2022", - "docNo": "test_invoice_no", - "docType": "INV", - "fromAddr1": "Test Address - 1", - "fromGstin": "05AAACG2115R1ZN", - "fromPincode": 380015, - "fromPlace": "Test City", - "fromStateCode": 24, - "fromTrdName": "_Test Indian Registered Company", - "igstValue": 0, - "itemList": [ - { - "cessNonAdvol": 0, - "cessRate": 0, - "cgstRate": 0, - "hsnCode": "61149090", - "igstRate": 0, - "itemNo": 1, - "productDesc": "Test Trading Goods 1", - "qtyUnit": "NOS", - "quantity": 1.0, - "sgstRate": 0, - "taxableAmount": 100.0 - } - ], - "mainHsnCode": "61149090", - "sgstValue": 0, - "subSupplyType": 1, - "supplyType": "O", - "toAddr1": "Test Address - 3", - "toGstin": "05AAACG2140A1ZL", - "toPincode": 380015, - "toPlace": "Test City", - "toStateCode": 24, - "toTrdName": "_Test Registered Customer", - "totInvValue": 100.0, - "totalValue": 100.0, - "transDistance": 10, - "transMode": 1, - "transactionType": 1, - "transporterName": "Test Common Supplier", - "userGstin": "05AAACG2115R1ZN", - "vehicleNo": "GJ07DL9009", - "vehicleType": "R" - }, - "params": "action=GENEWAYBILL", - "response_data": { - "message": "E-Way Bill is generated successfully", - "result": { - "alert": "", - "ewayBillDate": "05/10/2022 12:39:00 PM", - "ewayBillNo": 301002999920, - "validUpto": "06/10/2022 11:59:00 PM" - }, - "success": true - } - }, - "update_transporter": { - "params": "action=UPDATETRANSPORTER", - "values": { - "transporter": "_Test Common Supplier", - "gst_transporter_id": "05AAACG2140A1ZL" - }, - "request_data": { - "ewbNo": "301002999920", - "transporterId": "05AAACG2140A1ZL" - }, - "response_data": { - "message": "Transporter details are updated successfully", - "result": { - "ewayBillNo": "301002999920", - "transUpdateDate": "05/10/2022 12:45:00 PM", - "transporterId": "05AAACG2140A1ZL" - }, - "success": true - } - }, - "update_vehicle_info": { - "params": "action=VEHEWB", - "values": { - "vehicle_no": "GJ07DL9001", - "mode_of_transport": "Road", - "gst_vehicle_type": "Regular", - "reason": "Others", - "remark": "Vehicle Info added", - "update_e_waybill_data": 1 - }, - "request_data": { - "ewbNo": "301002999920", - "fromPlace": "Test City", - "fromState": 24, - "reasonCode": "3", - "reasonRem": "Vehicle Info added", - "transDocDate": "", - "transDocNo": null, - "transMode": 1, - "vehicleNo": "GJ07DL9001", - "vehicleType": "R" - }, - "response_data": { - "message": "E-Way Bill is updated successfully", - "result": { - "validUpto": "06/10/2022 11:59:00 PM", - "vehUpdDate": "05/10/2022 12:44:00 PM" - }, - "success": true - } - }, - "credit_note": { - "request_data": { - "userGstin": "05AAACG2115R1ZN", - "supplyType": "I", - "subSupplyType": 7, - "docType": "CHL", - "docNo": "test_invoice_no", - "docDate": "22/05/2023", - "transactionType": 1, - "fromTrdName": "_Test Registered Customer", - "fromGstin": "05AAACG2140A1ZL", - "fromAddr1": "Test Address - 3", - "fromPlace": "Test City", - "fromPincode": 380015, - "fromStateCode": 24, - "actFromStateCode": 24, - "toTrdName": "_Test Indian Registered Company", - "toGstin": "05AAACG2115R1ZN", - "toAddr1": "Test Address - 1", - "toPlace": "Test City", - "toPincode": 380015, - "toStateCode": 24, - "actToStateCode": 24, - "totalValue": 15.2, - "cgstValue": 0.91, - "sgstValue": 0.91, - "igstValue": 0, - "cessValue": 0, - "TotNonAdvolVal": 0, - "OthValue": -0.02, - "totInvValue": 17.0, - "transMode": 1, - "transDistance": 1, - "vehicleNo": "GJ05DL9009", - "vehicleType": "R", - "itemList": [ - { - "itemNo": 1, - "productDesc": "Test Trading Goods 1", - "hsnCode": "61149090", - "qtyUnit": "NOS", - "quantity": 1.0, - "taxableAmount": 7.6, - "sgstRate": 6.0, - "cgstRate": 6.0, - "igstRate": 0, - "cessRate": 0, - "cessNonAdvol": 0 - }, - { - "itemNo": 2, - "productDesc": "Test Trading Goods 1", - "hsnCode": "61149090", - "qtyUnit": "NOS", - "quantity": 1.0, - "taxableAmount": 7.6, - "sgstRate": 6.0, - "cgstRate": 6.0, - "igstRate": 0, - "cessRate": 0, - "cessNonAdvol": 0 - } - ], - "mainHsnCode": "61149090" - }, - "response_data": { - "message": "E-Way Bill is generated successfully", - "result": { - "alert": "", - "ewayBillDate": "22/05/2023 06:22:00 PM", - "ewayBillNo": 341003154687, - "validUpto": "23/05/2023 11:59:00 PM" - }, - "success": true - } - }, - "cancel_e_waybill": { - "request_data": { - "ewbNo": "301002999920", - "cancelRsnCode": "3", - "cancelRmrk": "For Test" - }, - "params": "action=CANEWB", - "values": { - "reason": "Data Entry Mistake", - "remark": "For Test" - }, - "response_data": { - "message": "E-Way Bill is cancelled successfully", - "result": { - "cancelDate": "04/10/2022 04:14:00 PM", - "ewayBillNo": "301002999920" - }, - "success": true - } - }, - "get_e_waybill": { - "request_data": "ewbNo=301002999920", - "response_data": { - "message": "E-Way Bill is fetched successfully", - "result": { - "VehiclListDetails": [ - { - "enteredDate": "05/10/2022 12:44:00 PM", - "fromPlace": "Test City", - "fromState": 24, - "groupNo": "0", - "transDocDate": null, - "transDocNo": "", - "transMode": "1", - "tripshtNo": 0, - "updMode": "API", - "userGSTINTransin": "05AAACG2115R1ZN", - "vehicleNo": "GJ07DL9001" - }, - { - "enteredDate": "05/10/2022 12:39:00 PM", - "fromPlace": "Test City", - "fromState": 24, - "groupNo": "0", - "transDocDate": null, - "transDocNo": "", - "transMode": "1", - "tripshtNo": 0, - "updMode": "API", - "userGSTINTransin": "05AAACG2115R1ZN", - "vehicleNo": "GJ07DL9009" - } - ], - "actFromStateCode": 24, - "actToStateCode": 24, - "actualDist": 10, - "cessNonAdvolValue": 0, - "cessValue": 0.0, - "cgstValue": 0.0, - "docDate": "05/10/2022", - "docNo": "test_invoice_no", - "docType": "INV", - "ewayBillDate": "05/10/2022 12:39:00 PM", - "ewbNo": 301002999920, - "extendedTimes": 0, - "fromAddr1": "Test Address - 1", - "fromAddr2": "", - "fromGstin": "05AAACG2115R1ZN", - "fromPincode": 380015, - "fromPlace": "Test City", - "fromStateCode": 24, - "fromTrdName": "_Test Indian Registered Company", - "genMode": "API", - "igstValue": 0.0, - "itemList": [ - { - "cessNonAdvol": 0.0, - "cessRate": 0.0, - "cgstRate": 0.0, - "hsnCode": 61149090, - "igstRate": 0.0, - "itemNo": 1, - "productDesc": "Test Trading Goods 1", - "productId": 0, - "productName": "", - "qtyUnit": "NOS", - "quantity": 1.0, - "sgstRate": 0.0, - "taxableAmount": 100.0 - } - ], - "noValidDays": 1, - "otherValue": 0, - "rejectStatus": "N", - "sgstValue": 0.0, - "status": "CNL", - "subSupplyType": "1 ", - "supplyType": "O", - "toAddr1": "Test Address - 3", - "toAddr2": "", - "toGstin": "05AAACG2140A1ZL", - "toPincode": 380015, - "toPlace": "Test City", - "toStateCode": 24, - "toTrdName": "_Test Registered Customer", - "totInvValue": 100.0, - "totalValue": 100.0, - "transactionType": 1, - "transporterId": "05AAACG2140A1ZL", - "transporterName": "GAMZEN PLAST PVT LATD", - "userGstin": "05AAACG2115R1ZN", - "validUpto": "06/10/2022 11:59:00 PM", - "vehicleType": "R" - }, - "success": true - } - } -} \ No newline at end of file + "goods_item_with_ewaybill": { + "kwargs": { + "vehicle_no": "GJ07DL9009" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "05/10/2022", + "docNo": "test_invoice_no", + "docType": "INV", + "fromAddr1": "Test Address - 1", + "fromGstin": "05AAACG2115R1ZN", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Indian Registered Company", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1.0, + "sgstRate": 0, + "taxableAmount": 100.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 1, + "supplyType": "O", + "toAddr1": "Test Address - 3", + "toGstin": "05AAACG2140A1ZL", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Registered Customer", + "totInvValue": 100.0, + "totalValue": 100.0, + "transDistance": 10, + "transMode": 1, + "transactionType": 1, + "transporterName": "Test Common Supplier", + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "05/10/2022 12:39:00 PM", + "ewayBillNo": 301002999920, + "validUpto": "06/10/2022 11:59:00 PM" + }, + "success": true + } + }, + "update_transporter": { + "params": "action=UPDATETRANSPORTER", + "values": { + "transporter": "_Test Common Supplier", + "gst_transporter_id": "05AAACG2140A1ZL" + }, + "request_data": { + "ewbNo": "301002999920", + "transporterId": "05AAACG2140A1ZL" + }, + "response_data": { + "message": "Transporter details are updated successfully", + "result": { + "ewayBillNo": "301002999920", + "transUpdateDate": "05/10/2022 12:45:00 PM", + "transporterId": "05AAACG2140A1ZL" + }, + "success": true + } + }, + "update_vehicle_info": { + "params": "action=VEHEWB", + "values": { + "vehicle_no": "GJ07DL9001", + "mode_of_transport": "Road", + "gst_vehicle_type": "Regular", + "reason": "Others", + "remark": "Vehicle Info added", + "update_e_waybill_data": 1 + }, + "request_data": { + "ewbNo": "301002999920", + "fromPlace": "Test City", + "fromState": 24, + "reasonCode": "3", + "reasonRem": "Vehicle Info added", + "transDocDate": "", + "transDocNo": null, + "transMode": 1, + "vehicleNo": "GJ07DL9001", + "vehicleType": "R" + }, + "response_data": { + "message": "E-Way Bill is updated successfully", + "result": { + "validUpto": "06/10/2022 11:59:00 PM", + "vehUpdDate": "05/10/2022 12:44:00 PM" + }, + "success": true + } + }, + "extend_validity": { + "params": "action=EXTENDVALIDITY", + "values": { + "ewaybill": "301002999920", + "vehicle_no": "GJ07DL9009", + "remaining_distance": 5, + "consignment_status": "In Movement", + "mode_of_transport": "Road", + "gst_vehicle_type": "Regular", + "current_place": "Test", + "current_pincode": "391510", + "current_state": "Gujarat", + "address_line1": "Test Address - 3", + "address_line3": "24-Gujarat", + "reason": "Natural Calamity", + "update_e_waybill_data": 0 + }, + "request_data": { + "consignmentStatus": "M", + "ewbNo": 301002999920, + "extnRemarks": "Natural Calamity", + "extnRsnCode": 1, + "fromPincode": 391510, + "fromPlace": "Test", + "fromState": "24", + "remainingDistance": 5, + "transDocDate": "", + "transDocNo": null, + "transMode": 1, + "transitType": "", + "vehicleNo": "GJ07DL9009" + }, + "response_data": { + "message": "E-Way Bill validity is extened successfully", + "result": { + "ewayBillNo": "321003228100", + "updatedDate": "04/07/2023 06:13:00 PM", + "validUpto": "05/07/2023 11:59:00 PM" + }, + "success": true + } + }, + "credit_note": { + "request_data": { + "userGstin": "05AAACG2115R1ZN", + "supplyType": "I", + "subSupplyType": 7, + "docType": "CHL", + "docNo": "test_invoice_no", + "docDate": "22/05/2023", + "transactionType": 1, + "fromTrdName": "_Test Registered Customer", + "fromGstin": "05AAACG2140A1ZL", + "fromAddr1": "Test Address - 3", + "fromPlace": "Test City", + "fromPincode": 380015, + "fromStateCode": 24, + "actFromStateCode": 24, + "toTrdName": "_Test Indian Registered Company", + "toGstin": "05AAACG2115R1ZN", + "toAddr1": "Test Address - 1", + "toPlace": "Test City", + "toPincode": 380015, + "toStateCode": 24, + "actToStateCode": 24, + "totalValue": 15.2, + "cgstValue": 0.91, + "sgstValue": 0.91, + "igstValue": 0, + "cessValue": 0, + "TotNonAdvolVal": 0, + "OthValue": -0.02, + "totInvValue": 17.0, + "transMode": 1, + "transDistance": 1, + "vehicleNo": "GJ05DL9009", + "vehicleType": "R", + "itemList": [ + { + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "hsnCode": "61149090", + "qtyUnit": "NOS", + "quantity": 1.0, + "taxableAmount": 7.6, + "sgstRate": 6.0, + "cgstRate": 6.0, + "igstRate": 0, + "cessRate": 0, + "cessNonAdvol": 0 + }, + { + "itemNo": 2, + "productDesc": "Test Trading Goods 1", + "hsnCode": "61149090", + "qtyUnit": "NOS", + "quantity": 1.0, + "taxableAmount": 7.6, + "sgstRate": 6.0, + "cgstRate": 6.0, + "igstRate": 0, + "cessRate": 0, + "cessNonAdvol": 0 + } + ], + "mainHsnCode": "61149090" + }, + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "22/05/2023 06:22:00 PM", + "ewayBillNo": 341003154687, + "validUpto": "23/05/2023 11:59:00 PM" + }, + "success": true + } + }, + "cancel_e_waybill": { + "request_data": { + "ewbNo": "301002999920", + "cancelRsnCode": "3", + "cancelRmrk": "For Test" + }, + "params": "action=CANEWB", + "values": { + "reason": "Data Entry Mistake", + "remark": "For Test" + }, + "response_data": { + "message": "E-Way Bill is cancelled successfully", + "result": { + "cancelDate": "04/10/2022 04:14:00 PM", + "ewayBillNo": "301002999920" + }, + "success": true + } + }, + "get_e_waybill": { + "request_data": "ewbNo=301002999920", + "response_data": { + "message": "E-Way Bill is fetched successfully", + "result": { + "VehiclListDetails": [ + { + "enteredDate": "05/10/2022 12:44:00 PM", + "fromPlace": "Test City", + "fromState": 24, + "groupNo": "0", + "transDocDate": null, + "transDocNo": "", + "transMode": "1", + "tripshtNo": 0, + "updMode": "API", + "userGSTINTransin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9001" + }, + { + "enteredDate": "05/10/2022 12:39:00 PM", + "fromPlace": "Test City", + "fromState": 24, + "groupNo": "0", + "transDocDate": null, + "transDocNo": "", + "transMode": "1", + "tripshtNo": 0, + "updMode": "API", + "userGSTINTransin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9009" + } + ], + "actFromStateCode": 24, + "actToStateCode": 24, + "actualDist": 10, + "cessNonAdvolValue": 0, + "cessValue": 0.0, + "cgstValue": 0.0, + "docDate": "05/10/2022", + "docNo": "test_invoice_no", + "docType": "INV", + "ewayBillDate": "05/10/2022 12:39:00 PM", + "ewbNo": 301002999920, + "extendedTimes": 0, + "fromAddr1": "Test Address - 1", + "fromAddr2": "", + "fromGstin": "05AAACG2115R1ZN", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Indian Registered Company", + "genMode": "API", + "igstValue": 0.0, + "itemList": [ + { + "cessNonAdvol": 0.0, + "cessRate": 0.0, + "cgstRate": 0.0, + "hsnCode": 61149090, + "igstRate": 0.0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "productId": 0, + "productName": "", + "qtyUnit": "NOS", + "quantity": 1.0, + "sgstRate": 0.0, + "taxableAmount": 100.0 + } + ], + "noValidDays": 1, + "otherValue": 0, + "rejectStatus": "N", + "sgstValue": 0.0, + "status": "CNL", + "subSupplyType": "1 ", + "supplyType": "O", + "toAddr1": "Test Address - 3", + "toAddr2": "", + "toGstin": "05AAACG2140A1ZL", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Registered Customer", + "totInvValue": 100.0, + "totalValue": 100.0, + "transactionType": 1, + "transporterId": "05AAACG2140A1ZL", + "transporterName": "GAMZEN PLAST PVT LATD", + "userGstin": "05AAACG2115R1ZN", + "validUpto": "06/10/2022 11:59:00 PM", + "vehicleType": "R" + }, + "success": true + } + } +} diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 7c8408745a..987eab34ea 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -14,10 +14,14 @@ from india_compliance.gst_india.api_classes.e_invoice import EInvoiceAPI from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI +from india_compliance.gst_india.constants import STATE_NUMBERS from india_compliance.gst_india.constants.e_waybill import ( CANCEL_REASON_CODES, + CONSIGNMENT_STATUS, + EXTEND_VALIDITY_REASON_CODES, ITEM_LIMIT, SUB_SUPPLY_TYPES, + TRANSIT_TYPES, UPDATE_VEHICLE_REASON_CODES, ) from india_compliance.gst_india.utils import ( @@ -318,6 +322,60 @@ def update_transporter(*, doctype, docname, values): return send_updated_doc(doc) +@frappe.whitelist() +def extend_validity(*, doctype, docname, values): + doc = load_doc(doctype, docname, "submit") + values = frappe.parse_json(values) + + doc.db_set( + { + "vehicle_no": values.vehicle_no.replace(" ", ""), + "lr_no": values.lr_no, + "mode_of_transport": values.mode_of_transport, + } + ) + data = EWaybillData(doc).get_extend_validity_data(values) + result = EWaybillAPI(doc).extend_validity(data) + + doc.db_set("distance", values.remaining_distance) + extended_validity_date = parse_datetime(result.validUpto, day_first=True) + values_in_comment = { + "Transit Type": values.transit_type, + "Vehicle No": values.vehicle_no, + "LR No": values.lr_no, + "Mode of Transport": values.mode_of_transport, + "GST Vehicle Type": values.gst_vehicle_type, + "Valid Upto": extended_validity_date, + "Remaining Distance": values.remaining_distance, + "Current Place": values.current_place, + "Current Pincode": values.current_pincode, + "Reason": values.reason, + "Remark": values.remark, + } + + comment = _( + "e-Waybill has been extended by {user}.

New details are:
" + ).format(user=frappe.bold(get_fullname())) + + for key, value in values_in_comment.items(): + if value: + comment += "{0}: {1}
".format(frappe.bold(_(key)), value) + + log_and_process_e_waybill( + doc, + { + "name": doc.ewaybill, + "updated_on": parse_datetime(result.updatedDate, day_first=True), + "valid_upto": extended_validity_date, + "is_latest_data": 0, + }, + fetch=values.update_e_waybill_data, + comment=comment, + ) + + return send_updated_doc(doc) + + ####################################################################################### ### e-Waybill Fetch, Print and Attach Functions ############################################## ####################################################################################### @@ -629,6 +687,48 @@ def get_update_transporter_data(self, values): "transporterId": values.gst_transporter_id, } + def get_extend_validity_data(self, values): + self.validate_if_e_waybill_is_set() + self.validate_if_e_waybill_can_be_extend() + self.validate_mode_of_transport() + self.validate_transit_type(values) + self.validate_remaining_distance(values) + self.set_transporter_details() + + extension_details = { + "ewbNo": int(self.doc.ewaybill), + "vehicleNo": self.transaction_details.vehicle_no, + "fromPlace": self.sanitize_value( + values.current_place, regex=3, max_length=50 + ), + "fromState": STATE_NUMBERS[values.current_state], + "fromPincode": int(values.current_pincode), + "remainingDistance": int(values.remaining_distance), + "transDocNo": self.transaction_details.lr_no, + "transDocDate": self.transaction_details.lr_date, + "transMode": self.transaction_details.mode_of_transport, + "consignmentStatus": CONSIGNMENT_STATUS[values.consignment_status], + "transitType": ( + TRANSIT_TYPES[values.transit_type] if values.transit_type else "" + ), + "extnRsnCode": EXTEND_VALIDITY_REASON_CODES[values.reason], + "extnRemarks": self.sanitize_value( + values.remark if values.remark else values.reason, regex=3 + ), + } + + if values.consignment_status == "In Transit": + extension_details.update( + { + "addressLine1": self.sanitize_value(values.address_line1, regex=3), + "addressLine2": self.sanitize_value(values.address_line2, regex=3), + "addressLine3": self.sanitize_value(values.address_line3, regex=3), + "transMode": "In Transit", + } + ) + + return extension_details + def validate_transaction(self): # TODO: Add Support for Delivery Note @@ -718,6 +818,55 @@ def check_e_waybill_validity(self): if valid_upto and get_datetime(valid_upto) < get_datetime(): frappe.throw(_("e-Waybill cannot be modified after its validity is over")) + def validate_if_e_waybill_can_be_extend(self): + # this works because we do run_onload in load_doc above + valid_upto = get_datetime( + self.doc.get_onload().get("e_waybill_info", {}).get("valid_upto") + ) + + now = get_datetime() + extend_after = add_to_date(valid_upto, hours=-8, as_datetime=True) + extend_before = add_to_date(valid_upto, hours=8, as_datetime=True) + + if now < extend_after or now > extend_before: + frappe.throw( + _( + "e-Waybill can be extended between 8 hours before expiry time and 8 hours after expiry time" + ) + ) + + if ( + self.doc.gst_transporter_id + and self.doc.gst_transporter_id == self.doc.company_gstin + ): + frappe.throw( + _( + "e-Waybill cannot be extended by you as GST Transporter ID is assigned. Ask the transporter to extend the e-Waybill." + ) + ) + + def validate_remaining_distance(self, values): + if not values.remaining_distance: + frappe.throw(_("Distance is mandatory to extend the validity of e-Waybill")) + + if self.doc.distance and values.remaining_distance > self.doc.distance: + frappe.throw( + _( + "Remaining distance should be less than or equal to actual distance mentioned during the generation of e-Waybill" + ) + ) + + def validate_transit_type(self, values): + if values.consignment_status == "In Movement": + values.transit_type = "" + + if values.consignment_status == "In Transit" and not values.transit_type: + frappe.throw( + _("Transit Type is should be one of {0}").format( + frappe.bold(" ,".join(TRANSIT_TYPES)) + ) + ) + def validate_if_ewaybill_can_be_cancelled(self): cancel_upto = add_to_date( # this works because we do run_onload in load_doc above diff --git a/india_compliance/gst_india/utils/test_e_waybill.py b/india_compliance/gst_india/utils/test_e_waybill.py index fc7b2bdbf6..00e934faa5 100644 --- a/india_compliance/gst_india/utils/test_e_waybill.py +++ b/india_compliance/gst_india/utils/test_e_waybill.py @@ -1,13 +1,15 @@ +import datetime import json import random import re import responses +import time_machine from responses import matchers import frappe from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_to_date, get_datetime, getdate, now_datetime, today +from frappe.utils import add_to_date, get_datetime, now_datetime, today from frappe.utils.data import format_date from erpnext.controllers.sales_and_purchase_return import make_return_doc @@ -561,6 +563,67 @@ def test_get_update_transporter_data(self): ), ) + @responses.activate + def test_get_extend_validity_data(self): + """Test if extend e-waybill validity data is generated correctly""" + self._generate_e_waybill() + doc = load_doc("Sales Invoice", self.sales_invoice.name, "submit") + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(e-Waybill can be extended between.*)$"), + EWaybillData(doc).validate_if_e_waybill_can_be_extend, + ) + + add_to_date( + get_datetime(), + hours=8, + as_datetime=True, + ) + + extend_validity_data = self.e_waybill_test_data.get("extend_validity") + values = frappe._dict(extend_validity_data.get("values")) + + values.remaining_distance = None + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(Distance is mandatory to extend .*)$"), + EWaybillData(doc).validate_remaining_distance, + values, + ) + + values.remaining_distance = 15 + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile( + r"^(Remaining distance should be less than or equal to actual .*)$" + ), + EWaybillData(doc).validate_remaining_distance, + values, + ) + + values.remaining_distance = 5 + values.consignment_status = "In Transit" + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(Transit Type is should be one of.*)$"), + EWaybillData(doc).validate_transit_type, + values, + ) + + values.consignment_status = "In Movement" + + with time_machine.travel(get_datetime(), tick=False) as traveller: + traveller.shift(datetime.timedelta(hours=18)) + + self.assertDictEqual( + extend_validity_data.get("request_data"), + EWaybillData(doc).get_extend_validity_data(values), + ) + def test_validate_doctype_for_e_waybill(self): """Validate if doctype is supported for e-waybill""" purchase_invoice = create_purchase_invoice() @@ -642,7 +705,7 @@ def update_dates_for_test_data(test_data): today_date = format_date(today(), DATE_FORMAT) current_datetime = now_datetime().strftime(DATETIME_FORMAT) - next_day_datetime = add_to_date(getdate(), days=1).strftime(DATETIME_FORMAT) + next_day_datetime = add_to_date(get_datetime(), days=1).strftime(DATETIME_FORMAT) # Iterate over dict like { 'goods_item_with_ewaybill' : {...}} for key, value in test_data.items(): @@ -665,6 +728,8 @@ def update_dates_for_test_data(test_data): response_result.update({k: current_datetime}) if k == "docDate": response_result.update({k: today_date}) + if k == "updatedDate": + response_result.update({k: current_datetime}) if "docDate" in response_request: response_request.update({"docDate": today_date}) diff --git a/pyproject.toml b/pyproject.toml index 9a34877bfc..24916330cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,4 +27,8 @@ sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FRAPPE", "ERPNEXT", "FIRSTPARTY", [tool.bench.dev-dependencies] parameterized = "~=0.8.1" -responses = "~=0.21.0" \ No newline at end of file +<<<<<<< HEAD +responses = "~=0.21.0" +======= +time-machine = "~=2.10.0" +>>>>>>> 7239eb20 (feat: provision to extend validity of e-Waybill (#807)) From de10de22f952c879b732104eee3e3d2d83d85d04 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 5 Aug 2023 17:44:25 +0530 Subject: [PATCH 3/9] feat: bulk generate e-waybill (#724) Co-authored-by: Smit Vora (cherry picked from commit c2d6a41307951cfaf9f178cc052c434683b469d7) --- .../client_scripts/sales_invoice_list.js | 27 +++++++++-- india_compliance/gst_india/utils/e_invoice.py | 3 +- india_compliance/gst_india/utils/e_waybill.py | 48 +++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/sales_invoice_list.js b/india_compliance/gst_india/client_scripts/sales_invoice_list.js index 009c71c45b..619e39c113 100644 --- a/india_compliance/gst_india/client_scripts/sales_invoice_list.js +++ b/india_compliance/gst_india/client_scripts/sales_invoice_list.js @@ -7,13 +7,20 @@ frappe.listview_settings[DOCTYPE].onload = function (list_view) { if (!frappe.perm.has_perm(DOCTYPE, 0, "submit")) return; - if (gst_settings.enable_e_waybill) + if (gst_settings.enable_e_waybill) { add_bulk_action_for_submitted_invoices( list_view, __("Generate e-Waybill JSON"), generate_e_waybill_json ); + add_bulk_action_for_submitted_invoices( + list_view, + __("Enqueue Bulk e-Waybill Generation"), + enqueue_bulk_e_waybill_generation + ); + } + if (india_compliance.is_e_invoice_enabled()) add_bulk_action_for_submitted_invoices( list_view, @@ -39,14 +46,24 @@ async function generate_e_waybill_json(docnames) { trigger_file_download(ewb_data, get_e_waybill_file_name()); } -async function enqueue_bulk_e_invoice_generation(docnames) { - const now = frappe.datetime.system_datetime(); +async function enqueue_bulk_e_waybill_generation(docnames) { + enqueue_bulk_generation( + "india_compliance.gst_india.utils.e_waybill.enqueue_bulk_e_waybill_generation", + { doctype: DOCTYPE, docnames } + ); +} - const job_id = await frappe.xcall( +async function enqueue_bulk_e_invoice_generation(docnames) { + enqueue_bulk_generation( "india_compliance.gst_india.utils.e_invoice.enqueue_bulk_e_invoice_generation", { docnames } ); +} + +async function enqueue_bulk_generation(method, args) { + const job_id = await frappe.xcall(method, args); + const now = frappe.datetime.system_datetime(); const creation_filter = `[">", "${now}"]`; const api_requests_link = frappe.utils.generate_route({ type: "doctype", @@ -66,7 +83,7 @@ async function enqueue_bulk_e_invoice_generation(docnames) { frappe.msgprint( __( - `Bulk e-Invoice Generation has been queued. You can track the + `Bulk Generation has been queued. You can track the Background Job, API Request(s), and Error Log(s).`, diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index 80e6526396..63bae37e25 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -87,8 +87,7 @@ def generate_e_invoices(docnames): finally: # each e-Invoice needs to be committed individually - # nosemgrep - frappe.db.commit() + frappe.db.commit() # nosemgrep @frappe.whitelist() diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 7c8408745a..4b1b8a7e8b 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -63,6 +63,54 @@ def generate_e_waybill_json(doctype: str, docnames, values=None): ####################################################################################### +@frappe.whitelist() +def enqueue_bulk_e_waybill_generation(doctype, docnames): + """ + Enqueue bulk generation of e-Waybill for the given documents. + """ + + frappe.has_permission(doctype, "submit", throw=True) + + from india_compliance.gst_india.utils import is_api_enabled + + gst_settings = frappe.get_cached_doc("GST Settings") + if not is_api_enabled(gst_settings) or not gst_settings.enable_e_waybill: + frappe.throw(_("Please enable e-Waybill in GST Settings first.")) + + docnames = frappe.parse_json(docnames) if docnames.startswith("[") else [docnames] + rq_job = frappe.enqueue( + "india_compliance.gst_india.utils.e_waybill.generate_e_waybills", + queue="long", + timeout=len(docnames) * 240, # 4 mins per e-Waybill + doctype=doctype, + docnames=docnames, + ) + + return rq_job.id + + +def generate_e_waybills(doctype, docnames): + """ + Bulk generate e-Waybill for the given documents. + """ + + for docname in docnames: + try: + doc = load_doc(doctype, docname, "submit") + _generate_e_waybill(doc) + except Exception: + frappe.log_error( + title=_("e-Waybill generation failed for {0} {1}").format( + doctype, docname + ), + message=frappe.get_traceback(), + ) + + finally: + # each e-Waybill needs to be committed individually + frappe.db.commit() # nosemgrep + + @frappe.whitelist() def generate_e_waybill(*, doctype, docname, values=None): doc = load_doc(doctype, docname, "submit") From f2be706e570dd4372430de9d47d01e72e9407f24 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 5 Aug 2023 18:03:09 +0530 Subject: [PATCH 4/9] fix: resolve conflicts for dependencies --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 24916330cf..6763e6545c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,5 @@ sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FRAPPE", "ERPNEXT", "FIRSTPARTY", [tool.bench.dev-dependencies] parameterized = "~=0.8.1" -<<<<<<< HEAD responses = "~=0.21.0" -======= time-machine = "~=2.10.0" ->>>>>>> 7239eb20 (feat: provision to extend validity of e-Waybill (#807)) From af634e4e47245d24ad19193e2af3ce1dfd8a2b8c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:12:25 +0530 Subject: [PATCH 5/9] feat: e-Waybill for purchase invoice (backport #350) (#934) Co-authored-by: Smit Vora Co-authored-by: ljain112 Co-authored-by: Daizy Modi --- .../client_scripts/e_waybill_actions.js | 87 ++++-- .../client_scripts/purchase_invoice.js | 21 +- .../gst_india/constants/custom_fields.py | 60 ++-- .../gst_india/constants/e_waybill.py | 19 ++ .../doctype/gst_settings/gst_settings.json | 8 + .../gst_india/overrides/purchase_invoice.py | 52 +++- .../gst_india/overrides/sales_invoice.py | 7 +- .../overrides/test_transaction_data.py | 2 +- india_compliance/gst_india/utils/e_invoice.py | 2 +- india_compliance/gst_india/utils/e_waybill.py | 280 +++++++++++------- .../gst_india/utils/test_e_waybill.py | 10 +- .../gst_india/utils/transaction_data.py | 21 +- india_compliance/hooks.py | 5 +- india_compliance/patches.txt | 2 +- 14 files changed, 382 insertions(+), 194 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/e_waybill_actions.js b/india_compliance/gst_india/client_scripts/e_waybill_actions.js index dba33b9185..ad86d5fba7 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_actions.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_actions.js @@ -1,9 +1,5 @@ function setup_e_waybill_actions(doctype) { - if ( - !gst_settings.enable_e_waybill || - (doctype == "Delivery Note" && !gst_settings.enable_e_waybill_from_dn) - ) - return; + if (!gst_settings.enable_e_waybill) return; frappe.ui.form.on(doctype, { mode_of_transport(frm) { @@ -31,8 +27,7 @@ function setup_e_waybill_actions(doctype) { if ( frm.doc.docstatus != 1 || frm.is_dirty() || - !is_e_waybill_applicable(frm) || - (frm.doctype === "Delivery Note" && !frm.doc.customer_address) + !is_e_waybill_applicable(frm) ) return; @@ -132,11 +127,9 @@ function setup_e_waybill_actions(doctype) { }, async on_submit(frm) { if ( - // threshold is only met for Sales Invoice + frm.doctype != "Sales Invoice" || !has_e_waybill_threshold_met(frm) || frm.doc.ewaybill || - frm.doc.is_return || - frm.doc.is_debit_note || !india_compliance.is_api_enabled() || !gst_settings.auto_generate_e_waybill || is_e_invoice_applicable(frm) || @@ -672,11 +665,10 @@ function show_update_transporter_dialog(frm) { } async function show_extend_validity_dialog(frm) { - const shipping_address = await frappe.db.get_doc( + const destination_address = await frappe.db.get_doc( "Address", - frm.doc.shipping_address_name || frm.doc.customer_address + get_destination_address_name(frm) ); - const is_in_movement = "eval: doc.consignment_status === 'In Movement'"; const is_in_transit = "eval: doc.consignment_status === 'In Transit'"; @@ -749,7 +741,7 @@ async function show_extend_validity_dialog(frm) { label: "Address Line1", fieldname: "address_line1", fieldtype: "Data", - default: shipping_address.address_line1, + default: destination_address.address_line1, depends_on: is_in_transit, mandatory_depends_on: is_in_transit, }, @@ -757,7 +749,7 @@ async function show_extend_validity_dialog(frm) { label: "Address Line2", fieldname: "address_line2", fieldtype: "Data", - default: shipping_address.address_line2, + default: destination_address.address_line2, depends_on: is_in_transit, mandatory_depends_on: is_in_transit, }, @@ -765,7 +757,7 @@ async function show_extend_validity_dialog(frm) { label: "Address Line3", fieldname: "address_line3", fieldtype: "Data", - default: shipping_address.city, + default: destination_address.city, depends_on: is_in_transit, mandatory_depends_on: is_in_transit, }, @@ -777,14 +769,14 @@ async function show_extend_validity_dialog(frm) { fieldname: "current_place", fieldtype: "Data", reqd: 1, - default: shipping_address.city, + default: destination_address.city, }, { label: "Current Pincode", fieldname: "current_pincode", fieldtype: "Data", reqd: 1, - default: shipping_address.pincode, + default: destination_address.pincode, }, { label: "Current State", @@ -792,7 +784,7 @@ async function show_extend_validity_dialog(frm) { fieldtype: "Autocomplete", options: frappe.boot.india_state_options.join("\n"), reqd: 1, - default: shipping_address.state, + default: destination_address.state, }, { fieldtype: "Section Break", @@ -855,19 +847,20 @@ function is_e_waybill_valid(frm) { } function has_e_waybill_threshold_met(frm) { - if ( - frm.doc.doctype == "Sales Invoice" && - Math.abs(frm.doc.base_grand_total) >= gst_settings.e_waybill_threshold - ) + if (Math.abs(frm.doc.base_grand_total) >= gst_settings.e_waybill_threshold) return true; } function is_e_waybill_applicable(frm) { - // means company is Indian and not Unregistered if ( + // means company is Indian and not Unregistered !frm.doc.company_gstin || - (frm.doctype === "Sales Invoice" && - frm.doc.company_gstin === frm.doc.billing_address_gstin) + !gst_settings.enable_e_waybill || + !( + is_e_waybill_applicable_on_sales_invoice(frm) || + is_e_waybill_applicable_on_purchase_invoice(frm) || + is_e_waybill_applicable_on_delivery_note(frm) + ) ) return; @@ -886,9 +879,11 @@ function can_extend_e_waybill(frm) { const valid_upto = frm.doc.__onload?.e_waybill_info?.valid_upto; const extend_after = get_hours(valid_upto, -8); const extend_before = get_hours(valid_upto, 8); + const now = frappe.datetime.now_datetime(); if ( - extend_after < frappe.datetime.now_datetime() < extend_before && + extend_after < now && + now < extend_before && frm.doc.gst_transporter_id != frm.doc.company_gstin ) return true; @@ -907,6 +902,33 @@ function is_e_waybill_cancellable(frm) { ); } +function is_e_waybill_applicable_on_sales_invoice(frm) { + return ( + frm.doctype == "Sales Invoice" && + frm.doc.company_gstin !== frm.doc.billing_address_gstin && + frm.doc.customer_address && + !frm.doc.is_return && + !frm.doc.is_debit_note + ); +} + +function is_e_waybill_applicable_on_delivery_note(frm) { + return ( + frm.doctype == "Delivery Note" && + frm.doc.customer_address && + gst_settings.enable_e_waybill_from_dn + ); +} + +function is_e_waybill_applicable_on_purchase_invoice(frm) { + return ( + frm.doctype == "Purchase Invoice" && + frm.doc.supplier_address && + frm.doc.company_gstin !== frm.doc.supplier_gstin && + gst_settings.enable_e_waybill_from_pi + ); +} + function is_e_waybill_generated_using_api(frm) { const e_waybill_info = frm.doc.__onload && frm.doc.__onload.e_waybill_info; return e_waybill_info && e_waybill_info.created_on; @@ -1021,3 +1043,14 @@ function get_e_waybill_file_name(docname) { function set_primary_action_label(dialog, primary_action_label) { dialog.get_primary_btn().removeClass("hide").html(primary_action_label); } + +function get_destination_address_name(frm) { + if (frm.doc.doctype == "Purchase Invoice") { + if (frm.doc.is_return) return frm.doc.supplier_address; + return frm.doc.shipping_address_name || frm.doc.billing_address; + } else { + if (frm.doc.is_return) + return frm.doc.dispatch_address_name || frm.doc.company_address; + return frm.doc.shipping_address_name || frm.doc.customer_address; + } +} diff --git a/india_compliance/gst_india/client_scripts/purchase_invoice.js b/india_compliance/gst_india/client_scripts/purchase_invoice.js index 3c7d411e77..4033caaf2d 100644 --- a/india_compliance/gst_india/client_scripts/purchase_invoice.js +++ b/india_compliance/gst_india/client_scripts/purchase_invoice.js @@ -1,4 +1,23 @@ -frappe.ui.form.on("Purchase Invoice", { +const DOCTYPE = "Purchase Invoice"; +setup_e_waybill_actions(DOCTYPE); + +frappe.ui.form.on(DOCTYPE, { + after_save(frm) { + if ( + frm.doc.docstatus || + frm.doc.supplier_address || + !is_e_waybill_applicable(frm) + ) + return; + + frappe.show_alert( + { + message: __("Supplier Address is required to create e-Waybill"), + indicator: "yellow", + }, + 10 + ); + }, refresh(frm) { if ( frm.doc.docstatus !== 1 || diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index d500741b50..01eadfad5f 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -370,7 +370,7 @@ "fieldname": "gst_section", "label": "GST Details", "fieldtype": "Section Break", - "insert_after": "language", + "insert_after": "gst_vehicle_type", "print_hide": 1, "collapsible": 1, }, @@ -726,19 +726,9 @@ "print_hide": 1, "translatable": 0, }, - { - "fieldname": "ewaybill", - "label": "e-Waybill No.", - "fieldtype": "Data", - "depends_on": "eval: doc.docstatus === 1 || doc.ewaybill", - "allow_on_submit": 1, - "insert_after": "customer_name", - "translatable": 0, - "no_copy": 1, - }, ] -E_WAYBILL_SI_FIELDS = [ +E_WAYBILL_INV_FIELDS = [ { "fieldname": "transporter_info", "label": "Transporter Info", @@ -814,23 +804,39 @@ "default": "Today", "print_hide": 1, }, - { - "fieldname": "e_waybill_status", - "label": "e-Waybill Status", - "fieldtype": "Select", - "insert_after": "ewaybill", - "options": "\nPending\nGenerated\nCancelled\nNot Applicable", - "print_hide": 1, - "no_copy": 1, - "translatable": 1, - "allow_on_submit": 1, - "depends_on": "eval:doc.docstatus === 1 && (!doc.ewaybill || in_list(['','Pending', 'Not Applicable'], doc.e_waybill_status))", - "read_only_depends_on": "eval:doc.e_waybill_status === 'Generated' && doc.ewaybill", - }, *E_WAYBILL_DN_FIELDS, ] +sales_e_waybill_field = { + "fieldname": "ewaybill", + "label": "e-Waybill No.", + "fieldtype": "Data", + "depends_on": "eval: doc.docstatus === 1 || doc.ewaybill", + "allow_on_submit": 1, + "translatable": 0, + "no_copy": 1, + "insert_after": "customer_name", +} + +e_waybill_status_field = { + "fieldname": "e_waybill_status", + "label": "e-Waybill Status", + "fieldtype": "Select", + "insert_after": "ewaybill", + "options": "\nPending\nGenerated\nCancelled\nNot Applicable", + "print_hide": 1, + "no_copy": 1, + "translatable": 1, + "allow_on_submit": 1, + "depends_on": "eval:doc.docstatus === 1 && (!doc.ewaybill || in_list(['','Pending', 'Not Applicable'], doc.e_waybill_status))", + "read_only_depends_on": "eval:doc.e_waybill_status === 'Generated' && doc.ewaybill", +} + +purchase_e_waybill_field = {**sales_e_waybill_field, "insert_after": "supplier_name"} + E_WAYBILL_FIELDS = { - "Sales Invoice": E_WAYBILL_SI_FIELDS, - "Delivery Note": E_WAYBILL_DN_FIELDS, + "Sales Invoice": E_WAYBILL_INV_FIELDS + + [sales_e_waybill_field, e_waybill_status_field], + "Delivery Note": E_WAYBILL_DN_FIELDS + [sales_e_waybill_field], + "Purchase Invoice": E_WAYBILL_INV_FIELDS + [purchase_e_waybill_field], } diff --git a/india_compliance/gst_india/constants/e_waybill.py b/india_compliance/gst_india/constants/e_waybill.py index 004a3e6778..374ed5b81f 100644 --- a/india_compliance/gst_india/constants/e_waybill.py +++ b/india_compliance/gst_india/constants/e_waybill.py @@ -10,7 +10,26 @@ # } # # DATETIME_FORMAT = "%d/%m/%Y %I:%M:%S %p" +selling_address = { + "bill_from": "company_address", + "bill_to": "customer_address", + "ship_from": "dispatch_address_name", + "ship_to": "shipping_address_name", +} + +buying_address = { + "bill_from": "supplier_address", + "bill_to": "billing_address", + "ship_from": "supplier_address", + "ship_to": "shipping_address", +} +ADDRESS_FIELDS = { + "Sales Invoice": selling_address, + "Purchase Invoice": buying_address, + "Delivery Note": selling_address, +} +PERMITTED_DOCTYPES = list(ADDRESS_FIELDS.keys()) CANCEL_REASON_CODES = { "Duplicate": "1", diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json index c887626417..17a86bd2c4 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json @@ -22,6 +22,7 @@ "e_waybill_section", "enable_e_waybill", "enable_e_waybill_from_dn", + "enable_e_waybill_from_pi", "fetch_e_waybill_data", "attach_e_waybill_print", "column_break_10", @@ -238,6 +239,13 @@ "fieldtype": "Check", "label": "Autofill Party Information based on GSTIN" }, + { + "default": "0", + "depends_on": "eval: doc.enable_e_waybill", + "fieldname": "enable_e_waybill_from_pi", + "fieldtype": "Check", + "label": "Enable e-Waybill Generation from Purchase Invoice" + }, { "collapsible": 1, "fieldname": "uom_mapping_section", diff --git a/india_compliance/gst_india/overrides/purchase_invoice.py b/india_compliance/gst_india/overrides/purchase_invoice.py index 3de80576af..f056c9e47c 100644 --- a/india_compliance/gst_india/overrides/purchase_invoice.py +++ b/india_compliance/gst_india/overrides/purchase_invoice.py @@ -1,8 +1,41 @@ import frappe from frappe.utils import flt +from india_compliance.gst_india.overrides.sales_invoice import ( + 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 +from india_compliance.gst_india.utils import get_gst_accounts_by_type, is_api_enabled +from india_compliance.gst_india.utils.e_waybill import get_e_waybill_info + + +def onload(doc, method=None): + if doc.docstatus != 1: + return + + if doc.gst_category == "Overseas": + doc.set_onload( + "bill_of_entry_exists", + frappe.db.exists( + "Bill of Entry", + {"purchase_invoice": doc.name, "docstatus": 1}, + ), + ) + + if not doc.get("ewaybill"): + return + + gst_settings = frappe.get_cached_doc("GST Settings") + + if not is_api_enabled(gst_settings): + return + + if ( + gst_settings.enable_e_waybill + and gst_settings.enable_e_waybill_from_pi + and doc.ewaybill + ): + doc.set_onload("e_waybill_info", get_e_waybill_info(doc)) def validate(doc, method=None): @@ -35,19 +68,6 @@ def update_itc_totals(doc, method=None): doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) -def onload(doc, method): - if doc.docstatus != 1 or doc.gst_category != "Overseas": - return - - doc.set_onload( - "bill_of_entry_exists", - frappe.db.exists( - "Bill of Entry", - {"purchase_invoice": doc.name, "docstatus": 1}, - ), - ) - - def get_dashboard_data(data): transactions = data.setdefault("transactions", []) reference_section = next( @@ -60,4 +80,8 @@ def get_dashboard_data(data): reference_section["items"].append("Bill of Entry") + update_dashboard_with_gst_logs( + "Purchase Invoice", data, "e-Waybill Log", "Integration Request" + ) + return data diff --git a/india_compliance/gst_india/overrides/sales_invoice.py b/india_compliance/gst_india/overrides/sales_invoice.py index 702832cd20..be16e50d53 100644 --- a/india_compliance/gst_india/overrides/sales_invoice.py +++ b/india_compliance/gst_india/overrides/sales_invoice.py @@ -33,12 +33,7 @@ def onload(doc, method=None): if not doc.get("irn"): return - gst_settings = frappe.get_cached_value( - "GST Settings", - "GST Settings", - ("enable_api", "enable_e_waybill", "enable_e_invoice", "api_secret"), - as_dict=1, - ) + gst_settings = frappe.get_cached_doc("GST Settings") if not is_api_enabled(gst_settings): return diff --git a/india_compliance/gst_india/overrides/test_transaction_data.py b/india_compliance/gst_india/overrides/test_transaction_data.py index 91e4b7f475..2f8b905556 100644 --- a/india_compliance/gst_india/overrides/test_transaction_data.py +++ b/india_compliance/gst_india/overrides/test_transaction_data.py @@ -149,7 +149,7 @@ def test_set_transaction_details(self): gst_transaction_data.transaction_details, { "company_name": "_Test Indian Registered Company", - "customer_name": "_Test Registered Customer", + "party_name": "_Test Registered Customer", "date": format_date(frappe.utils.today(), "dd/mm/yyyy"), "total": 100.0, "rounding_adjustment": 0.0, diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index 63bae37e25..ef87976355 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -510,7 +510,7 @@ def set_party_address_details(self): self.doc.dispatch_address_name ) - self.billing_address.legal_name = self.transaction_details.customer_name + self.billing_address.legal_name = self.transaction_details.party_name self.company_address.legal_name = self.transaction_details.company_name def get_invoice_data(self): diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 3fe0660af6..57d7ebc361 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -16,10 +16,12 @@ from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI from india_compliance.gst_india.constants import STATE_NUMBERS from india_compliance.gst_india.constants.e_waybill import ( + ADDRESS_FIELDS, CANCEL_REASON_CODES, CONSIGNMENT_STATUS, EXTEND_VALIDITY_REASON_CODES, ITEM_LIMIT, + PERMITTED_DOCTYPES, SUB_SUPPLY_TYPES, TRANSIT_TYPES, UPDATE_VEHICLE_REASON_CODES, @@ -33,9 +35,6 @@ ) from india_compliance.gst_india.utils.transaction_data import GSTTransactionData -PERMITTED_DOCTYPES = {"Sales Invoice", "Delivery Note"} - - ####################################################################################### ### Manual JSON Generation for e-Waybill ############################################## ####################################################################################### @@ -129,7 +128,7 @@ def _generate_e_waybill(doc, throw=True): # Via e-Invoice API if not Return or Debit Note # Handles following error when generating e-Waybill using IRN: # 4010: E-way Bill cannot generated for Debit Note, Credit Note and Services - with_irn = doc.get("irn") and not (doc.is_return or doc.is_debit_note) + with_irn = doc.get("irn") and not (doc.is_return or doc.get("is_debit_note")) data = EWaybillData(doc).get_data(with_irn=with_irn) except frappe.ValidationError as e: @@ -232,7 +231,7 @@ def _cancel_e_waybill(doc, values): if ( e_waybill_data.sandbox_mode and doc.get("irn") - and not (doc.is_return or doc.is_debit_note) + and not (doc.is_return or doc.get("is_debit_note")) ) else EWaybillAPI ) @@ -706,18 +705,20 @@ def get_update_vehicle_data(self, values): self.validate_mode_of_transport() self.set_transporter_details() - dispatch_address_name = ( - self.doc.dispatch_address_name - if self.doc.dispatch_address_name - else self.doc.company_address + addresses = ADDRESS_FIELDS.get(self.doc.doctype, {}) + + ship_from_address_name = ( + addresses.get("ship_from") + if self.doc.get(addresses.get("ship_from")) + else addresses.get("bill_from") ) - dispatch_address = self.get_address_details(dispatch_address_name) + ship_from = self.get_address_details(self.doc.get(ship_from_address_name)) return { "ewbNo": self.doc.ewaybill, "vehicleNo": self.transaction_details.vehicle_no, - "fromPlace": dispatch_address.city, - "fromState": dispatch_address.state_number, + "fromPlace": ship_from.city, + "fromState": ship_from.state_number, "reasonCode": UPDATE_VEHICLE_REASON_CODES[values.reason], "reasonRem": self.sanitize_value(values.remark, regex=3), "transDocNo": self.transaction_details.lr_no, @@ -790,6 +791,7 @@ def validate_transaction(self): ) self.validate_applicability() + self.validate_bill_no_for_purchase() def validate_settings(self): if not self.settings.enable_e_waybill: @@ -805,12 +807,11 @@ def validate_applicability(self): - Sales Invoice with same company and billing gstin """ - for fieldname in ("company_address", "customer_address"): - if not self.doc.get(fieldname): + address = ADDRESS_FIELDS.get(self.doc.doctype) + for key in ("bill_from", "bill_to"): + if not self.doc.get(address[key]): frappe.throw( - _("{0} is required to generate e-Waybill").format( - _(self.doc.meta.get_label(fieldname)) - ), + _("{0} is required to generate e-Waybill").format(_(address[key])), exc=frappe.MandatoryError, ) @@ -845,12 +846,23 @@ def validate_applicability(self): title=_("Invalid Data"), ) + def validate_bill_no_for_purchase(self): + if ( + self.doc.doctype == "Purchase Invoice" + and not self.doc.is_return + and not self.doc.bill_no + and self.doc.gst_category != "Unregistered" + ): + frappe.throw( + _("Bill No is mandatory to generate e-Waybill for Purchase Invoice"), + title=_("Invalid Data"), + ) + def validate_doctype_for_e_waybill(self): if self.doc.doctype not in PERMITTED_DOCTYPES: frappe.throw( - _( - "Only Sales Invoice and Delivery Note are supported for e-Waybill" - " actions" + _("Only {0} are supported for e-Waybill actions").format( + ", ".join(PERMITTED_DOCTYPES) ), title=_("Unsupported DocType"), ) @@ -969,93 +981,136 @@ def get_all_item_details(self): def update_transaction_details(self): # first HSN Code for goods + doc = self.doc main_hsn_code = next( row.gst_hsn_code - for row in self.doc.items + for row in doc.items if not row.gst_hsn_code.startswith("99") ) self.transaction_details.update( - { + sub_supply_desc="", + main_hsn_code=main_hsn_code, + ) + + default_supply_types = { + # Key: (doctype, is_return) + ("Sales Invoice", 0): { "supply_type": "O", - "sub_supply_type": 1, + "sub_supply_type": 1, # Supply "document_type": "INV", - "main_hsn_code": main_hsn_code, - } - ) + }, + ("Sales Invoice", 1): { + "supply_type": "I", + "sub_supply_type": 7, # Sales Return + "document_type": "CHL", + }, + ("Delivery Note", 0): { + "supply_type": "O", + "sub_supply_type": doc.get("_sub_supply_type", ""), + "document_type": "CHL", + }, + ("Delivery Note", 1): { + "supply_type": "I", + "sub_supply_type": doc.get("_sub_supply_type", ""), + "document_type": "CHL", + }, + ("Purchase Invoice", 0): { + "supply_type": "I", + "sub_supply_type": 1, # Supply + "document_type": "INV", + }, + ("Purchase Invoice", 1): { + "supply_type": "O", + "sub_supply_type": 8, # Others + "document_type": "OTH", + "sub_supply_desc": "Purchase Return", + }, + } - if self.doc.is_return: - self.transaction_details.update( - { - "supply_type": "I", - "sub_supply_type": 7, - "document_type": "CHL", - } - ) + self.transaction_details.update( + default_supply_types.get((doc.doctype, doc.is_return), {}) + ) - elif is_foreign_doc(self.doc): - self.transaction_details.sub_supply_type = 3 + if is_foreign_doc(self.doc): + self.transaction_details.update(sub_supply_type=3) # Export + if not doc.is_export_with_gst: + self.transaction_details.update(document_type="BIL") - if not self.doc.is_export_with_gst: - self.transaction_details.document_type = "BIL" - - if self.doc.doctype == "Delivery Note": - self.transaction_details.update( - { - "sub_supply_type": self.doc._sub_supply_type, - "document_type": "CHL", - } - ) + if self.doc.doctype == "Purchase Invoice" and not self.doc.is_return: + self.transaction_details.name = self.doc.bill_no or self.doc.name def set_party_address_details(self): transaction_type = 1 - ship_to_address = ( + address = self.get_address_map() + + address.ship_to = ( self.doc.port_address if (is_foreign_doc(self.doc) and self.doc.port_address) - else self.doc.shipping_address_name + else address.ship_to ) - has_different_shipping_address = ( - ship_to_address and self.doc.customer_address != ship_to_address + if self.doc.is_return: + address.bill_from, address.bill_to = address.bill_to, address.bill_from + address.ship_from, address.ship_to = address.ship_to, address.ship_from + + has_different_to_address = ( + address.ship_to and address.ship_to != address.bill_to ) - has_different_dispatch_address = ( - self.doc.dispatch_address_name - and self.doc.company_address != self.doc.dispatch_address_name + has_different_from_address = ( + address.ship_from and address.ship_from != address.bill_from ) - self.to_address = self.get_address_details(self.doc.customer_address) - self.from_address = self.get_address_details(self.doc.company_address) + self.bill_to = self.get_address_details(address.bill_to) + self.bill_from = self.get_address_details(address.bill_from) # Defaults - # calling copy() since we're mutating from_address and to_address below - self.shipping_address = self.to_address.copy() - self.dispatch_address = self.from_address.copy() + # billing state is changed for SEZ, hence copy() + self.ship_to = self.bill_to.copy() + self.ship_from = self.bill_from.copy() - if has_different_shipping_address and has_different_dispatch_address: + if has_different_to_address and has_different_from_address: transaction_type = 4 - self.shipping_address = self.get_address_details(ship_to_address) - self.dispatch_address = self.get_address_details( - self.doc.dispatch_address_name - ) + self.ship_to = self.get_address_details(address.ship_to) + self.ship_from = self.get_address_details(address.ship_from) - elif has_different_dispatch_address: + elif has_different_from_address: transaction_type = 3 - self.dispatch_address = self.get_address_details( - self.doc.dispatch_address_name - ) + self.ship_from = self.get_address_details(address.ship_from) - elif has_different_shipping_address: + elif has_different_to_address: transaction_type = 2 - self.shipping_address = self.get_address_details(ship_to_address) + self.ship_to = self.get_address_details(address.ship_to) self.transaction_details.transaction_type = transaction_type - self.to_address.legal_name = self.transaction_details.customer_name - self.from_address.legal_name = self.transaction_details.company_name + to_party = self.transaction_details.party_name + from_party = self.transaction_details.company_name + + if self.doc.doctype == "Purchase Invoice": + to_party, from_party = from_party, to_party + + if self.doc.is_return: + to_party, from_party = from_party, to_party + + self.bill_to.legal_name = to_party + self.bill_from.legal_name = from_party if self.doc.gst_category == "SEZ": - self.to_address.state_number = 96 + self.bill_to.state_number = 96 + + def get_address_map(self): + """ + Return address names for bill_to, bill_from, ship_to, ship_from + """ + address_fields = ADDRESS_FIELDS.get(self.doc.doctype, {}) + out = frappe._dict() + + for key, field in address_fields.items(): + out[key] = self.doc.get(field) + + return out def get_address_details(self, *args, **kwargs): address_details = super().get_address_details(*args, **kwargs) @@ -1072,7 +1127,7 @@ def validate_distance_for_same_pincode(self): Accuracy of distance is immaterial and used only for e-Waybill validity determination. """ - if self.dispatch_address.pincode != self.shipping_address.pincode: + if self.ship_from.pincode != self.ship_to.pincode: return if self.transaction_details.distance > 100: @@ -1088,52 +1143,63 @@ def validate_distance_for_same_pincode(self): def get_transaction_data(self): if self.sandbox_mode: - self.transaction_details.company_gstin = "05AAACG2115R1ZN" - self.transaction_details.name = ( - random_string(6).lstrip("0") - if not frappe.flags.in_test - else "test_invoice_no" - ) + REGISTERED_GSTIN = "05AAACG2115R1ZN" + OTHER_GSTIN = "05AAACG2140A1ZL" - self.from_address.gstin = "05AAACG2115R1ZN" - self.to_address.gstin = ( - "05AAACG2140A1ZL" - if self.transaction_details.sub_supply_type not in (5, 10, 11, 12) - else "05AAACG2115R1ZN" + self.transaction_details.update( + { + "company_gstin": REGISTERED_GSTIN, + "name": random_string(6).lstrip("0") + if not frappe.flags.in_test + else "test_invoice_no", + } ) - if self.doc.is_return: - self.from_address, self.to_address = self.to_address, self.from_address - self.dispatch_address, self.shipping_address = ( - self.shipping_address, - self.dispatch_address, - ) + # to ensure company_gstin is inline with company address gstin + sandbox_gstin = { + # (doctype, is_return): (bill_from, bill_to) + ("Sales Invoice", 0): (REGISTERED_GSTIN, OTHER_GSTIN), + ("Sales Invoice", 1): (OTHER_GSTIN, REGISTERED_GSTIN), + ("Purchase Invoice", 0): (OTHER_GSTIN, REGISTERED_GSTIN), + ("Purchase Invoice", 1): (REGISTERED_GSTIN, OTHER_GSTIN), + ("Delivery Note", 0): (REGISTERED_GSTIN, OTHER_GSTIN), + ("Delivery Note", 1): (OTHER_GSTIN, REGISTERED_GSTIN), + } + + def _get_sandbox_gstin(address, key): + if address.gst_category == "Unregistered": + return "URP" + + return sandbox_gstin.get((self.doc.doctype, self.doc.is_return))[key] + + self.bill_from.gstin = _get_sandbox_gstin(self.bill_from, 0) + self.bill_to.gstin = _get_sandbox_gstin(self.bill_to, 1) data = { "userGstin": self.transaction_details.company_gstin, "supplyType": self.transaction_details.supply_type, "subSupplyType": self.transaction_details.sub_supply_type, - "subSupplyDesc": "", + "subSupplyDesc": self.transaction_details.sub_supply_desc, "docType": self.transaction_details.document_type, "docNo": self.transaction_details.name, "docDate": self.transaction_details.date, "transactionType": self.transaction_details.transaction_type, - "fromTrdName": self.from_address.legal_name, - "fromGstin": self.from_address.gstin, - "fromAddr1": self.dispatch_address.address_line1, - "fromAddr2": self.dispatch_address.address_line2, - "fromPlace": self.dispatch_address.city, - "fromPincode": self.dispatch_address.pincode, - "fromStateCode": self.from_address.state_number, - "actFromStateCode": self.dispatch_address.state_number, - "toTrdName": self.to_address.legal_name, - "toGstin": self.to_address.gstin, - "toAddr1": self.shipping_address.address_line1, - "toAddr2": self.shipping_address.address_line2, - "toPlace": self.shipping_address.city, - "toPincode": self.shipping_address.pincode, - "toStateCode": self.to_address.state_number, - "actToStateCode": self.shipping_address.state_number, + "fromTrdName": self.bill_from.legal_name, + "fromGstin": self.bill_from.gstin, + "fromAddr1": self.ship_from.address_line1, + "fromAddr2": self.ship_from.address_line2, + "fromPlace": self.ship_from.city, + "fromPincode": self.ship_from.pincode, + "fromStateCode": self.bill_from.state_number, + "actFromStateCode": self.ship_from.state_number, + "toTrdName": self.bill_to.legal_name, + "toGstin": self.bill_to.gstin, + "toAddr1": self.ship_to.address_line1, + "toAddr2": self.ship_to.address_line2, + "toPlace": self.ship_to.city, + "toPincode": self.ship_to.pincode, + "toStateCode": self.bill_to.state_number, + "actToStateCode": self.ship_to.state_number, "totalValue": self.transaction_details.total, "cgstValue": self.transaction_details.total_cgst_amount, "sgstValue": self.transaction_details.total_sgst_amount, diff --git a/india_compliance/gst_india/utils/test_e_waybill.py b/india_compliance/gst_india/utils/test_e_waybill.py index 00e934faa5..157c3fe109 100644 --- a/india_compliance/gst_india/utils/test_e_waybill.py +++ b/india_compliance/gst_india/utils/test_e_waybill.py @@ -26,8 +26,8 @@ from india_compliance.gst_india.utils.tests import ( _append_taxes, append_item, - create_purchase_invoice, create_sales_invoice, + create_transaction, ) DATETIME_FORMAT = "%d/%m/%Y %I:%M:%S %p" @@ -626,13 +626,15 @@ def test_get_extend_validity_data(self): def test_validate_doctype_for_e_waybill(self): """Validate if doctype is supported for e-waybill""" - purchase_invoice = create_purchase_invoice() + purchase_order = create_transaction(doctype="Purchase Order") self.assertRaisesRegex( frappe.exceptions.ValidationError, - re.compile(r"^(Only Sales Invoice and Delivery Note are supported.*)$"), + re.compile( + r"^(Only Sales Invoice, Purchase Invoice, Delivery Note are supported.*)$" + ), EWaybillData, - purchase_invoice, + purchase_order, ) @responses.activate diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 90c381376d..16c68ff751 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -39,10 +39,22 @@ 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" + + if self.doc.doctype == "Purchase Invoice": + 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" + + 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, "Output").items() + for k, v in get_gst_accounts_by_type(self.doc.company, gst_type).items() } def set_transaction_details(self): @@ -50,10 +62,10 @@ def set_transaction_details(self): self.transaction_details.update( { "company_name": self.sanitize_value(self.doc.company), - "customer_name": self.sanitize_value( - self.doc.customer_name + "party_name": self.sanitize_value( + self.party_name or frappe.db.get_value( - "Customer", self.doc.customer, "customer_name" + self.doc.doctype, self.party_name, self.party_name_field ) ), "date": format_date(self.doc.posting_date, self.DATE_FORMAT), @@ -266,6 +278,7 @@ def validate_transaction(self): msg=_("Posting Date cannot be greater than Today's Date"), title=_("Invalid Data"), ) + # compare posting date and lr date, only if lr no is set if ( self.doc.lr_no diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 84a2de4b00..0db440fc04 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -45,7 +45,10 @@ ], "Journal Entry": "gst_india/client_scripts/journal_entry.js", "Payment Entry": "gst_india/client_scripts/payment_entry.js", - "Purchase Invoice": "gst_india/client_scripts/purchase_invoice.js", + "Purchase Invoice": [ + "gst_india/client_scripts/e_waybill_actions.js", + "gst_india/client_scripts/purchase_invoice.js", + ], "Sales Invoice": [ "gst_india/client_scripts/e_invoice_actions.js", "gst_india/client_scripts/e_waybill_actions.js", diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 500c3072c1..e5f97d0e85 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() #21 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #22 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #2 india_compliance.patches.post_install.remove_old_fields india_compliance.patches.post_install.update_company_gstin From 9efab01e3eb717f45275cbdc23bd6fb028af7faf Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Tue, 8 Aug 2023 18:59:21 +0530 Subject: [PATCH 6/9] test: generate e-waybill for Delivery Note and Purchase Invoice (#936) (cherry picked from commit 53877978ddc87c1953e2949f9ff9851b0cf435b9) --- .../gst_india/data/test_e_waybill.json | 509 ++++++++++++++++++ india_compliance/gst_india/utils/e_waybill.py | 12 +- .../gst_india/utils/test_e_waybill.py | 192 ++++++- india_compliance/tests/test_records.json | 19 + 4 files changed, 720 insertions(+), 12 deletions(-) diff --git a/india_compliance/gst_india/data/test_e_waybill.json b/india_compliance/gst_india/data/test_e_waybill.json index 6068181be8..624cd17fe2 100644 --- a/india_compliance/gst_india/data/test_e_waybill.json +++ b/india_compliance/gst_india/data/test_e_waybill.json @@ -352,5 +352,514 @@ }, "success": true } + }, + "is_return_dn_with_same_gstin": { + "kwargs": { + "qty": 1000, + "customer_address": "_Test Registered Customer-Billing", + "company_address": "_Test Registered Customer-Billing" + }, + "values": { + "distance": 100, + "mode_of_transport": "Road", + "vehicle_no": "GJ07DL9009", + "sub_supply_type": "Exhibition or Fairs" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "07/08/2023", + "docNo": "test_invoice_no", + "docType": "CHL", + "fromAddr1": "Test Address - 3", + "fromGstin": "05AAACG2115R1ZN", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Registered Customer", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 12, + "supplyType": "I", + "toAddr1": "Test Address - 3", + "toGstin": "05AAACG2115R1ZN", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Indian Registered Company", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 100, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "07/08/2023 12:34:00 PM", + "ewayBillNo": 321003382521, + "validUpto": "08/08/2023 11:59:00 PM" + }, + "success": true + } + }, + "is_return_dn_with_different_gstin": { + "kwargs": { + "qty": 1000 + }, + "values": { + "distance": 100, + "mode_of_transport": "Road", + "vehicle_no": "GJ07DL9009", + "sub_supply_type": "Job Work Returns" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "07/08/2023", + "docNo": "test_invoice_no", + "docType": "CHL", + "fromAddr1": "Test Address - 3", + "fromGstin": "05AAACG2140A1ZL", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Registered Customer", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 6, + "supplyType": "I", + "toAddr1": "Test Address - 1", + "toGstin": "05AAACG2115R1ZN", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Indian Registered Company", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 100, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "07/08/2023 12:18:00 PM", + "ewayBillNo": 361003382507, + "validUpto": "08/08/2023 11:59:00 PM" + }, + "success": true + } + }, + "dn_with_different_gstin": { + "kwargs": { + "qty": 1000 + }, + "values": { + "distance": 100, + "mode_of_transport": "Road", + "vehicle_no": "GJ07DL9009", + "sub_supply_type": "Job Work" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "07/08/2023", + "docNo": "test_invoice_no", + "docType": "CHL", + "fromAddr1": "Test Address - 1", + "fromGstin": "05AAACG2115R1ZN", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Indian Registered Company", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 4, + "supplyType": "O", + "toAddr1": "Test Address - 3", + "toGstin": "05AAACG2140A1ZL", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Registered Customer", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 100, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "07/08/2023 11:58:00 AM", + "ewayBillNo": 371003382498, + "validUpto": "08/08/2023 11:59:00 PM" + }, + "success": true + } + }, + "dn_with_same_gstin": { + "kwargs": { + "qty": 1000, + "company_address": "_Test Registered Customer-Billing" + }, + "values": { + "distance": 100, + "mode_of_transport": "Road", + "vehicle_no": "GJ07DL9009", + "sub_supply_type": "For Own Use" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "07/08/2023", + "docNo": "test_invoice_no", + "docType": "CHL", + "fromAddr1": "Test Address - 3", + "fromGstin": "05AAACG2115R1ZN", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Indian Registered Company", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 5, + "supplyType": "O", + "toAddr1": "Test Address - 3", + "toGstin": "05AAACG2115R1ZN", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Registered Customer", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 100, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ07DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "07/08/2023 12:24:00 PM", + "ewayBillNo": 301003382512, + "validUpto": "08/08/2023 11:59:00 PM" + }, + "success": true + } + }, + "pi_data_for_unregistered_supplier": { + "kwargs": { + "supplier": "_Test Unregistered Supplier", + "qty": 1000, + "distance": 10, + "mode_of_transport": "Road", + "vehicle_no": "GJ05DL9009" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "08/08/2023", + "docNo": "test_invoice_no", + "docType": "INV", + "fromAddr1": "Test Address - 10", + "fromGstin": "URP", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Unregistered Supplier", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 1, + "supplyType": "I", + "toAddr1": "Test Address - 1", + "toGstin": "05AAACG2115R1ZN", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Indian Registered Company", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 10, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ05DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "08/08/2023 03:28:00 PM", + "ewayBillNo": 371003383264, + "validUpto": "09/08/2023 11:59:00 PM" + }, + "success": true + } + }, + "pi_data_for_registered_supplier": { + "kwargs": { + "supplier": "_Test Registered Supplier", + "qty": 1000, + "distance": 10, + "mode_of_transport": "Road", + "vehicle_no": "GJ05DL9009" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "08/08/2023", + "docNo": "test_invoice_no", + "docType": "INV", + "fromAddr1": "Test Address - 7", + "fromGstin": "05AAACG2140A1ZL", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Registered Supplier", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyType": 1, + "supplyType": "I", + "toAddr1": "Test Address - 1", + "toGstin": "05AAACG2115R1ZN", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Indian Registered Company", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 10, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ05DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "08/08/2023 12:18:00 PM", + "ewayBillNo": 301003383151, + "validUpto": "09/08/2023 11:59:00 PM" + }, + "success": true + } + }, + "purchase_return_for_registered_supplier": { + "kwargs": { + "supplier": "_Test Registered Supplier", + "qty": 1000, + "distance": 10, + "mode_of_transport": "Road", + "vehicle_no": "GJ05DL9009" + }, + "request_data": { + "OthValue": 0.0, + "TotNonAdvolVal": 0, + "actFromStateCode": 24, + "actToStateCode": 24, + "cessValue": 0, + "cgstValue": 0, + "docDate": "08/08/2023", + "docNo": "test_invoice_no", + "docType": "OTH", + "fromAddr1": "Test Address - 1", + "fromGstin": "05AAACG2115R1ZN", + "fromPincode": 380015, + "fromPlace": "Test City", + "fromStateCode": 24, + "fromTrdName": "_Test Indian Registered Company", + "igstValue": 0, + "itemList": [ + { + "cessNonAdvol": 0, + "cessRate": 0, + "cgstRate": 0, + "hsnCode": "61149090", + "igstRate": 0, + "itemNo": 1, + "productDesc": "Test Trading Goods 1", + "qtyUnit": "NOS", + "quantity": 1000.0, + "sgstRate": 0, + "taxableAmount": 100000.0 + } + ], + "mainHsnCode": "61149090", + "sgstValue": 0, + "subSupplyDesc": "Purchase Return", + "subSupplyType": 8, + "supplyType": "O", + "toAddr1": "Test Address - 7", + "toGstin": "05AAACG2140A1ZL", + "toPincode": 380015, + "toPlace": "Test City", + "toStateCode": 24, + "toTrdName": "_Test Registered Supplier", + "totInvValue": 100000.0, + "totalValue": 100000.0, + "transDistance": 10, + "transMode": 1, + "transactionType": 1, + "userGstin": "05AAACG2115R1ZN", + "vehicleNo": "GJ05DL9009", + "vehicleType": "R" + }, + "params": "action=GENEWAYBILL", + "response_data": { + "message": "E-Way Bill is generated successfully", + "result": { + "alert": "", + "ewayBillDate": "08/08/2023 03:30:00 PM", + "ewayBillNo": 341003383265, + "validUpto": "09/08/2023 11:59:00 PM" + }, + "success": true + } } } diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 57d7ebc361..9f9d2822c6 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -1166,9 +1166,17 @@ def get_transaction_data(self): ("Delivery Note", 1): (OTHER_GSTIN, REGISTERED_GSTIN), } + if self.bill_from.gstin == self.bill_to.gstin: + sandbox_gstin.update( + { + ("Delivery Note", 0): (REGISTERED_GSTIN, REGISTERED_GSTIN), + ("Delivery Note", 1): (REGISTERED_GSTIN, REGISTERED_GSTIN), + } + ) + def _get_sandbox_gstin(address, key): - if address.gst_category == "Unregistered": - return "URP" + if address.gstin == "URP": + return address.gstin return sandbox_gstin.get((self.doc.doctype, self.doc.is_return))[key] diff --git a/india_compliance/gst_india/utils/test_e_waybill.py b/india_compliance/gst_india/utils/test_e_waybill.py index 157c3fe109..a12f88105a 100644 --- a/india_compliance/gst_india/utils/test_e_waybill.py +++ b/india_compliance/gst_india/utils/test_e_waybill.py @@ -26,6 +26,7 @@ from india_compliance.gst_india.utils.tests import ( _append_taxes, append_item, + create_purchase_invoice, create_sales_invoice, create_transaction, ) @@ -650,19 +651,183 @@ def test_invoice_update_after_submit(self): "You have already generated e-Waybill/e-Invoice for this document. This could result in mismatch of item details in e-Waybill/e-Invoice with print format.", ) + @change_settings("GST Settings", {"enable_e_waybill_from_dn": 1}) + @responses.activate + def test_e_waybill_for_dn_with_different_gstin(self): + """Test to generate e-waybill for Delivery Note with different GSTIN""" + dn_with_different_gstin_data = self.e_waybill_test_data.get( + "dn_with_different_gstin" + ) + different_gstin_dn = _create_delivery_note(dn_with_different_gstin_data) + + self._generate_e_waybill( + "Delivery Note", different_gstin_dn.name, dn_with_different_gstin_data + ) + + self.assertDocumentEqual( + { + "name": dn_with_different_gstin_data.get("response_data") + .get("result") + .get("ewayBillNo") + }, + frappe.get_doc( + "e-Waybill Log", {"reference_name": different_gstin_dn.name} + ), + ) + + # Return Note + is_return_dn_with_different_gstin_data = self.e_waybill_test_data.get( + "is_return_dn_with_different_gstin" + ) + + return_note = make_return_doc("Delivery Note", different_gstin_dn.name).submit() + + self._generate_e_waybill( + "Delivery Note", return_note.name, is_return_dn_with_different_gstin_data + ) + + self.assertDocumentEqual( + { + "name": is_return_dn_with_different_gstin_data.get("response_data") + .get("result") + .get("ewayBillNo") + }, + frappe.get_doc("e-Waybill Log", {"reference_name": return_note.name}), + ) + + @change_settings("GST Settings", {"enable_e_waybill_from_dn": 1}) + @responses.activate + def test_e_waybill_for_dn_with_same_gstin(self): + """Test to generate e-waybill for Delivery Note with Same GSTIN""" + dn_with_same_gstin_data = self.e_waybill_test_data.get("dn_with_same_gstin") + same_gstin_dn = _create_delivery_note(dn_with_same_gstin_data) + + self._generate_e_waybill( + "Delivery Note", same_gstin_dn.name, dn_with_same_gstin_data + ) + + self.assertDocumentEqual( + { + "name": dn_with_same_gstin_data.get("response_data") + .get("result") + .get("ewayBillNo") + }, + frappe.get_doc("e-Waybill Log", {"reference_name": same_gstin_dn.name}), + ) + + # Return Note + return_note = make_return_doc("Delivery Note", same_gstin_dn.name) + return_note.submit() + + is_return_dn_with_same_gstin_data = self.e_waybill_test_data.get( + "is_return_dn_with_same_gstin" + ) + + self._generate_e_waybill( + "Delivery Note", return_note.name, is_return_dn_with_same_gstin_data + ) + + self.assertDocumentEqual( + { + "name": is_return_dn_with_same_gstin_data.get("response_data") + .get("result") + .get("ewayBillNo") + }, + frappe.get_doc("e-Waybill Log", {"reference_name": return_note.name}), + ) + + @change_settings("GST Settings", {"enable_e_waybill_from_pi": 1}) + @responses.activate + def test_e_waybill_for_pi_with_unregistered_supplier(self): + purchase_invoice_data = self.e_waybill_test_data.get( + "pi_data_for_unregistered_supplier" + ) + purchase_invoice = create_purchase_invoice( + **purchase_invoice_data.get("kwargs") + ) + + self._generate_e_waybill( + "Purchase Invoice", purchase_invoice.name, purchase_invoice_data + ) + + self.assertDocumentEqual( + { + "name": purchase_invoice_data.get("response_data") + .get("result") + .get("ewayBillNo") + }, + frappe.get_doc("e-Waybill Log", {"reference_name": purchase_invoice.name}), + ) + + @change_settings("GST Settings", {"enable_e_waybill_from_pi": 1}) + @responses.activate + def test_e_waybill_for_registered_purchase(self): + purchase_invoice_data = self.e_waybill_test_data.get( + "pi_data_for_registered_supplier" + ) + + purchase_invoice = create_purchase_invoice( + **purchase_invoice_data.get("kwargs"), do_not_submit=True + ) + + # Bill No Validation + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(Bill No is mandatory.*)$"), + EWaybillData(purchase_invoice).validate_bill_no_for_purchase, + ) + + purchase_invoice.bill_no = "1234" + purchase_invoice.submit() + + # Test get_data + self.assertDictContainsSubset( + EWaybillData(purchase_invoice).get_data(), + purchase_invoice_data.get("request_data"), + ) + + self._generate_e_waybill( + "Purchase Invoice", purchase_invoice.name, purchase_invoice_data + ) + + # Return Note + return_note = make_return_doc( + "Purchase Invoice", purchase_invoice.name + ).submit() + + return_pi_data = self.e_waybill_test_data.get( + "purchase_return_for_registered_supplier" + ) + + self._generate_e_waybill("Purchase Invoice", return_note.name, return_pi_data) + + self.assertDocumentEqual( + { + "name": return_pi_data.get("response_data") + .get("result") + .get("ewayBillNo") + }, + frappe.get_doc("e-Waybill Log", {"reference_name": return_note.name}), + ) + # helper functions - def _generate_e_waybill(self): + def _generate_e_waybill( + self, doctype="Sales Invoice", docname=None, test_data=None + ): """Generate e-waybill""" + if not test_data: + test_data = self.e_waybill_test_data.goods_item_with_ewaybill + + if not docname and doctype == "Sales Invoice": + docname = self.sales_invoice.name + # Mock POST response for generate_e_waybill - e_waybill_with_goods_item = self.e_waybill_test_data.goods_item_with_ewaybill self._mock_e_waybill_response( - data=e_waybill_with_goods_item.get("response_data"), + data=test_data.get("response_data"), match_list=[ - matchers.query_string_matcher(e_waybill_with_goods_item.get("params")), - matchers.json_params_matcher( - e_waybill_with_goods_item.get("request_data") - ), + matchers.query_string_matcher(test_data.get("params")), + matchers.json_params_matcher(test_data.get("request_data")), ], ) @@ -680,11 +845,12 @@ def _generate_e_waybill(self): api="getewaybill", ) - generate_e_waybill( - doctype="Sales Invoice", - docname=self.sales_invoice.name, + values = ( + frappe._dict(test_data.get("values")) if test_data.get("values") else None ) + generate_e_waybill(doctype=doctype, docname=docname, values=values) + def _mock_e_waybill_response(self, data, match_list, method="POST", api=None): """Mock e-waybill response for given data and match_list""" base_api = "/test/ewb/ewayapi/" @@ -762,6 +928,12 @@ def _create_sales_invoice(test_data): return si +def _create_delivery_note(test_data): + test_data.get("kwargs").update({"doctype": "Delivery Note"}) + delivery_note = create_transaction(**test_data.get("kwargs")) + return delivery_note + + def _bulk_insert_hsn_wise_items(hsn_codes): frappe.db.bulk_insert( "Item", diff --git a/india_compliance/tests/test_records.json b/india_compliance/tests/test_records.json index 8dd73a3584..a1468d43da 100644 --- a/india_compliance/tests/test_records.json +++ b/india_compliance/tests/test_records.json @@ -369,6 +369,25 @@ } ] }, + { + "name": "_Test Unregistered Supplier-Billing", + "address_type": "Billing", + "address_line1": "Test Address - 10", + "city": "Test City", + "state": "Gujarat", + "pincode": "380015", + "country": "India", + "gstin": "", + "gst_category": "Unregistered", + "is_primary_address": 1, + "is_shipping_address": 1, + "links": [ + { + "link_doctype": "Supplier", + "link_name": "_Test Unregistered Supplier" + } + ] + }, { "name": "_Test Indian Registered Company-Shipping", "address_type": "Shipping", From 8c8ec8b6a3dea2cb09af4299b4fec0dcff69da74 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 8 Aug 2023 19:33:27 +0530 Subject: [PATCH 7/9] chore: add nosemgrep for commit (cherry picked from commit ece560c4737d05cd1a6ae91472021ac9ec4716ae) --- india_compliance/gst_india/utils/e_waybill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 9f9d2822c6..3626d8a082 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -580,7 +580,7 @@ def _log_and_process_e_waybill(doc, log_data, fetch=False, comment=None): if comment: log.add_comment(text=comment) - frappe.db.commit() + frappe.db.commit() # nosemgrep # before delete if log.is_cancelled: delete_file(doc, get_pdf_filename(log.name)) @@ -592,7 +592,7 @@ def _log_and_process_e_waybill(doc, log_data, fetch=False, comment=None): return _fetch_e_waybill_data(doc, log) - frappe.db.commit() + frappe.db.commit() # nosemgrep # after fetch ### Attach PDF From 87ea20b56c87f2a8466d1bbc1df748e3abc5abca Mon Sep 17 00:00:00 2001 From: Lakshit Jain <108322669+ljain112@users.noreply.github.com> Date: Tue, 8 Aug 2023 20:30:28 +0530 Subject: [PATCH 8/9] feat: validate supplier invoice no for purchase invoice (#938) Co-authored-by: Smit Vora (cherry picked from commit 5aaba9ac5674d93a5f29803c307a41ddd89f9206) --- .../doctype/gst_settings/gst_settings.json | 10 +++++++++- .../gst_india/overrides/purchase_invoice.py | 18 ++++++++++++++++++ india_compliance/gst_india/setup/__init__.py | 1 + .../gst_india/setup/property_setters.py | 6 ++++++ .../gst_india/utils/test_e_waybill.py | 2 ++ india_compliance/gst_india/utils/tests.py | 4 ++++ india_compliance/patches.txt | 2 +- 7 files changed, 41 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json index 17a86bd2c4..87b55607b7 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json @@ -10,6 +10,7 @@ "enable_reverse_charge_in_sales", "enable_overseas_transactions", "round_off_gst_values", + "require_supplier_invoice_no", "column_break_4", "validate_hsn_code", "min_hsn_digits", @@ -322,12 +323,19 @@ "fieldname": "enable_rcm_for_unregistered_supplier", "fieldtype": "Check", "label": "Enable Reverse Charge for Purchase from Unregistered Supplier" + }, + { + "default": "1", + "description": "If checked, Supplier Invoice Number will be mandatory for all purchases (except from unregistered suppliers). This will help ensure smooth purchase reconciliation.", + "fieldname": "require_supplier_invoice_no", + "fieldtype": "Check", + "label": "Require Supplier Invoice Number for GST Purchases" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-07-12 16:32:45.185690", + "modified": "2023-08-08 12:52:48.713020", "modified_by": "Administrator", "module": "GST India", "name": "GST Settings", diff --git a/india_compliance/gst_india/overrides/purchase_invoice.py b/india_compliance/gst_india/overrides/purchase_invoice.py index f056c9e47c..069679fc4e 100644 --- a/india_compliance/gst_india/overrides/purchase_invoice.py +++ b/india_compliance/gst_india/overrides/purchase_invoice.py @@ -1,4 +1,5 @@ import frappe +from frappe import _ from frappe.utils import flt from india_compliance.gst_india.overrides.sales_invoice import ( @@ -43,6 +44,7 @@ def validate(doc, method=None): return update_itc_totals(doc) + validate_supplier_invoice_number(doc) def update_itc_totals(doc, method=None): @@ -68,6 +70,22 @@ def update_itc_totals(doc, method=None): doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) +def validate_supplier_invoice_number(doc): + if ( + doc.bill_no + or doc.gst_category == "Unregistered" + or not frappe.get_cached_value( + "GST Settings", "GST Settings", "require_supplier_invoice_no" + ) + ): + return + + frappe.throw( + _("As per your GST Settings, Bill No is mandatory for Purchase Invoice."), + title=_("Missing Mandatory Field"), + ) + + def get_dashboard_data(data): transactions = data.setdefault("transactions", []) reference_section = next( diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index e55dceba3e..5de86900f4 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -145,6 +145,7 @@ def set_default_gst_settings(): default_settings = { "hsn_wise_tax_breakup": 1, "enable_reverse_charge_in_sales": 0, + "require_supplier_invoice_no": 1, "validate_hsn_code": 1, "min_hsn_digits": 6, "enable_e_waybill": 1, diff --git a/india_compliance/gst_india/setup/property_setters.py b/india_compliance/gst_india/setup/property_setters.py index 3887ecd729..cf8c0aae3d 100644 --- a/india_compliance/gst_india/setup/property_setters.py +++ b/india_compliance/gst_india/setup/property_setters.py @@ -30,6 +30,12 @@ def get_property_setters(): ["Bill of Entry"], prepend=False, ), + { + "doctype": "Purchase Invoice", + "fieldname": "bill_no", + "property": "mandatory_depends_on", + "value": "eval: doc.gst_category !== 'Unregistered' && gst_settings.require_supplier_invoice_no === 1 && doc.company_gstin", + }, { "doctype": "Address", "fieldname": "state", diff --git a/india_compliance/gst_india/utils/test_e_waybill.py b/india_compliance/gst_india/utils/test_e_waybill.py index a12f88105a..8fab7b2c3d 100644 --- a/india_compliance/gst_india/utils/test_e_waybill.py +++ b/india_compliance/gst_india/utils/test_e_waybill.py @@ -770,6 +770,8 @@ def test_e_waybill_for_registered_purchase(self): **purchase_invoice_data.get("kwargs"), do_not_submit=True ) + purchase_invoice.bill_no = "" + # Bill No Validation self.assertRaisesRegex( frappe.exceptions.ValidationError, diff --git a/india_compliance/gst_india/utils/tests.py b/india_compliance/gst_india/utils/tests.py index a4247eae48..b6d9a75266 100644 --- a/india_compliance/gst_india/utils/tests.py +++ b/india_compliance/gst_india/utils/tests.py @@ -11,6 +11,10 @@ def create_sales_invoice(**data): def create_purchase_invoice(**data): data["doctype"] = "Purchase Invoice" + + if "bill_no" not in data: + data["bill_no"] = frappe.generate_hash(length=5) + return create_transaction(**data) diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index e5f97d0e85..130d4a89bd 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -4,7 +4,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() #22 -execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #2 +execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #3 india_compliance.patches.post_install.remove_old_fields india_compliance.patches.post_install.update_company_gstin india_compliance.patches.post_install.update_custom_role_for_e_invoice_summary From f8e8c7f48f7d1235e2c2f8441c745b3d09da980b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 8 Aug 2023 20:45:26 +0530 Subject: [PATCH 9/9] test: fix test cases for mandatory bill no (cherry picked from commit a432c4a34eca6310f3e5a0641f33ce43aaa13ff8) --- .../gst_india/doctype/bill_of_entry/test_bill_of_entry.py | 8 ++------ india_compliance/gst_india/overrides/test_transaction.py | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py b/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py index 2fb013dfb4..cc722f0615 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py +++ b/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py @@ -10,7 +10,7 @@ make_journal_entry_for_payment, make_landed_cost_voucher, ) -from india_compliance.gst_india.utils.tests import create_transaction +from india_compliance.gst_india.utils.tests import create_purchase_invoice class TestBillofEntry(FrappeTestCase): @@ -20,11 +20,7 @@ def setUpClass(cls): frappe.db.set_single_value("GST Settings", "enable_overseas_transactions", 1) def test_create_bill_of_entry(self): - pi = create_transaction( - doctype="Purchase Invoice", - supplier="_Test Foreign Supplier", - update_stock=1, - ) + pi = create_purchase_invoice(supplier="_Test Foreign Supplier", update_stock=1) # Create BOE boe = make_bill_of_entry(pi.name) diff --git a/india_compliance/gst_india/overrides/test_transaction.py b/india_compliance/gst_india/overrides/test_transaction.py index 4fd8b4ca9f..02a9d5bea1 100644 --- a/india_compliance/gst_india/overrides/test_transaction.py +++ b/india_compliance/gst_india/overrides/test_transaction.py @@ -47,6 +47,9 @@ def setUp(cls): # Hack. Avoid failing validations as quotation does not have customer field cls.transaction_details.customer = "_Test Registered Customer" + if cls.doctype == "Purchase Invoice": + cls.transaction_details.bill_no = frappe.generate_hash(length=5) + def test_transaction_for_unregistered_company(self): if self.is_sales_doctype: self.transaction_details.customer = "_Test Registered Customer"