mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-25 09:38:31 +00:00
feat: complete e-invoice schema
This commit is contained in:
@@ -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}",
|
||||
|
||||
@@ -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}"
|
||||
}}
|
||||
}}
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user