feat: QRCode Image and E-Invoice Print Format

This commit is contained in:
Saqib Ansari
2020-10-10 19:43:25 +05:30
parent d3a6b9ed81
commit 154c433a86
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 frappe
from Crypto.PublicKey import RSA
from pyqrcode import create as qrcreate
from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Util.Padding import pad, unpad
from frappe.model.document import Document
@@ -116,12 +117,12 @@ def extract_token_and_sek(response, appkey):
sek = aes_decrypt(enc_sek, appkey)
return auth_token, token_expiry, sek
def attach_signed_json(invoice, data):
def attach_signed_invoice(doctype, name, data):
f = frappe.get_doc({
"doctype": "File",
"file_name": invoice.name + "e_invoice.json",
"attached_to_doctype": invoice.doctype,
"attached_to_name": invoice.name,
"file_name": name + "e_invoice.json",
"attached_to_doctype": doctype,
"attached_to_name": name,
"content": json.dumps(data),
"is_private": True
}).insert()
@@ -147,8 +148,7 @@ def generate_irn(doctype, name):
einv_creds = get_einv_credentials()
headers = get_header(einv_creds)
invoice = frappe.get_doc(doctype, name)
e_invoice = make_e_invoice(invoice)
e_invoice = make_e_invoice(doctype, name)
enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek)
payload = dict(Data=enc_e_invoice_json)
@@ -159,12 +159,15 @@ def generate_irn(doctype, name):
enc_json = res.get('Data')
json_str = aes_decrypt(enc_json, einv_creds.sek)
data = json.loads(json_str)
handle_irn_response(data)
signed_einvoice = json.loads(json_str)
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):
einv_creds = get_einv_credentials()
@@ -175,16 +178,10 @@ def get_irn_details(irn):
res = make_get_request(endpoint, headers=headers)
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
@frappe.whitelist()
def cancel_irn(irn, reason, remark=''):
def cancel_irn(doctype, name, irn, reason, remark=''):
einv_creds = get_einv_credentials()
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))
handle_err_response(res)
frappe.db.set_value(doctype, name, 'irn_cancelled', 1)
return res
@frappe.whitelist()
@@ -277,15 +276,13 @@ def get_party_gstin_details(party_address):
gstin = address.get('gstin')
gstin_details = get_gstin_details(gstin)
# legal_name = address.get('address_title')
legal_name = gstin_details.get('LegalName')
trade_name = gstin_details.get('TradeName')
# location = address.get('city')
location = gstin_details.get('Loc')
state_code = address.get('gst_state_number')
pincode = cint(address.get('pincode'))
address_line1 = address.get('address_line1')
address_line2 = address.get('address_line2')
location = gstin_details.get('AddrLoc')
state_code = gstin_details.get('StateCode')
pincode = cint(gstin_details.get('AddrPncd'))
address_line1 = "{} {}".format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
address_line2 = "{} {}".format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
email_id = address.get('email_id')
phone = address.get('phone')
if state_code == 97:
@@ -527,6 +524,15 @@ def validate_einvoice(validations, e_invoice, 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()
def download_einvoice():
data = frappe._dict(frappe.local.form_dict)
@@ -545,13 +551,7 @@ def upload_einvoice():
doctype = data['doctype']
name = data['docname']
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'))
frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice)
update_einvoice_fields(doctype, name, signed_einvoice)
@frappe.whitelist()
def download_cancel_einvoice():
@@ -575,4 +575,25 @@ def upload_cancel_ack():
doctype = data['doctype']
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": {{
"TaxSch": "{trans_details.tax_scheme}",
"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',
// args: { doctype: frm.doc.doctype, name: frm.doc.name },
// freeze: true,
// callback: (res) => {
// 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();
// }
// callback: () => frm.reload_doc()
// })
// }
// )
@@ -44,13 +36,15 @@ erpnext.setup_einvoice_actions = (doctype) => {
// const data = d.get_values();
// frappe.call({
// method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn',
// args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark },
// freeze: true,
// callback: () => {
// frm.set_value('irn_cancelled', 1);
// frm.save("Update");
// d.hide()
// args: {
// doctype: frm.doc.doctype,
// name: frm.doc.name,
// irn: frm.doc.irn,
// reason: data.reason.split('-')[0],
// remark: data.remark
// },
// freeze: true,
// callback: () => frm.reload_doc() || d.hide(),
// error: () => d.hide()
// })
// },

View File

@@ -377,14 +377,20 @@ def make_custom_fields(update=True):
]
si_einvoice_fields = [
dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1,
depends_on='eval:in_list(["Register 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', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
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, print_hide=1,
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,
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
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 = {