feat: complete e-invoice schema

This commit is contained in:
Saqib Ansari
2020-09-29 16:25:08 +05:30
parent d971917ecf
commit 418818f0ec
5 changed files with 205 additions and 92 deletions

View File

@@ -6,6 +6,7 @@
"Barcde": "{item.barcode}",
"Unit": "{item.uom}",
"Qty": "{item.qty}",
"FreeQty": "{item.free_qty}",
"UnitPrice": "{item.base_rate}",
"TotAmt": "{item.base_amount}",
"Discount": "{item.discount_amount}",

View File

@@ -3,7 +3,9 @@
"TranDtls": {{
"TaxSch": "{trans_details.tax_scheme}",
"SupTyp": "{trans_details.supply_type}",
"RegRev": "{trans_details.reverse_charge}"
"RegRev": "{trans_details.reverse_charge}",
"EcmGstin": "{trans_details.ecom_gstin}",
"IgstOnIntra": "{trans_details.igst_on_intra}"
}},
"DocDtls": {{
"Typ": "{doc_details.invoice_type}",
@@ -35,6 +37,24 @@
"Em": "{buyer_details.email}",
"Pos": "{buyer_details.place_of_supply}"
}},
"DispDtls": {{
"Nm": "{dispatch_details.company_name}",
"Addr1": "{dispatch_details.address_line1}",
"Addr2": "{dispatch_details.address_line2}",
"Loc": "{dispatch_details.location}",
"Pin": "{dispatch_details.pincode}",
"Stcd": "{dispatch_details.state_code}"
}},
"ShipDtls": {{
"Gstin": "{shipping_details.gstin}",
"LglNm": "{shipping_details.legal_name}",
"TrdNm": "{shipping_details.trader_name}",
"Addr1": "{shipping_details.address_line1}",
"Addr2": "{shipping_details.address_line2}",
"Loc": "{shipping_details.location}",
"Pin": "{shipping_details.pincode}",
"Stcd": "{shipping_details.state_code}"
}},
"ItemList": [
{item_list}
],
@@ -48,5 +68,42 @@
"RndOffAmt": "{value_details.round_off}",
"TotInvVal": "{value_details.base_grand_total}",
"TotInvValFc": "{value_details.grand_total}"
}},
"PayDtls": {{
"Nm": "{payment_details.payee_name}",
"AccDet": "{payment_details.account_no}",
"Mode": "{payment_details.mode_of_payment}",
"FinInsBr": "{payment_details.ifsc_code}",
"PayTerm": "{payment_details.terms}",
"PaidAmt": "{payment_details.paid_amount}",
"PaymtDue": "{payment_details.outstanding_amount}"
}},
"RefDtls": {{
"DocPerdDtls": {{
"InvStDt": "{period_details.start_date}",
"InvEndDt": "{period_details.end_date}"
}},
"PrecDocDtls": {{
"InvNo": "{prev_doc_details.invoice_name}",
"InvDt": "{prev_doc_details.invoice_date}"
}}
}},
"ExpDtls": {{
"ShipBNo": "{export_details.bill_no}",
"ShipBDt": "{export_details.bill_date}",
"Port": "{export_details.port}",
"ForCur": "{export_details.foreign_curr_code}",
"CntCode": "{export_details.country_code}",
"ExpDuty": "{export_details.export_duty}"
}},
"EwbDtls": {{
"TransId": "{eway_bill_details.gstin}",
"TransName": "{eway_bill_details.name}",
"TransMode": "{eway_bill_details.mode_of_transport}",
"Distance": "{eway_bill_details.distance}",
"TransDocNo": "{eway_bill_details.document_name}",
"TransDocDt": "{eway_bill_details.document_date}",
"VehNo": "{eway_bill_details.vehicle_no}",
"VehType": "{eway_bill_details.vehicle_type}"
}}
}}

View File

