mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-12 14:58:24 +00:00
feat: QRCode Image and E-Invoice Print Format
This commit is contained in:
147
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
147
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal 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>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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)
|
||||
@@ -1,5 +1,5 @@
|
||||
{{
|
||||
"Version": "1.01",
|
||||
"Version": "1.1",
|
||||
"TranDtls": {{
|
||||
"TaxSch": "{trans_details.tax_scheme}",
|
||||
"SupTyp": "{trans_details.supply_type}",
|
||||
|
||||
@@ -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()
|
||||
// })
|
||||
// },
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user