feat: QRCode Image and E-Invoice Print Format

This commit is contained in:
Saqib Ansari
2020-10-10 19:43:25 +05:30
committed by Saurabh
parent e0ea4265a8
commit 4e12466bef
7 changed files with 246 additions and 54 deletions

View File

@@ -0,0 +1,147 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
<div class="page-break">
<div id="header-html" class="hidden-pdf">
<div class="row print-heading">
<h2>E-Invoice</h2>
</div>
</div>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd;">
<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
<div class="col-xs-8 column-break">
<div class="row data-field">
<div class="col-xs-4"><label>IRN</label></div>
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Ack. No</label></div>
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Ack. Date</label></div>
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Category</label></div>
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document Type</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
</div>
<div class="row data-field">
<div class="col-xs-4"><label>Document No</label></div>
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
</div>
</div>
<div class="col-xs-4 column-break">
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
</div>
</div>
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
{%- set seller = einvoice.SellerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Seller</h5>
<p>{{ seller.Gstin }}</p>
<p>{{ seller.LglNm }}</p>
<p>{{ seller.Addr1 }}</p>
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
<p>{{ seller.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
{%- if einvoice.ShipDtls -%}
{%- set shipping = einvoice.ShipDtls -%}
<h5 style="margin-bottom: 5px;">Shipping</h5>
<p>{{ shipping.Gstin }}</p>
<p>{{ shipping.LglNm }}</p>
<p>{{ shipping.Addr1 }}</p>
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
<p>{{ shipping.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
{% endif %}
</div>
{%- set buyer = einvoice.BuyerDtls -%}
<div class="col-xs-6 column-break">
<h5 style="margin-bottom: 5px;">Buyer</h5>
<p>{{ buyer.Gstin }}</p>
<p>{{ buyer.LglNm }}</p>
<p>{{ buyer.Addr1 }}</p>
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
<p>{{ buyer.Loc }}</p>
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
</div>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-left" style="width: 3%;">Sr. No.</th>
<th class="text-left">Item</th>
<th class="text-left" style="width: 10%;">HSN Code</th>
<th class="text-left" style="width: 5%;">Qty</th>
<th class="text-left" style="width: 5%;">UOM</th>
<th class="text-left">Rate</th>
<th class="text-left" style="width: 5%;">Discount</th>
<th class="text-left">Taxable Amount</th>
<th class="text-left" style="width: 7%;">Tax Rate</th>
<th class="text-left" style="width: 5%;">Other Charges</th>
<th class="text-left">Total</th>
</tr>
</thead>
<tbody>
<tr>
{% for item in einvoice.ItemList %}
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
<td class="text-left">{{ item.PrdDesc }}</td>
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
<div style="overflow-x: auto;">
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
<table class="table table-bordered">
<thead>
<tr>
<th class="text-left">Taxable Amount</th>
<th class="text-left">CGST</th>
<th class="text-left"">SGST</th>
<th class="text-left">IGST</th>
<th class="text-left">CESS</th>
<th class="text-left" style="width: 10%;">State CESS</th>
<th class="text-left">Discount</th>
<th class="text-left" style="width: 10%;">Other Charges</th>
<th class="text-left" style="width: 10%;">Round Off</th>
<th class="text-left">Total Value</th>
</tr>
</thead>
<tbody>
{%- set value_details = einvoice.ValDtls -%}
<tr>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,24 @@
{
"align_labels_right": 1,
"creation": "2020-10-10 18:01:21.032914",
"custom_format": 0,
"default_print_language": "en-US",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "",
"idx": 0,
"line_breaks": 1,
"modified": "2020-10-10 18:01:21.032914",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST E-Invoice",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@@ -10,6 +10,7 @@ import json
import base64 import base64
import frappe import frappe
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from pyqrcode import create as qrcreate
from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Util.Padding import pad, unpad from Crypto.Util.Padding import pad, unpad
from frappe.model.document import Document from frappe.model.document import Document
@@ -116,12 +117,12 @@ def extract_token_and_sek(response, appkey):
sek = aes_decrypt(enc_sek, appkey) sek = aes_decrypt(enc_sek, appkey)
return auth_token, token_expiry, sek return auth_token, token_expiry, sek
def attach_signed_json(invoice, data): def attach_signed_invoice(doctype, name, data):
f = frappe.get_doc({ f = frappe.get_doc({
"doctype": "File", "doctype": "File",
"file_name": invoice.name + "e_invoice.json", "file_name": name + "e_invoice.json",
"attached_to_doctype": invoice.doctype, "attached_to_doctype": doctype,
"attached_to_name": invoice.name, "attached_to_name": name,
"content": json.dumps(data), "content": json.dumps(data),
"is_private": True "is_private": True
}).insert() }).insert()
@@ -147,8 +148,7 @@ def generate_irn(doctype, name):
einv_creds = get_einv_credentials() einv_creds = get_einv_credentials()
headers = get_header(einv_creds) headers = get_header(einv_creds)
invoice = frappe.get_doc(doctype, name) e_invoice = make_e_invoice(doctype, name)
e_invoice = make_e_invoice(invoice)
enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek) enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek)
payload = dict(Data=enc_e_invoice_json) payload = dict(Data=enc_e_invoice_json)
@@ -159,12 +159,15 @@ def generate_irn(doctype, name):
enc_json = res.get('Data') enc_json = res.get('Data')
json_str = aes_decrypt(enc_json, einv_creds.sek) json_str = aes_decrypt(enc_json, einv_creds.sek)
data = json.loads(json_str) signed_einvoice = json.loads(json_str)
handle_irn_response(data) handle_irn_response(signed_einvoice)
attach_signed_json(invoice, data['DecryptedSignedInvoice']) update_einvoice_fields(doctype, name, signed_einvoice)
return data attach_qrcode_image(doctype, name)
attach_signed_invoice(doctype, name, signed_einvoice['DecryptedSignedInvoice'])
return signed_einvoice
def get_irn_details(irn): def get_irn_details(irn):
einv_creds = get_einv_credentials() einv_creds = get_einv_credentials()
@@ -175,16 +178,10 @@ def get_irn_details(irn):
res = make_get_request(endpoint, headers=headers) res = make_get_request(endpoint, headers=headers)
handle_err_response(res) handle_err_response(res)
# enc_json = res.get('Data')
# json_str = aes_decrypt(enc_json, einv_creds.sek)
# data = json.loads(json_str)
# handle_irn_response(data)
return res return res
@frappe.whitelist() @frappe.whitelist()
def cancel_irn(irn, reason, remark=''): def cancel_irn(doctype, name, irn, reason, remark=''):
einv_creds = get_einv_credentials() einv_creds = get_einv_credentials()
endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel'
@@ -197,6 +194,8 @@ def cancel_irn(irn, reason, remark=''):
res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) res = make_post_request(endpoint, headers=headers, data=json.dumps(payload))
handle_err_response(res) handle_err_response(res)
frappe.db.set_value(doctype, name, 'irn_cancelled', 1)
return res return res
@frappe.whitelist() @frappe.whitelist()
@@ -277,15 +276,13 @@ def get_party_gstin_details(party_address):
gstin = address.get('gstin') gstin = address.get('gstin')
gstin_details = get_gstin_details(gstin) gstin_details = get_gstin_details(gstin)
# legal_name = address.get('address_title')
legal_name = gstin_details.get('LegalName') legal_name = gstin_details.get('LegalName')
trade_name = gstin_details.get('TradeName') trade_name = gstin_details.get('TradeName')
# location = address.get('city') location = gstin_details.get('AddrLoc')
location = gstin_details.get('Loc') state_code = gstin_details.get('StateCode')
state_code = address.get('gst_state_number') pincode = cint(gstin_details.get('AddrPncd'))
pincode = cint(address.get('pincode')) address_line1 = "{} {}".format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
address_line1 = address.get('address_line1') address_line2 = "{} {}".format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
address_line2 = address.get('address_line2')
email_id = address.get('email_id') email_id = address.get('email_id')
phone = address.get('phone') phone = address.get('phone')
if state_code == 97: if state_code == 97:
@@ -527,6 +524,15 @@ def validate_einvoice(validations, e_invoice, error_msgs=[]):
return error_msgs return error_msgs
def update_einvoice_fields(doctype, name, signed_einvoice):
enc_signed_invoice = signed_einvoice.get('SignedInvoice')
decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data']
frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn'))
frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo'))
frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode').split('.')[1])
frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice)
@frappe.whitelist() @frappe.whitelist()
def download_einvoice(): def download_einvoice():
data = frappe._dict(frappe.local.form_dict) data = frappe._dict(frappe.local.form_dict)
@@ -545,13 +551,7 @@ def upload_einvoice():
doctype = data['doctype'] doctype = data['doctype']
name = data['docname'] name = data['docname']
enc_signed_invoice = signed_einvoice.get('SignedInvoice') update_einvoice_fields(doctype, name, signed_einvoice)
decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data']
frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn'))
frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo'))
frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode'))
frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice)
@frappe.whitelist() @frappe.whitelist()
def download_cancel_einvoice(): def download_cancel_einvoice():
@@ -576,3 +576,24 @@ def upload_cancel_ack():
name = data['docname'] name = data['docname']
frappe.db.set_value(doctype, name, "irn_cancelled", 1) frappe.db.set_value(doctype, name, "irn_cancelled", 1)
def attach_qrcode_image(doctype, name):
qrcode = frappe.db.get_value(doctype, name, 'signed_qr_code')
if not qrcode: return
_file = frappe.get_doc({
"doctype": "File",
"file_name": "Signed_QR_{name}.png".format(name=name),
"attached_to_doctype": doctype,
"attached_to_name": name,
"attached_to_field": "qrcode_image",
"content": "qrcode"
})
_file.save()
frappe.db.commit()
url = qrcreate(qrcode)
abs_file_path = os.path.abspath(_file.get_full_path())
url.png(abs_file_path, scale=2)
frappe.db.set_value(doctype, name, 'qrcode_image', _file.file_url)

View File

@@ -1,5 +1,5 @@
{{ {{
"Version": "1.01", "Version": "1.1",
"TranDtls": {{ "TranDtls": {{
"TaxSch": "{trans_details.tax_scheme}", "TaxSch": "{trans_details.tax_scheme}",
"SupTyp": "{trans_details.supply_type}", "SupTyp": "{trans_details.supply_type}",

View File

@@ -15,15 +15,7 @@ erpnext.setup_einvoice_actions = (doctype) => {
// method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn',
// args: { doctype: frm.doc.doctype, name: frm.doc.name }, // args: { doctype: frm.doc.doctype, name: frm.doc.name },
// freeze: true, // freeze: true,
// callback: (res) => { // callback: () => frm.reload_doc()
// console.log(res.message);
// frm.set_value('irn', res.message['Irn']);
// frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice']));
// frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode']));
// if (res.message['EwbNo']) frm.set_value('ewaybill', res.message['EwbNo']);
// frm.save();
// }
// }) // })
// } // }
// ) // )
@@ -44,13 +36,15 @@ erpnext.setup_einvoice_actions = (doctype) => {
// const data = d.get_values(); // const data = d.get_values();
// frappe.call({ // frappe.call({
// method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn',
// args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, // args: {
// freeze: true, // doctype: frm.doc.doctype,
// callback: () => { // name: frm.doc.name,
// frm.set_value('irn_cancelled', 1); // irn: frm.doc.irn,
// frm.save("Update"); // reason: data.reason.split('-')[0],
// d.hide() // remark: data.remark
// }, // },
// freeze: true,
// callback: () => frm.reload_doc() || d.hide(),
// error: () => d.hide() // error: () => d.hide()
// }) // })
// }, // },

View File

@@ -377,14 +377,20 @@ def make_custom_fields(update=True):
] ]
si_einvoice_fields = [ si_einvoice_fields = [
dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
depends_on='eval:in_list(["Register Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1,
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1),
dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1), dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
] ]
custom_fields = { custom_fields = {