@@ -621,90 +621,80 @@
"required": ["InvStDt", "InvEndDt"]
},
"PrecDocDtls": {
"type": "Array",
"PrecDocument": [
{
"type": "object",
"properties": {
"InvNo": {
"type": "string",
"minLength": 1,
"maxLength": 16,
"pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$"
},
"InvDt": {
"type": "string",
"maxLength": 10,
"minLength": 10,
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
},
"OthRefNo": {
"type": "string",
"minLength": 1,
"maxLength": 20
}
}
"type": "object",
"properties": {
"InvNo": {
"type": "string",
"minLength": 1,
"maxLength": 16,
"pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$"
},
"InvDt": {
"type": "string",
"maxLength": 10,
"minLength": 10,
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
},
"OthRefNo": {
"type": "string",
"minLength": 1,
"maxLength": 20
}
],
},
"required": ["InvNo", "InvDt"]
},
"ContrDtls": {
"type": "Array",
"Contract": [
{
"type": "object",
"properties": {
"RecAdvRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"RecAdvDt": {
"type": "string",
"minLength": 10,
"maxLength": 10,
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
},
"TendRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"ContrRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"ExtRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"ProjRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"PORefr": {
"type": "string",
"minLength": 1,
"maxLength": 16,
"pattern": "^([0-9A-Za-z/-]){1,16}$"
},
"PORefDt": {
"type": "string",
"minLength": 10,
"maxLength": 10,
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
}
}
"type": "object",
"properties": {
"RecAdvRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"RecAdvDt": {
"type": "string",
"minLength": 10,
"maxLength": 10,
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
},
"TendRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"ContrRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"ExtRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"ProjRefr": {
"type": "string",
"minLength": 1,
"maxLength": 20,
"pattern": "^([0-9A-Za-z/-]){1,20}$"
},
"PORefr": {
"type": "string",
"minLength": 1,
"maxLength": 16,
"pattern": "^([0-9A-Za-z/-]){1,16}$"
},
"PORefDt": {
"type": "string",
"minLength": 10,
"maxLength": 10,
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
}
]
}
}
},
"required": ["InvStDt", "InvEndDt"]

View File

