Skip to content

Commit

Permalink
feat: make purchase invoice from irn
Browse files Browse the repository at this point in the history
  • Loading branch information
Sanket322 committed Sep 23, 2024
1 parent 589b19c commit a7da87f
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 2 deletions.
46 changes: 45 additions & 1 deletion india_compliance/gst_india/client_scripts/purchase_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@ frappe.ui.form.on(DOCTYPE, {
});
},

onload: toggle_reverse_charge,
onload: function(frm) {
toggle_reverse_charge(frm);

if (frm.is_new()) {
frm.add_custom_button(
__("Create Purchase Invoice"),
() => get_irn_dialog(frm),
);
}
},

gst_category(frm) {
validate_gst_hsn_code(frm);
Expand Down Expand Up @@ -103,6 +112,41 @@ frappe.ui.form.on("Purchase Invoice Item", {
gst_hsn_code: validate_gst_hsn_code,
});


function get_irn_dialog(frm) {
const dialog = new frappe.ui.Dialog({
title: __("Create Purchase Invoice"),
fields: [
{
label: "IRN",
fieldname: "irn",
fieldtype: "Data",
reqd: 1,
},
{
label: "Company GSTIN",
fieldname: "gstin",
fieldtype: "Data",
reqd: 1,
}
],
primary_action(values) {
taxpayer_api.call(
method ="india_compliance.gst_india.overrides.purchase_invoice.create_purchase_invoice_from_irn",
args= {
company_gstin: values.gstin,
irn: values.irn,
},
function (r){
dialog.hide();
frappe.set_route("purchase-invoice", r.message);
},
);
},
});
dialog.show();
}

function toggle_reverse_charge(frm) {
let is_read_only = 0;
if (frm.doc.gst_category !== "Overseas") is_read_only = 0;
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024, Resilient Tech and contributors
// For license information, please see license.txt

// frappe.ui.form.on("e-Invoice Mapping", {
// refresh(frm) {

// },
// });
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-09-20 15:05:52.401445",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"party_type",
"party",
"erpnext_fieldname",
"erpnext_value",
"log_value"
],
"fields": [
{
"fieldname": "party_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Party Type",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Party",
"options": "party_type"
},
{
"fieldname": "erpnext_fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Erpnext Fieldname"
},
{
"fieldname": "erpnext_value",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Erpnext Value"
},
{
"fieldname": "log_value",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Log Value"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-09-20 15:09:27.296167",
"modified_by": "Administrator",
"module": "GST India",
"name": "e-Invoice Mapping",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Resilient Tech and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class eInvoiceMapping(Document):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Resilient Tech and Contributors
# See license.txt

# import frappe
from frappe.tests.utils import FrappeTestCase


class TesteInvoiceMapping(FrappeTestCase):
pass
211 changes: 211 additions & 0 deletions india_compliance/gst_india/overrides/purchase_invoice.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import json

import jwt

import frappe
from frappe import _
from frappe.utils import flt
from erpnext.accounts.doctype.sales_invoice.sales_invoice import update_address

from india_compliance.gst_india.api_classes.taxpayer_base import (
TaxpayerBaseAPI,
otp_handler,
)
from india_compliance.gst_india.api_classes.taxpayer_e_invoice import (
EInvoiceAPI as TaxpayerEInvoiceAPI,
)
from india_compliance.gst_india.overrides.sales_invoice import (
update_dashboard_with_gst_logs,
)
Expand Down Expand Up @@ -52,6 +64,18 @@ def validate(doc, method=None):
validate_supplier_invoice_number(doc)
validate_with_inward_supply(doc)
set_reconciliation_status(doc)
update_item_mapping(doc)


def update_item_mapping(doc):
pass
# if not frappe.db.exists(
# "e-Invoice Log",
# {"reference_name": doc.name, "reference_doctype": "Purchase Invoice"},
# ):
# return

# # TODO


def on_cancel(doc, method=None):
Expand Down Expand Up @@ -166,6 +190,7 @@ def get_dashboard_data(data):
"e-Waybill Log",
"Integration Request",
"GST Inward Supply",
"e-Invoice Log",
)

return data
Expand Down Expand Up @@ -276,3 +301,189 @@ def validate_hsn_codes(doc):
throw=True,
message="GST HSN Code is mandatory for Overseas Purchase Invoice.<br>",
)


def fetch_irn_details(company_gstin, irn):
return TaxpayerEInvoiceAPI(company_gstin=company_gstin).get_irn_details(irn)


@frappe.whitelist()
@otp_handler
def create_purchase_invoice_from_irn(company_gstin, irn):
TaxpayerBaseAPI(company_gstin).validate_auth_token()

response = fetch_irn_details(company_gstin, irn)
response = frappe._dict(response.data)

invoice_data = json.loads(
jwt.decode(response.SignedInvoice, options={"verify_signature": False})["data"]
)

supplier_name, supplier_address_name = get_party_details(
invoice_data.get("SellerDtls"), party_type="Company" # to change
)
buyer_name, buyer_address_name = get_party_details(
invoice_data.get("BuyerDtls"), party_type="Company"
)
items, unmapped_items = get_item_info(invoice_data.get("ItemList"), supplier_name)

doc = create_purchase_invoice(
supplier_name, supplier_address_name, buyer_address_name, invoice_data, items
)

create_item_mapping(unmapped_items, doc.supplier)

e_invoice_log = frappe.get_doc(
{
"doctype": "e-Invoice Log",
"reference_doctype": "Purchase Invoice",
"reference_name": doc.name,
"irn": invoice_data.get("Irn"),
"is_generated_from_irn": 0,
"acknowledgement_number": invoice_data.get("AckNo"),
"acknowledged_on": invoice_data.get("AckDt"),
"invoice_data": frappe.as_json(invoice_data, indent=4),
}
)
e_invoice_log.save(ignore_permissions=True)

return doc.name


def create_purchase_invoice(
supplier_name, supplier_address_name, buyer_address_name, invoice_data, items
):
doc = frappe.get_doc(
{
"doctype": "Purchase Invoice",
"supplier": supplier_name,
"company": "Shalibhadra Metal Corporation",
"posting_date": invoice_data.get("AckDt"),
"due_date": frappe.utils.nowdate(),
"items": [
{
"item_name": item.get("PrdDesc"),
"qty": item.get("Qty"),
"rate": item.get("UnitPrice"),
"uom": item.get("Unit"),
"amount": item.get("AssAmt"),
}
for item in items
],
}
)

update_address(doc, "supplier_address", "address_display", supplier_address_name)
update_address(
doc, "billing_address", "billing_address_display", buyer_address_name
)

doc.flags.ignore_validate = True
doc.insert(ignore_mandatory=True)
return doc


def get_item_info(items, supplier):
unmapped_items = {"item_name": [], "uom": []}

mapped_item_names = frappe.get_all(
"e-Invoice Mapping",
filters={"party": supplier, "erpnext_fieldname": "item_name"},
fields=["log_value", "erpnext_value"],
)
item_name_map = {
item.get("log_value"): item.get("erpnext_value") for item in mapped_item_names
}
mapped_item_uoms = frappe.get_all(
"e-Invoice Mapping",
filters={"party": supplier, "erpnext_fieldname": "uom"},
fields=["log_value", "erpnext_value"],
)
item_uom_map = {
item.get("log_value"): item.get("erpnext_value") for item in mapped_item_uoms
}

for item in items:
if item_desc := item_name_map.get(item.get("PrdDesc")):
item["PrdDesc"] = item_desc
else:
unmapped_items["item_name"].append(item.get("PrdDesc"))

if item_uom := item_uom_map.get(item.get("Unit")):
item["Unit"] = item_uom
else:
unmapped_items["uom"].append(item.get("Unit"))

return items, unmapped_items


def create_item_mapping(unmapped_items, supplier):
def save_mapping(fieldname, value):
frappe.get_doc(
{
"doctype": "e-Invoice Mapping",
"party_type": "Company", # to change
"party": supplier,
"erpnext_fieldname": fieldname,
"log_value": value,
}
).save()

for item_name in unmapped_items.get("item_name", []):
save_mapping("item_name", item_name)

for uom_value in unmapped_items.get("uom", []):
save_mapping("uom", uom_value)


def get_party_details(party_details, party_type):
try:
address_doc = frappe.get_doc(
"Address",
{
"gstin": party_details.get("Gstin") or None,
"pincode": party_details.get("Pin"),
"state": party_details.get("Loc"),
},
)

except frappe.DoesNotExistError:
# not able to handle
frappe.clear_last_message()
frappe.throw(
_(
"Address with GSTIN {gstin}, Pincode {pincode}, and State {state} not found"
).format(
gstin=party_details.get("Gstin"),
pincode=party_details.get("Pin"),
state=party_details.get("Loc"),
)
)

for link in address_doc.links:
if link.link_doctype == party_type:
return link.link_name, address_doc.name

frappe.throw(f"{party_type.capitalize()} not found with this address")


@frappe.whitelist()
def get_item_details(args, doc):
from erpnext.stock.get_item_details import get_item_details

doc = json.loads(doc)
data = get_item_details(args, doc)

if not frappe.db.exists("e-Invoice Log", {"reference_name": doc.get("name")}):
return data

args = json.loads(args)
data.rate = args.get("net_rate")
data.qty = args.get("qty")
data.uom = args.get("uom")
data.price_list_rate = 0
data.discount_percentage = 0
data.discount_amount = 20
data.margin_rate_or_amount = data.rate

return data
Loading

0 comments on commit a7da87f

Please sign in to comment.