@@ -4,7 +4,7 @@
frappe.ui.form.on('E Invoice Settings', {
refresh: function(frm) {
if (!frm.doc.enable) return;
frm.trigger("show_fetch_token_btn");
frm.add_custom_button(__("Get GSTIN Details"),
() => {
@@ -25,7 +25,7 @@ frappe.ui.form.on('E Invoice Settings', {
doc: frm.doc,
method: 'generate_irn',
args: {
'invoice': 'SINV-20-21-0044'
'invoice': 'SINV-20-21-0051'
},
freeze: true,
callback: (res) => console.log(res)

View File

@@ -129,13 +129,17 @@ class EInvoiceSettings(Document):
invoice = frappe.get_doc("Sales Invoice", invoice)
e_invoice = self.make_e_invoice(invoice)
enc_e_invoice_json = self.aes_encrypt(e_invoice, self.sek)
payload = dict(Data=enc_e_invoice_json)
res = make_post_request(endpoint, headers=headers, data=json.dumps(payload))
self.handle_err_response(res)
data = json.loads(res)
enc_json = res.get('Data')
json_str = self.aes_decrypt(enc_json, self.sek)
data = json.loads(json_str)
self.handle_irn_response(data)
return data
@@ -178,8 +182,12 @@ class EInvoiceSettings(Document):
def handle_err_response(self, response):
if response.get('Status') == 0:
err_details = response.get('ErrorDetails')
print(response)
err_msg = response.get('ErrorDetails')[0].get('ErrorMessage')
err_msg = ""
for d in err_details:
err_msg += d.get('ErrorMessage')
err_msg += "<br>"
frappe.throw(_(err_msg), title=_('API Request Failed'))
def read_json(self, name):
@@ -220,6 +228,8 @@ class EInvoiceSettings(Document):
location = gstin_details.get('AddrLoc')
state_code = gstin_details.get('StateCode')
pincode = cint(gstin_details.get('AddrPncd'))
if state_code == 97:
pincode = 999999
return frappe._dict(dict(
gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location,
@@ -227,6 +237,17 @@ class EInvoiceSettings(Document):
address_line2=address_line2, email=email_id, phone=phone
))
def get_overseas_address_details(self, party_address):
address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
"Address", party_address, ["address_title", "address_line1", "address_line2", "city", "phone", "email_id"]
)
return frappe._dict(dict(
gstin='URP', legal_name=address_title, address_line1=address_line1,
address_line2=address_line2, email=email_id, phone=phone,
pincode=999999, state_code=96, place_of_supply=96, location=city
))
def get_item_list(self, invoice):
item_list = []
gst_accounts = get_gst_accounts(invoice.company)
@@ -295,6 +316,23 @@ class EInvoiceSettings(Document):
value_details.total_cgst_amt += t.base_tax_amount
return value_details
def get_payment_details(self, invoice):
payee_name = invoice.company
mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments])
paid_amount = invoice.base_paid_amount
outstanding_amount = invoice.outstanding_amount
return frappe._dict(dict(
payee_name=payee_name, mode_of_payment=mode_of_payment,
paid_amount=paid_amount, outstanding_amount=outstanding_amount
))
def get_return_doc_reference(self, invoice):
invoice_date = frappe.db.get_value("Sales Invoice", invoice.return_against, "posting_date")
return frappe._dict(dict(
invoice_name=invoice.return_against, invoice_date=invoice_date
))
def make_e_invoice(self, invoice):
schema = self.read_json("e_inv_schema")
@@ -304,17 +342,39 @@ class EInvoiceSettings(Document):
trans_details = self.get_trans_details(invoice)
doc_details = self.get_doc_details(invoice)
seller_details = self.get_party_gstin_details(invoice.company_address)
buyer_details = self.get_party_gstin_details(invoice.customer_address)
place_of_supply = invoice.place_of_supply.split('-')[0]
buyer_details.update(dict(place_of_supply=place_of_supply))
if invoice.gst_category == 'Overseas':
buyer_details = self.get_overseas_address_details(invoice.customer_address)
else:
buyer_details = self.get_party_gstin_details(invoice.customer_address)
place_of_supply = invoice.place_of_supply.split('-')[0]
buyer_details.update(dict(place_of_supply=place_of_supply))
item_list = self.get_item_list(invoice)
value_details = self.get_value_details(invoice)
dispatch_details = frappe._dict({})
period_details = frappe._dict({})
shipping_details = frappe._dict({})
export_details = frappe._dict({})
eway_bill_details = frappe._dict({})
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
shipping_details = self.get_party_gstin_details(invoice.shipping_address_name)
payment_details = frappe._dict({})
if invoice.is_pos and invoice.base_paid_amount:
payment_details = self.get_payment_details(invoice)
prev_doc_details = frappe._dict({})
if invoice.is_return and invoice.return_against:
prev_doc_details = self.get_return_doc_reference(invoice)
e_invoice = schema.format(
trans_details=trans_details, doc_details=doc_details,
seller_details=seller_details, buyer_details=buyer_details,
item_list=item_list, value_details=value_details
trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details,
seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
item_list=item_list, value_details=value_details, payment_details=payment_details,
period_details=period_details, prev_doc_details=prev_doc_details,
export_details=export_details, eway_bill_details=eway_bill_details
)
e_invoice = json.loads(e_invoice)
@@ -339,7 +399,7 @@ class EInvoiceSettings(Document):
invoice_value = e_invoice.get(field)
if not invoice_value:
print(field, "Value undefined")
print(field, "value undefined")
continue
should_be_of_type = type_map[value.get('type').lower()]
@@ -351,9 +411,14 @@ class EInvoiceSettings(Document):
self.run_e_invoice_validations(properties, d)
else:
self.run_e_invoice_validations(properties, invoice_value)
if not invoice_value:
e_invoice.pop(field, None)
continue
e_invoice[field] = None if invoice_value == "None" else invoice_value
if invoice_value == "None":
e_invoice.pop(field, None)
continue
e_invoice[field] = should_be_of_type(invoice_value) if e_invoice[field] else e_invoice[field]
should_be_of_len = value.get('maxLength')